class Chef::Resource::AptRepository
Constants
- LIST_APT_KEY_FINGERPRINTS
Public Instance Methods
build complete repo text that will be written to the config @param [String] uri @param [Array] components @param [Boolean] trusted @param [String] arch @param [Array] options @param [Boolean] add_src
@return [String] complete repo config text
# File lib/chef/resource/apt_repository.rb, line 410 def build_repo(uri, distribution, components, trusted, arch, options, add_src = false) uri = make_ppa_url(uri) if is_ppa_url?(uri) uri = Addressable::URI.parse(uri) components = Array(components).join(" ") options_list = [] options_list << "arch=#{arch}" if arch options_list << "trusted=yes" if trusted options_list += options optstr = unless options_list.empty? "[" + options_list.join(" ") + "]" end info = [ optstr, uri.normalize.to_s, distribution, components ].compact.join(" ") repo = "deb #{info}\n" repo << "deb-src #{info}\n" if add_src repo end
clean up a potentially legacy file from before we fixed the usage of new_resource.name vs. new_resource.repo_name. We might have the name.list file hanging around and need to clean it up.
@return [void]
# File lib/chef/resource/apt_repository.rb, line 433 def cleanup_legacy_file! legacy_path = "/etc/apt/sources.list.d/#{new_resource.name}.list" if new_resource.name != new_resource.repo_name && ::TargetIO::File.exist?(legacy_path) converge_by "Cleaning up legacy #{legacy_path} repo file" do file legacy_path do action :delete # Not triggering an update since it isn't super likely to be needed. end end end end
return the specified cookbook name or the cookbook containing the resource.
@return [String] name of the cookbook
# File lib/chef/resource/apt_repository.rb, line 238 def cookbook_name new_resource.cookbook || new_resource.cookbook_name end
run the specified command and extract the fingerprints from the output accepts a command so it can be used to extract both the current key’s fingerprints and the fingerprint of the new key @param [Array<String>] cmd the command to run
@return [Array] an array of fingerprints
# File lib/chef/resource/apt_repository.rb, line 196 def extract_fingerprints_from_cmd(*cmd) so = shell_out(*cmd) so.stdout.split(/\n/).map do |t| if z = t.match(/^fpr:+([0-9A-F]+):/) z[1].split.join end end.compact end
run the specified command and extract the public key ids accepts the command so it can be used to extract both the current keys and the new keys @param [Array<String>] cmd the command to run
@return [Array] an array of key ids
# File lib/chef/resource/apt_repository.rb, line 211 def extract_public_keys_from_cmd(*cmd) so = shell_out(*cmd) # Sample output # pub:-:4096:1:D94AA3F0EFE21092:1336774248:::-:::scSC::::::23::0: so.stdout.split(/\n/).map do |t| if t.match(/^pub:/) f = t.split(":") f.slice(0, 6).join(":") end end.compact end
determine if a cookbook file is available in the run @param [String] fn the path to the cookbook file
@return [Boolean] cookbook file exists or doesn’t
# File lib/chef/resource/apt_repository.rb, line 246 def has_cookbook_file?(fn) run_context.has_cookbook_file_in_cookbook?(cookbook_name, fn) end
@param [String] key @param [String] keyserver
@raise [RuntimeError] Invalid key which can’t verify the apt repository
@return [void]
# File lib/chef/resource/apt_repository.rb, line 336 def install_key_from_keyserver(key, keyserver = new_resource.keyserver) execute "install-key #{key}" do command keyserver_install_cmd(key, keyserver) default_env true sensitive new_resource.sensitive not_if do present = extract_fingerprints_from_cmd(*LIST_APT_KEY_FINGERPRINTS).any? do |fp| fp.end_with? key.upcase end present && key_is_valid?(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?(key.upcase) end
Fetch the key using either cookbook_file or remote_file, validate it, and install it with apt-key add @param [String] key the key to install
@raise [RuntimeError] Invalid key which can’t verify the apt repository
@return [void]
# File lib/chef/resource/apt_repository.rb, line 287 def install_key_from_uri(key) key_name = key.gsub(/[^0-9A-Za-z\-]/, "_") cached_keyfile = ::File.join(Chef::Config[:file_cache_path], key_name) tmp_dir = TargetIO::Dir.mktmpdir(".gpg") at_exit { TargetIO::FileUtils.remove_entry(tmp_dir) } declare_resource(key_type(key), cached_keyfile) do source key mode "0644" sensitive new_resource.sensitive action :create verify "gpg --homedir #{tmp_dir} %{path}" end execute "apt-key add #{cached_keyfile}" do command [ "apt-key", "add", cached_keyfile ] default_env true sensitive new_resource.sensitive action :run not_if { no_new_keys?(cached_keyfile) } notifies :run, "execute[apt-cache gencaches]", :immediately end end
@param [String] owner @param [String] repo
@raise [RuntimeError] Could not access the Launchpad PPA API
@return [void]
# File lib/chef/resource/apt_repository.rb, line 359 def install_ppa_key(owner, repo) url = "https://launchpad.net/api/1.0/~#{owner}/+archive/#{repo}" key_id = TargetIO::HTTP.new(url).get("signing_key_fingerprint").delete('"') install_key_from_keyserver(key_id, "keyserver.ubuntu.com") rescue Net::HTTPClientException => e raise "Could not access Launchpad ppa API: #{e.message}" end
is the provided ID a key ID from a keyserver. Looks at length and HEX only values @param [String] id the key value passed by the user that may be an ID
# File lib/chef/resource/apt_repository.rb, line 185 def is_key_id?(id) id = id[2..] if id.start_with?("0x") id =~ /^\h+$/ && [8, 16, 40].include?(id.length) end
determine if the repository URL is a PPA @param [String] url the url of the repository
@return [Boolean] is the repo URL a PPA
# File lib/chef/resource/apt_repository.rb, line 371 def is_ppa_url?(url) url.start_with?("ppa:") end
validate the key against the apt keystore to see if that version is expired @param [String] key
@return [Boolean] is the key valid or not
# File lib/chef/resource/apt_repository.rb, line 227 def key_is_valid?(key) valid = shell_out("apt-key", "list").stdout.each_line.none?(%r{^\/#{key}.*\[expired: .*\]$}) logger.debug "key #{key} #{valid ? "is valid" : "is not valid"}" valid end
Given the provided key URI determine what kind of chef resource we need to fetch the key @param [String] uri the uri of the gpg key (local path or http URL)
@raise [Chef::Exceptions::FileNotFound] Key
isn’t remote or found in the current run
@return [Symbol] :remote_file or :cookbook_file
# File lib/chef/resource/apt_repository.rb, line 270 def key_type(uri) if uri.start_with?("http") :remote_file elsif has_cookbook_file?(uri) :cookbook_file else raise Chef::Exceptions::FileNotFound, "Cannot locate key file: #{uri}" end end
build the apt-key command to install the keyserver @param [String] key the key to install @param [String] keyserver the key server to use
@return [String] the full apt-key command to run
# File lib/chef/resource/apt_repository.rb, line 316 def keyserver_install_cmd(key, keyserver) cmd = "apt-key adv --no-tty --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}" cmd end
given a PPA return a PPA URL in ppa.launchpad.net format @param [String] ppa the ppa URL
@return [String] full PPA URL
# File lib/chef/resource/apt_repository.rb, line 393 def make_ppa_url(ppa) owner, repo = ppa[4..-1].split("/") repo ||= "ppa" install_ppa_key(owner, repo) "http://ppa.launchpad.net/#{owner}/#{repo}/ubuntu" end
determine if there are any new keys by comparing the fingerprints of installed keys to those of the passed file @param [String] file the keyfile of the new repository
@return [Boolean] true: no new keys in the file. false: there are new keys
# File lib/chef/resource/apt_repository.rb, line 255 def no_new_keys?(file) # Now we are using the option --with-colons that works across old os versions # as well as the latest (16.10). This for both `apt-key` and `gpg` commands installed_keys = extract_public_keys_from_cmd(*LIST_APT_KEY_FINGERPRINTS) proposed_keys = extract_public_keys_from_cmd("gpg", "--with-fingerprint", "--with-colons", file) (installed_keys & proposed_keys).sort == proposed_keys.sort end
determine the repository’s components:
- "components" property if defined - "main" if "components" not defined and the repo is a PPA URL - otherwise nothing
@return [String] the repository component
# File lib/chef/resource/apt_repository.rb, line 381 def repo_components if is_ppa_url?(new_resource.uri) && new_resource.components.empty? "main" else new_resource.components end end