Commit 4010da1d authored by Tim Smith's avatar Tim Smith
Browse files

Require Chef 12.9 and remove apt_update and apt_repository resources



These are built into Chef now. Use the built in resources and require a modern Chef release.
Signed-off-by: default avatarTim Smith <tsmith@chef.io>
parent 5571e451
......@@ -13,11 +13,11 @@ platforms:
- name: debian-8.6
- name: ubuntu-14.04
- name: ubuntu-16.04
- name: ubuntu-16.04-chef-12.1
- name: ubuntu-16.04-chef-12.9
driver_config:
box: bento/ubuntu-16.04
provisioner:
require_chef_omnibus: 12.1.0
require_chef_omnibus: 12.9.41
suites:
- name: default
......
......@@ -15,7 +15,7 @@ May work with or without modification on other Debian derivatives.
### Chef
- Chef 12.1+
- Chef 12.9+
### Cookbooks
......@@ -151,94 +151,6 @@ There is an `interface_ipaddress` method that returns the IP address for a parti
## Resources/Providers
### apt_repository
This resource provides an easy way to manage additional APT repositories. Adding a new repository will notify running the `execute[apt-get-update]` resource immediately.
#### Actions
- `:add`: creates a repository file and builds the repository listing (default)
- `:remove`: removes the repository file
#### Attribute Parameters
- repo_name: name attribute. The name of the channel to discover
- uri: the base of the Debian distribution
- distribution: this is usually your release's codename...ie something like `karmic`, `lucid` or `maverick`
- components: package groupings... when in doubt use `main`
- arch: constrain package to a particular arch like `i386`, `amd64` or even `armhf` or `powerpc`. Defaults to nil.
- trusted: treat all packages from this repository as authenticated regardless of signature
- deb_src: whether or not to add the repository as a source repo as well - value can be `true` or `false`, default `false`.
- keyserver: the GPG keyserver where the key for the repo should be retrieved
- key: if a `keyserver` is provided, this is assumed to be the fingerprint, otherwise it can be either the URI to the GPG key for the repo, or a cookbook_file.
- key_proxy: if set, pass the specified proxy via `http-proxy=` to GPG.
- cookbook: if key should be a cookbook_file, specify a cookbook where the key is located for files/default. Defaults to nil, so it will use the cookbook where the resource is used.
#### Examples
Add the Zenoss repo:
```ruby
apt_repository 'zenoss' do
uri 'http://dev.zenoss.org/deb'
components ['main', 'stable']
end
```
Enable Ubuntu [multiverse](https://help.ubuntu.com/community/Repositories/Ubuntu) repositories:
```ruby
apt_repository 'security-ubuntu-multiverse' do
uri 'http://security.ubuntu.com/ubuntu'
distribution 'trusty-security'
components ['multiverse']
deb_src true
end
```
Add the Nginx PPA, autodetect the key and repository url:
```ruby
apt_repository 'nginx-php' do
uri 'ppa:nginx/stable'
distribution node['lsb']['codename']
end
```
Add the JuJu PPA, grab the key from the keyserver, and add source repo:
```ruby
apt_repository 'juju' do
uri 'http://ppa.launchpad.net/juju/stable/ubuntu'
components ['main']
distribution 'trusty'
key 'C8068B11'
keyserver 'keyserver.ubuntu.com'
action :add
deb_src true
end
```
Add the Cloudera Repo of CDH4 packages for Ubuntu 12.04 on AMD64:
```ruby
apt_repository 'cloudera' do
uri 'http://archive.cloudera.com/cdh4/ubuntu/precise/amd64/cdh'
arch 'amd64'
distribution 'precise-cdh4'
components ['contrib']
key 'http://archive.cloudera.com/debian/archive.key'
end
```
Remove Zenoss repo:
```ruby
apt_repository 'zenoss' do
action :remove
end
```
### apt_preference
This resource provides an easy way to pin packages in /etc/apt/preferences.d. Although apt-pinning is quite helpful from time to time please note that Debian does not encourage its use without thorough consideration.
......
unless defined? Chef::Resource::AptUpdate
require 'chef_compat/copied_from_chef/chef/dsl/declare_resource'
require 'chef/mixin/shell_out'
class AptUpdate < ChefCompat::Resource
include ChefCompat::CopiedFromChef::Chef::DSL::DeclareResource
include Chef::Mixin::ShellOut
resource_name :apt_update
provides :apt_update, os: 'linux'
property :frequency, Integer, default: 86_400
default_action :periodic
allowed_actions :update, :periodic
APT_CONF_DIR = '/etc/apt/apt.conf.d'.freeze
STAMP_DIR = '/var/lib/apt/periodic'.freeze
action :periodic do
unless apt_up_to_date? # ~FC023
converge_by 'update new lists of packages' do
do_update
end
end
end
action :update do
converge_by 'force update new lists of packages' do
do_update
end
end
# Determines whether we need to run `apt-get update`
#
# @return [Boolean]
def apt_up_to_date?
::File.exist?("#{STAMP_DIR}/update-success-stamp") &&
::File.mtime("#{STAMP_DIR}/update-success-stamp") > Time.now - new_resource.frequency
end
def do_update
[STAMP_DIR, APT_CONF_DIR].each do |d|
build_resource(:directory, d, caller[0]) do
recursive true
end.run_action(:create)
end
build_resource(:file, "#{APT_CONF_DIR}/15update-stamp", caller[0]) do
content "APT::Update::Post-Invoke-Success {\"touch #{STAMP_DIR}/update-success-stamp 2>/dev/null || true\";};"
end.run_action(:create_if_missing)
shell_out!('apt-get -q update')
end
end
end
......@@ -27,18 +27,6 @@ module Apt
!which('apt-get').nil?
end
# Determines whether we need to run `apt-get update`
#
# @return [Boolean]
def apt_up_to_date?
if ::File.exist?('/var/lib/apt/periodic/update-success-stamp') &&
::File.mtime('/var/lib/apt/periodic/update-success-stamp') > Time.now - node['apt']['periodic_update_min_delay']
true
else
false
end
end
# Finds a command in $PATH
#
# @return [String, nil]
......
......@@ -14,31 +14,4 @@ if defined?(ChefSpec)
ChefSpec::Matchers::ResourceMatcher.new(:apt_preference, :remove, resource_name)
end
#################
# apt_repository
#################
ChefSpec.define_matcher :apt_repository
def add_apt_repository(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:apt_repository, :add, resource_name)
end
def remove_apt_repository(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:apt_repository, :remove, resource_name)
end
#################
# apt_update
#################
ChefSpec.define_matcher :apt_update
def update_apt_update(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:apt_update, :update, resource_name)
end
def periodic_apt_update(resource_name)
ChefSpec::Matchers::ResourceMatcher.new(:apt_update, :periodic, resource_name)
end
end
......@@ -16,6 +16,4 @@ end
source_url 'https://github.com/chef-cookbooks/apt'
issues_url 'https://github.com/chef-cookbooks/apt/issues'
chef_version '>= 12.1' if respond_to?(:chef_version)
depends 'compat_resource', '>= 12.16.3'
chef_version '>= 12.9'
#
# Cookbook:: apt
# Provider:: repository
#
# Copyright:: 2010-2016, Chef Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use_inline_resources
def whyrun_supported?
true
end
# install apt key from keyserver
def install_key_from_keyserver(key, keyserver, key_proxy)
execute "install-key #{key}" do
if keyserver.start_with?('hkp://')
command "apt-key adv --keyserver #{keyserver} --recv #{key}"
elsif key_proxy.empty?
command "apt-key adv --keyserver hkp://#{keyserver}:80 --recv #{key}"
else
command "apt-key adv --keyserver-options http-proxy=#{key_proxy} --keyserver hkp://#{keyserver}:80 --recv #{key}"
end
sensitive new_resource.sensitive if respond_to?(:sensitive)
action :run
not_if do
key_present = extract_fingerprints_from_cmd('apt-key finger').any? do |fingerprint|
fingerprint.end_with?(key.upcase)
end
key_present && key_is_valid('apt-key list', key.upcase)
end
end
ruby_block "validate-key #{key}" do
block do
raise "The key #{key} is no longer valid and cannot be used for an apt repository."
end
not_if { key_is_valid('apt-key list', key.upcase) }
end
end
# run command and extract gpg ids
def extract_fingerprints_from_cmd(cmd)
so = Mixlib::ShellOut.new(cmd, env: { 'LANG' => 'en_US', 'LANGUAGE' => 'en_US' })
so.run_command
so.stdout.split(/\n/).map do |t|
if z = t.match(/^ +Key fingerprint = ([0-9A-F ]+)/) # rubocop: disable Lint/AssignmentInCondition
z[1].split.join
end
end.compact
end
# determine whether apt thinks the key is still valid
def key_is_valid(cmd, key)
valid = true
so = Mixlib::ShellOut.new(cmd, env: { 'LANG' => 'en_US', 'LANGUAGE' => 'en_US' })
so.run_command
# rubocop:disable Style/Next
so.stdout.split(/\n/).map do |t|
if t =~ %r{^\/#{key}.*\[expired: .*\]$}
Chef::Log.debug "Found expired key: #{t}"
valid = false
break
end
end
Chef::Log.debug "key #{key} validity: #{valid}"
valid
end
# install apt key from URI
def install_key_from_uri(uri)
key_name = uri.split(%r{\/}).last
cached_keyfile = "#{Chef::Config[:file_cache_path]}/#{key_name}"
if new_resource.key =~ /http/
remote_file cached_keyfile do
source new_resource.key
mode '0644'
sensitive new_resource.sensitive if respond_to?(:sensitive)
action :create
end
else
cookbook_file cached_keyfile do
source new_resource.key
cookbook new_resource.cookbook
mode '0644'
sensitive new_resource.sensitive if respond_to?(:sensitive)
action :create
end
ruby_block "validate-key #{cached_keyfile}" do
block do
raise "The key #{cached_keyfile} is no longer valid and cannot be used for an apt repository." unless key_is_valid("gpg #{cached_keyfile}", '')
end
end
end
execute "install-key #{key_name}" do
command "apt-key add #{cached_keyfile}"
sensitive new_resource.sensitive if respond_to?(:sensitive)
action :run
not_if do
installed_keys = extract_fingerprints_from_cmd('apt-key finger')
proposed_keys = extract_fingerprints_from_cmd("gpg --with-fingerprint #{cached_keyfile}")
(installed_keys & proposed_keys).sort == proposed_keys.sort
end
end
end
# build repo file contents
def build_repo(uri, distribution, components, trusted, arch, add_deb_src) # rubocop: disable Metrics/ParameterLists
uri = '"' + uri + '"' unless uri.start_with?('"', "'")
components = components.join(' ') if components.respond_to?(:join)
repo_options = []
repo_options << "arch=#{arch}" if arch
repo_options << 'trusted=yes' if trusted
repo_opts = '[' + repo_options.join(' ') + ']' unless repo_options.empty?
repo_info = "#{repo_opts} #{uri} #{distribution} #{components}\n".lstrip
repo = "deb #{repo_info}"
repo << "deb-src #{repo_info}" if add_deb_src
repo
end
def get_ppa_key(ppa_owner, ppa_repo, key_proxy)
# Launchpad has currently only one stable API which is marked as EOL April 2015.
# The new api in devel still uses the same api call for +archive, so I made the version
# configurable to provide some sort of workaround if api 1.0 ceases to exist.
# See https://launchpad.net/+apidoc/
launchpad_ppa_api = "https://launchpad.net/api/#{node['apt']['launchpad_api_version']}/~%s/+archive/%s"
default_keyserver = 'keyserver.ubuntu.com'
require 'open-uri'
api_query = format("#{launchpad_ppa_api}/signing_key_fingerprint", ppa_owner, ppa_repo)
begin
key_id = open(api_query).read.delete('"')
rescue OpenURI::HTTPError => e
error = 'Could not access launchpad ppa key api: HttpError: ' + e.message
raise error
rescue SocketError => e
error = 'Could not access launchpad ppa key api: SocketError: ' + e.message
raise error
end
install_key_from_keyserver(key_id, default_keyserver, key_proxy)
end
# fetch ppa key, return full repo url
def get_ppa_url(ppa, key_proxy)
repo_schema = 'http://ppa.launchpad.net/%s/%s/ubuntu'
# ppa:user/repo logic ported from
# http://bazaar.launchpad.net/~ubuntu-core-dev/software-properties/main/view/head:/softwareproperties/ppa.py#L86
return false unless ppa.start_with?('ppa:')
ppa_name = ppa.split(':')[1]
ppa_owner = ppa_name.split('/')[0]
ppa_repo = ppa_name.split('/')[1]
ppa_repo = 'ppa' if ppa_repo.nil?
get_ppa_key(ppa_owner, ppa_repo, key_proxy)
format(repo_schema, ppa_owner, ppa_repo)
end
action :add do
# add key
if new_resource.keyserver && new_resource.key
install_key_from_keyserver(new_resource.key, new_resource.keyserver, new_resource.key_proxy)
elsif new_resource.key
install_key_from_uri(new_resource.key)
end
file '/var/lib/apt/periodic/update-success-stamp' do
action :nothing
end
execute 'apt-cache gencaches' do
ignore_failure true
action :nothing
end
execute 'apt-get update' do
command "apt-get update -o Dir::Etc::sourcelist='sources.list.d/#{new_resource.name}.list' -o Dir::Etc::sourceparts='-' -o APT::Get::List-Cleanup='0'"
ignore_failure true
sensitive new_resource.sensitive if respond_to?(:sensitive)
action :nothing
notifies :run, 'execute[apt-cache gencaches]', :immediately
end
repository = if new_resource.uri.start_with?('ppa:')
# build ppa repo file
build_repo(
get_ppa_url(new_resource.uri, new_resource.key_proxy),
new_resource.distribution,
'main',
new_resource.trusted,
new_resource.arch,
new_resource.deb_src
)
else
# build repo file
build_repo(
new_resource.uri,
new_resource.distribution,
new_resource.components,
new_resource.trusted,
new_resource.arch,
new_resource.deb_src
)
end
file "/etc/apt/sources.list.d/#{new_resource.name}.list" do
owner 'root'
group 'root'
mode '0644'
content repository
sensitive new_resource.sensitive if respond_to?(:sensitive)
action :create
notifies :delete, 'file[/var/lib/apt/periodic/update-success-stamp]', :immediately
notifies :run, 'execute[apt-get update]', :immediately if new_resource.cache_rebuild
end
end
action :remove do
if ::File.exist?("/etc/apt/sources.list.d/#{new_resource.name}.list")
Chef::Log.info "Removing #{new_resource.name} repository from /etc/apt/sources.list.d/"
file "/etc/apt/sources.list.d/#{new_resource.name}.list" do
sensitive new_resource.sensitive if respond_to?(:sensitive)
action :delete
end
end
end
#
# Cookbook:: apt
# Resource:: repository
#
# Copyright:: 2010-2016, Chef Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
actions :add, :remove
default_action :add
state_attrs :arch,
:cache_rebuild,
:components,
:cookbook,
:deb_src,
:distribution,
:key,
:keyserver,
:key_proxy,
:repo_name,
:trusted,
:uri,
:sensitive
# name of the repo, used for source.list filename
attribute :repo_name, kind_of: String, name_attribute: true, regex: [/^([a-z]|[A-Z]|[0-9]|_|-|\.)+$/]
attribute :uri, kind_of: String
attribute :distribution, kind_of: String
attribute :components, kind_of: Array, default: []
attribute :arch, kind_of: String, default: nil
attribute :trusted, kind_of: [TrueClass, FalseClass], default: false
# whether or not to add the repository as a source repo as well
attribute :deb_src, default: false
attribute :keyserver, kind_of: String, default: nil
attribute :key, kind_of: String, default: nil
attribute :key_proxy, kind_of: String, default: node['apt']['key_proxy']
attribute :cookbook, kind_of: String, default: nil
# trigger cache rebuild
# If not you can trigger in the recipe itself after checking the status of resource.updated{_by_last_action}?
attribute :cache_rebuild, kind_of: [TrueClass, FalseClass], default: true
# Hide content of the source file, don't show output for commands being run, etc.
attribute :sensitive, kind_of: [TrueClass, FalseClass], default: false
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment