Class: Chef::Provider::AptRepository

Inherits:
Chef::Provider show all
Includes:
Mixin::ShellOut
Defined in:
lib/chef/provider/apt_repository.rb

Constant Summary collapse

LIST_APT_KEY_FINGERPRINTS =
%w{apt-key adv --list-public-keys --with-fingerprint --with-colons}.freeze

Instance Attribute Summary

Attributes inherited from Chef::Provider

#action, #current_resource, #logger, #new_resource, #recipe_name, #run_context

Instance Method Summary collapse

Methods included from Mixin::ShellOut

#a_to_s, apply_default_env, #clean_array, maybe_add_timeout, #shell_out, #shell_out!, #shell_out_compact, #shell_out_compact!, #shell_out_compact_timeout, #shell_out_compact_timeout!, #shell_out_with_systems_locale, #shell_out_with_systems_locale!

Methods included from Mixin::PathSanity

#enforce_path_sanity, #sanitized_path

Methods inherited from Chef::Provider

action, #action_nothing, #check_resource_semantics!, #cleanup_after_converge, #compile_and_converge_action, #converge_by, #converge_if_changed, #define_resource_requirements, #description, #events, include_resource_dsl?, include_resource_dsl_module, #initialize, #introduced, #node, #process_resource_requirements, provides, provides?, #requirements, #resource_collection, #resource_updated?, #run_action, #set_updated_status, supports?, use_inline_resources, #whyrun_mode?, #whyrun_supported?

Methods included from Mixin::Provides

#provided_as, #provides, #provides?

Methods included from Mixin::DescendantsTracker

#descendants, descendants, direct_descendants, #direct_descendants, find_descendants_by_name, #find_descendants_by_name, #inherited, store_inherited

Methods included from Mixin::LazyModuleInclude

#descendants, #include, #included

Methods included from Mixin::NotifyingBlock

#notifying_block, #subcontext_block

Methods included from DSL::DeclareResource

#build_resource, #declare_resource, #delete_resource, #delete_resource!, #edit_resource, #edit_resource!, #find_resource, #find_resource!, #resources, #with_run_context

Methods included from Mixin::PowershellOut

#powershell_out, #powershell_out!

Methods included from Mixin::WindowsArchitectureHelper

#assert_valid_windows_architecture!, #disable_wow64_file_redirection, #forced_32bit_override_required?, #is_i386_process_on_x86_64_windows?, #node_supports_windows_architecture?, #node_windows_architecture, #restore_wow64_file_redirection, #valid_windows_architecture?, #with_os_architecture, #wow64_architecture_override_required?, #wow64_directory

Methods included from Mixin::PowershellExec

#powershell_exec

Methods included from DSL::PlatformIntrospection

#docker?, #older_than_win_2012_or_8?, #platform?, #platform_family?, #value_for_platform, #value_for_platform_family

Constructor Details

This class inherits a constructor from Chef::Provider

Instance Method Details

#build_repo(uri, distribution, components, trusted, arch, add_src = false) ⇒ String

build complete repo text that will be written to the config

Parameters:

  • uri (String)
  • components (Array)
  • trusted (Boolean)
  • arch (String)
  • add_src (Boolean) (defaults to: false)

Returns:

  • (String)

    complete repo config text



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/chef/provider/apt_repository.rb', line 323

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
  optstr = unless options.empty?
             "[" + options.join(" ") + "]"
           end
  info = [ optstr, uri, distribution, components ].compact.join(" ")
  repo =  "deb      #{info}\n"
  repo << "deb-src  #{info}\n" if add_src
  repo
end

#cleanup_legacy_file!void

This method returns an undefined value.

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.



345
346
347
348
349
350
351
352
353
354
355
# File 'lib/chef/provider/apt_repository.rb', line 345

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
      declare_resource(:file, legacy_path) do
        action :delete
        # Not triggering an update since it isn't super likely to be needed.
      end
    end
  end
end

#cookbook_nameString

return the specified cookbook name or the cookbook containing the resource.

Returns:

  • (String)

    name of the cookbook



152
153
154
# File 'lib/chef/provider/apt_repository.rb', line 152

def cookbook_name
  new_resource.cookbook || new_resource.cookbook_name
end

#extract_fingerprints_from_cmd(*cmd) ⇒ Array

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

Parameters:

  • cmd (Array<String>)

    the command to run

Returns:

  • (Array)

    an array of fingerprints



119
120
121
122
123
124
125
126
# File 'lib/chef/provider/apt_repository.rb', line 119

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

#has_cookbook_file?(fn) ⇒ Boolean

determine if a cookbook file is available in the run

Parameters:

  • fn (String)

    the path to the cookbook file

Returns:

  • (Boolean)

    cookbook file exists or doesn’t



