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

apply_default_env, maybe_add_timeout, #shell_out, #shell_out!

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::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::Powershell

#ps_credential

Methods included from DSL::RegistryHelper

#registry_data_exists?, #registry_get_subkeys, #registry_get_values, #registry_has_subkeys?, #registry_key_exists?, #registry_value_exists?

Methods included from DSL::DataQuery

#data_bag, #data_bag_item, #search, #tagged?

Methods included from EncryptedDataBagItem::CheckEncrypted

#encrypted?

Methods included from DSL::PlatformIntrospection

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

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

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



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

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.



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

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



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

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



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

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



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

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



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

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



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

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



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

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)


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

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



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

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



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

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:



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

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



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

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
# 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:



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

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



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

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



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

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