diff options
author | Thom May <thom@chef.io> | 2016-04-01 09:50:32 +0100 |
---|---|---|
committer | Thom May <thom@chef.io> | 2016-04-08 13:00:49 -0700 |
commit | 79548accdf02e199d48a09b6eb0cc8d8613b0916 (patch) | |
tree | 4cc9150c10a0712e3025b137e4a859731b410ad6 /lib | |
parent | 1eb4511c15f5042ecb0432170c6532fb065a842c (diff) | |
download | chef-79548accdf02e199d48a09b6eb0cc8d8613b0916.tar.gz |
Add an apt_repository resourcetm/apt_repository
More or less stolen from the apt cookbook, but tweaked and fixes a heap
of bugs.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/chef/provider/apt_repository.rb | 255 | ||||
-rw-r--r-- | lib/chef/providers.rb | 1 | ||||
-rw-r--r-- | lib/chef/resource/apt_repository.rb | 47 | ||||
-rw-r--r-- | lib/chef/resources.rb | 1 |
4 files changed, 304 insertions, 0 deletions
diff --git a/lib/chef/provider/apt_repository.rb b/lib/chef/provider/apt_repository.rb new file mode 100644 index 0000000000..8880a059ac --- /dev/null +++ b/lib/chef/provider/apt_repository.rb @@ -0,0 +1,255 @@ +# +# Author:: Thom May (<thom@chef.io>) +# Copyright:: Copyright (c) 2016 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# 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. +# + +require "chef/resource" +require "chef/dsl/declare_resource" +require "chef/mixin/shell_out" +require "chef/http/simple" +require "chef/provider/noop" + +class Chef + class Provider + class AptRepository < Chef::Provider + use_inline_resources + + include Chef::Mixin::ShellOut + + provides :apt_repository do + uses_apt? + end + + def whyrun_supported? + true + end + + def load_current_resource + end + + action :add do + unless new_resource.key.nil? + if is_key_id?(new_resource.key) && !has_cookbook_file?(new_resource.key) + install_key_from_keyserver + else + install_key_from_uri + end + end + + declare_resource(:execute, "apt-cache gencaches") do + ignore_failure true + action :nothing + end + + declare_resource(:apt_update, new_resource.name) do + ignore_failure true + action :nothing + end + + components = if is_ppa_url?(new_resource.uri) && new_resource.components.empty? + "main" + else + new_resource.components + end + + repo = build_repo( + new_resource.uri, + new_resource.distribution, + components, + new_resource.trusted, + new_resource.arch, + new_resource.deb_src + ) + + declare_resource(:file, "/etc/apt/sources.list.d/#{new_resource.name}.list") do + owner "root" + group "root" + mode "0644" + content repo + sensitive new_resource.sensitive + action :create + notifies :run, "execute[apt-cache gencaches]", :immediately + notifies :update, "apt_update[#{new_resource.name}]", :immediately if new_resource.cache_rebuild + end + end + + action :remove do + if ::File.exist?("/etc/apt/sources.list.d/#{new_resource.name}.list") + converge_by "Removing #{new_resource.name} repository from /etc/apt/sources.list.d/" do + declare_resource(:file, "/etc/apt/sources.list.d/#{new_resource.name}.list") do + sensitive new_resource.sensitive + action :delete + notifies :update, "apt_update[#{new_resource.name}]", :immediately if new_resource.cache_rebuild + end + + declare_resource(:apt_update, new_resource.name) do + ignore_failure true + action :nothing + end + + end + end + end + + def self.uses_apt? + ENV["PATH"] ||= "" + paths = %w{ /bin /usr/bin /sbin /usr/sbin } + ENV["PATH"].split(::File::PATH_SEPARATOR) + paths.any? { |path| ::File.executable?(::File.join(path, "apt-get")) } + end + + def is_key_id?(id) + id = id[2..-1] if id.start_with?("0x") + id =~ /^\h+$/ && [8, 16, 40].include?(id.length) + end + + def extract_fingerprints_from_cmd(cmd) + so = shell_out(cmd) + so.run_command + so.stdout.split(/\n/).map do |t| + if z = t.match(/^ +Key fingerprint = ([0-9A-F ]+)/) + z[1].split.join + end + end.compact + end + + def key_is_valid?(cmd, key) + valid = true + + so = shell_out(cmd) + so.run_command + 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} #{valid ? "is valid" : "is not valid"}" + valid + end + + def cookbook_name + new_resource.cookbook || new_resource.cookbook_name + end + + def has_cookbook_file?(fn) + run_context.has_cookbook_file_in_cookbook?(cookbook_name, fn) + end + + def no_new_keys?(file) + installed_keys = extract_fingerprints_from_cmd("apt-key finger") + proposed_keys = extract_fingerprints_from_cmd("gpg --with-fingerprint #{file}") + (installed_keys & proposed_keys).sort == proposed_keys.sort + end + + def install_key_from_uri + key_name = new_resource.key.split(%r{\/}).last + cached_keyfile = ::File.join(Chef::Config[:file_cache_path], key_name) + type = if new_resource.key.start_with?("http") + :remote_file + elsif has_cookbook_file?(new_resource.key) + :cookbook_file + else + raise Chef::Exceptions::FileNotFound, "Cannot locate key file" + end + + declare_resource(type, cached_keyfile) do + source new_resource.key + mode "0644" + sensitive new_resource.sensitive + action :create + end + + raise "The key #{cached_keyfile} is invalid and cannot be used to verify an apt repository." unless key_is_valid?("gpg #{cached_keyfile}", "") + + declare_resource(:execute, "apt-key add #{cached_keyfile}") do + sensitive new_resource.sensitive + action :run + not_if do + no_new_keys?(cached_keyfile) + end + notifies :run, "execute[apt-cache gencaches]", :immediately + end + end + + def install_key_from_keyserver(key = new_resource.key, keyserver = new_resource.keyserver) + cmd = "apt-key adv --recv" + cmd << " --keyserver-options http-proxy=#{new_resource.key_proxy}" if new_resource.key_proxy + cmd << " --keyserver " + cmd << if keyserver.start_with?("hkp://") + keyserver + else + "hkp://#{keyserver}:80" + end + + cmd << " #{key}" + + declare_resource(:execute, "install-key #{key}") do + command cmd + sensitive new_resource.sensitive + not_if do + present = extract_fingerprints_from_cmd("apt-key finger").any? do |fp| + fp.end_with? key.upcase + end + present && key_is_valid?("apt-key list", key.upcase) + end + notifies :run, "execute[apt-cache gencaches]", :immediately + end + + raise "The key #{key} is invalid and cannot be used to verify an apt repository." unless key_is_valid?("apt-key list", key.upcase) + end + + def install_ppa_key(owner, repo) + url = "https://launchpad.net/api/1.0/~#{owner}/+archive/#{repo}" + key_id = Chef::HTTP::Simple.new(url).get("signing_key_fingerprint").delete('"') + install_key_from_keyserver(key_id, "keyserver.ubuntu.com") + rescue Net::HTTPServerException => e + raise "Could not access Launchpad ppa API: #{e.message}" + end + + def is_ppa_url?(url) + url.start_with?("ppa:") + end + + def make_ppa_url(ppa) + return unless is_ppa_url?(ppa) + owner, repo = ppa[4..-1].split("/") + repo ||= "ppa" + + install_ppa_key(owner, repo) + "http://ppa.launchpad.net/#{owner}/#{repo}/ubuntu" + end + + def build_repo(uri, distribution, components, trusted, arch, add_src = false) + uri = make_ppa_url(uri) if is_ppa_url?(uri) + + uri = '"' + uri + '"' unless uri.start_with?("'", '"') + components = Array(components).join(" ") + options = "" + options << "arch=#{arch} " if arch + options << "trusted=yes" if trusted + options = "[#{options}]" unless options.empty? + info = "#{options} #{uri} #{distribution} #{components}\n".lstrip + repo = "deb #{info}" + repo << "deb-src #{info}" if add_src + repo + end + end + end +end + +Chef::Provider::Noop.provides :apt_resource diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index 80fadfc8c2..c723da030b 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -17,6 +17,7 @@ # require "chef/provider/apt_update" +require "chef/provider/apt_repository" require "chef/provider/batch" require "chef/provider/breakpoint" require "chef/provider/cookbook_file" diff --git a/lib/chef/resource/apt_repository.rb b/lib/chef/resource/apt_repository.rb new file mode 100644 index 0000000000..e1ea665858 --- /dev/null +++ b/lib/chef/resource/apt_repository.rb @@ -0,0 +1,47 @@ +# +# Author:: Thom May (<thom@chef.io>) +# Copyright:: Copyright (c) 2016 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# 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. +# + +require "chef/resource" + +class Chef + class Resource + class AptRepository < Chef::Resource + resource_name :apt_repository + provides :apt_repository + + property :repo_name, String, name_property: true + property :uri, String + property :distribution, String, default: lazy { node["lsb"]["codename"] } + property :components, Array, default: [] + property :arch, [String, nil], default: nil + property :trusted, [TrueClass, FalseClass], default: false + # whether or not to add the repository as a source repo, too + property :deb_src, [TrueClass, FalseClass], default: false + property :keyserver, [String, nil], default: "keyserver.ubuntu.com" + property :key, [String, nil], default: nil + property :key_proxy, [String, nil], default: nil + + property :cookbook, [String, nil], default: nil, desired_state: false + property :cache_rebuild, [TrueClass, FalseClass], default: true, desired_state: false + property :sensitive, [TrueClass, FalseClass], default: false, desired_state: false + + default_action :add + allowed_actions :add, :remove + end + end +end diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index 797f7b8b7e..d8cec8c51d 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -17,6 +17,7 @@ # require "chef/resource/apt_package" +require "chef/resource/apt_repository" require "chef/resource/apt_update" require "chef/resource/bash" require "chef/resource/batch" |