160
161
162
# File 'lib/chef/provider/apt_repository.rb', line 160

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) ⇒ void

This method returns an undefined value.

Parameters:

  • key (String)
  • keyserver (String) (defaults to: new_resource.keyserver)

Raises:

  • (RuntimeError)

    Invalid key which can’t verify the apt repository



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/chef/provider/apt_repository.rb', line 250

def install_key_from_keyserver(key, keyserver = new_resource.keyserver)
  declare_resource(: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) ⇒ void

This method returns an undefined value.

Fetch the key using either cookbook_file or remote_file, validate it, and install it with apt-key add

Parameters:

  • key (String)

    the key to install

Raises:

  • (RuntimeError)

    Invalid key which can’t verify the apt repository



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/chef/provider/apt_repository.rb', line 201

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 = Dir.mktmpdir(".gpg")
  at_exit { 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

  declare_resource(: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) ⇒ void

This method returns an undefined value.

Parameters:

Raises:

  • (RuntimeError)

    Could not access the Launchpad PPA API



273
274
275
276
277
278
279
# File 'lib/chef/provider/apt_repository.rb', line 273

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

#is_key_id?(id) ⇒ Boolean

is the provided ID a key ID from a keyserver. Looks at length and HEX only values

Parameters:

  • id (String)

    the key value passed by the user that may be an ID

Returns:

  • (Boolean)


108
109
110
111
# File 'lib/chef/provider/apt_repository.rb', line 108

def is_key_id?(id)
  id = id[2..-1] if id.start_with?("0x")
  id =~ /^\h+$/ && [8, 16, 40].include?(id.length)
end

#is_ppa_url?(url) ⇒ Boolean

determine if the repository URL is a PPA

Parameters:

  • url (String)

    the url of the repository

Returns:

  • (Boolean)

    is the repo URL a PPA



285
286
287
# File 'lib/chef/provider/apt_repository.rb', line 285

def is_ppa_url?(url)
  url.start_with?("ppa:")
end

#key_is_valid?(key) ⇒ Boolean

validate the key against the apt keystore to see if that version is expired

Parameters:

Returns:

  • (Boolean)

    is the key valid or not



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/chef/provider/apt_repository.rb', line 132

def key_is_valid?(key)
  valid = true

  so = shell_out("apt-key", "list")
  so.stdout.split(/\n/).map do |t|
    if t =~ %r{^\/#{key}.*\[expired: .*\]$}
      logger.debug "Found expired key: #{t}"
      valid = false
      break
    end
  end

  logger.debug "key #{key} #{valid ? "is valid" : "is not valid"}"
  valid
end

#key_type(uri) ⇒ Symbol

Given the provided key URI determine what kind of chef resource we need to fetch the key

Parameters:

  • uri (String)

    the uri of the gpg key (local path or http URL)

Returns:

  • (Symbol)

    :remote_file or :cookbook_file

Raises:



184
185
186
187
188
189
190
191
192
# File 'lib/chef/provider/apt_repository.rb', line 184

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) ⇒ String

build the apt-key command to install the keyserver

Parameters:

  • key (String)

    the key to install

  • keyserver (String)

    the key server to use

Returns:

  • (String)

    the full apt-key command to run



230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/chef/provider/apt_repository.rb', line 230

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

#load_current_resourceObject



35
36
# File 'lib/chef/provider/apt_repository.rb', line 35

def load_current_resource
end

#make_ppa_url(ppa) ⇒ String

given a PPA return a PPA URL in ppa.launchpad.net format

Parameters:

  • ppa (String)

    the ppa URL

Returns:



307
308
309
310
311
312
313
# File 'lib/chef/provider/apt_repository.rb', line 307

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) ⇒ Boolean

determine if there are any new keys by comparing the fingerprints of installed keys to those of the passed file

Parameters:

  • file (String)

    the keyfile of the new repository

Returns:

  • (Boolean)

    true: no new keys in the file. false: there are new keys



169
170
171
172
173
174
175
# File 'lib/chef/provider/apt_repository.rb', line 169

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_fingerprints_from_cmd(*LIST_APT_KEY_FINGERPRINTS)
  proposed_keys = extract_fingerprints_from_cmd("gpg", "--with-fingerprint", "--with-colons", file)
  (installed_keys & proposed_keys).sort == proposed_keys.sort
end

#repo_componentsString

determine the repository’s components:

- "components" property if defined
- "main" if "components" not defined and the repo is a PPA URL
- otherwise nothing

Returns:

  • (String)

    the repository component



295
296
297
298
299
300
301
# File 'lib/chef/provider/apt_repository.rb', line 295

def repo_components
  if is_ppa_url?(new_resource.uri) && new_resource.components.empty?
    "main"
  else
    new_resource.components
  end
end