class Chef::Resource::AptRepository
Constants
- LIST_APT_KEY_FINGERPRINTS
Public Instance Methods
Source
# File lib/chef/resource/apt_repository.rb, line 483 def build_repo(uri, distribution, components, trusted, arch, signed_by, 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 << "signed-by=#{signed_by}" if signed_by 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
build complete repo text that will be written to the config @param [String] uri @param [Array] components @param [Boolean] trusted @param [String] arch @param [String] signed_by @param [Array] options @param [Boolean] add_src
@return [String] complete repo config text
Source
# File lib/chef/resource/apt_repository.rb, line 507 def cleanup_legacy_file! legacy_path = "/etc/apt/sources.list.d/#{new_resource.name}.list" if new_resource.name != new_resource.repo_name && ::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
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]
Source
# File lib/chef/resource/apt_repository.rb, line 255 def cookbook_name new_resource.cookbook || new_resource.cookbook_name end
return the specified cookbook name or the cookbook containing the resource.
@return [String] name of the cookbook
Source
# File lib/chef/resource/apt_repository.rb, line 202 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 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
Source
# File lib/chef/resource/apt_repository.rb, line 217 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
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
Source
# File lib/chef/resource/apt_repository.rb, line 263 def has_cookbook_file?(fn) run_context.has_cookbook_file_in_cookbook?(cookbook_name, fn) 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
Source
# File lib/chef/resource/apt_repository.rb, line 379 def install_key_from_keyserver(key, keyserver = new_resource.keyserver) if new_resource.signed_by install_key_from_keyserver_to_keyring(key, keyserver, keyring_path) return end 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
@param [String] key @param [String] keyserver
@raise [RuntimeError] Invalid key which can’t verify the apt repository
@return [void]
Source
# File lib/chef/resource/apt_repository.rb, line 403 def install_key_from_keyserver_to_keyring(key, keyserver, keyring) keyserver = "hkp://#{keyserver}:80" unless keyserver.start_with?("hkp://") cmd = "gpg --no-default-keyring --keyring #{keyring}" cmd << " --keyserver-options http-proxy=#{new_resource.key_proxy}" if new_resource.key_proxy cmd << " --keyserver #{keyserver}" cmd << " --recv #{key}" execute "install-key #{key}" do command cmd default_env true sensitive new_resource.sensitive not_if do present = shell_out(*%W{gpg --no-default-keyring --keyring #{keyring} --list-public-keys --with-fingerprint --with-colons #{key}}).exitstatus != 0 present && keyring_key_is_valid?(keyring, 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 keyring_key_is_valid?(keyring, key.upcase) end
@param [String] key @param [String] keyserver @param [String] keyring
Source
# File lib/chef/resource/apt_repository.rb, line 308 def install_key_from_uri(key) key_name = key.gsub(/[^0-9A-Za-z\-]/, "_") keyfile_path = ::File.join(Chef::Config[:file_cache_path], key_name) tmp_dir = Dir.mktmpdir(".gpg") at_exit { FileUtils.remove_entry(tmp_dir) } if new_resource.signed_by keyfile_path = keyring_path directory "/etc/apt/keyrings" do mode "0755" end end declare_resource(key_type(key), keyfile_path) do source key mode "0644" sensitive new_resource.sensitive action :create verify "gpg --homedir #{tmp_dir} %{path}" end # If signed by is true, then we don't need to # add to the default keyring. Instead make sure it's dearmored if new_resource.signed_by execute "gpg dearmor key" do input ::File.read(keyfile_path) command [ "gpg", "--batch", "--yes", "--dearmor", "-o", keyfile_path ] default_env true sensitive new_resource.sensitive action :run only_if { ::File.read(keyfile_path).include?("-----BEGIN PGP PUBLIC KEY BLOCK-----") } notifies :run, "execute[apt-cache gencaches]", :immediately end else execute "apt-key add #{keyfile_path}" do command [ "apt-key", "add", keyfile_path ] default_env true sensitive new_resource.sensitive action :run not_if { no_new_keys?(keyfile_path) } notifies :run, "execute[apt-cache gencaches]", :immediately end end 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]
Source
# File lib/chef/resource/apt_repository.rb, line 431 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::HTTPClientException => e raise "Could not access Launchpad ppa API: #{e.message}" end
@param [String] owner @param [String] repo
@raise [RuntimeError] Could not access the Launchpad PPA API
@return [void]
Source
# File lib/chef/resource/apt_repository.rb, line 191 def is_key_id?(id) id = id[2..] if id.start_with?("0x") id =~ /^\h+$/ && [8, 16, 40].include?(id.length) 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
Source
# File lib/chef/resource/apt_repository.rb, line 443 def is_ppa_url?(url) url.start_with?("ppa:") 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
Source
# File lib/chef/resource/apt_repository.rb, line 233 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
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
Source
# File lib/chef/resource/apt_repository.rb, line 287 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
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
Source
# File lib/chef/resource/apt_repository.rb, line 244 def keyring_key_is_valid?(keyring, key) valid = shell_out("gpg", "--no-default-keyring", "--keyring", keyring, "--list-public-keys", key).stdout.each_line.none?(/\[(expired|revoked):/) logger.debug "key #{key} #{valid ? "is valid" : "is not valid"}" valid end
validate the key against the a gpg keyring to see if that version is expired @param [String] key
@return [Boolean] is the key valid or not
Source
# File lib/chef/resource/apt_repository.rb, line 297 def keyring_path "/etc/apt/keyrings/#{new_resource.repo_name}.gpg" end
Source
# File lib/chef/resource/apt_repository.rb, line 359 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
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
Source
# File lib/chef/resource/apt_repository.rb, line 465 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
given a PPA return a PPA URL in ppa.launchpad.net format @param [String] ppa the ppa URL
@return [String] full PPA URL
Source
# File lib/chef/resource/apt_repository.rb, line 272 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 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
Source
# File lib/chef/resource/apt_repository.rb, line 453 def repo_components if is_ppa_url?(new_resource.uri) && new_resource.components.empty? "main" else new_resource.components end 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