class Chef::Resource::AptRepository

Constants

LIST_APT_KEY_FINGERPRINTS

Public Instance Methods

build_repo(uri, distribution, components, trusted, arch, options, add_src = false) click to toggle source

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
cleanup_legacy_file!() click to toggle source

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
cookbook_name() click to toggle source

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
extract_fingerprints_from_cmd(*cmd) click to toggle source

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
extract_public_keys_from_cmd(*cmd) click to toggle source

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
has_cookbook_file?(fn) click to toggle source

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
install_key_from_keyserver(key, keyserver = new_resource.keyserver) click to toggle source

@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
install_key_from_uri(key) click to toggle source

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
install_ppa_key(owner, repo) click to toggle source

@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_key_id?(id) click to toggle source

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
is_ppa_url?(url) click to toggle source

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
key_is_valid?(key) click to toggle source

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
key_type(uri) click to toggle source

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
keyserver_install_cmd(key, keyserver) click to toggle source

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
make_ppa_url(ppa) click to toggle source

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
no_new_keys?(file) click to toggle source

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
repo_components() click to toggle source

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