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 =
"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, #clean_array, #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!, #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?, #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



312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/chef/provider/apt_repository.rb', line 312

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

#cookbook_nameString

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

Returns:

  • (String)

    name of the cookbook



146
147
148
# File 'lib/chef/provider/apt_repository.rb', line 146

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

    the command to run

Returns:

  • (Array)

    an array of fingerprints



113
114
115
116
117
118
119
120
# File 'lib/chef/provider/apt_repository.rb', line 113

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



154
155
156
# File 'lib/chef/provider/apt_repository.rb', line 154

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



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/chef/provider/apt_repository.rb', line 240

def install_key_from_keyserver(key, keyserver = new_resource.keyserver)
  declare_resource(:execute, "install-key #{key}") do
    command keyserver_install_cmd(key, keyserver)
    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



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/chef/provider/apt_repository.rb', line 195

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)

  declare_resource(key_type(key), cached_keyfile) do
    source key
    mode "0644"
    sensitive new_resource.sensitive
    action :create
    verify "gpg %{path}"
  end

  declare_resource(:execute, "apt-key add #{cached_keyfile}") do
    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



262
263
264
265
266
267
268
# File 'lib/chef/provider/apt_repository.rb', line 262

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::HTTPServerException => 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)


102
103
104
105
# File 'lib/chef/provider/apt_repository.rb', line 102

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



274
275
276
# File 'lib/chef/provider/apt_repository.rb', line 274

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



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/chef/provider/apt_repository.rb', line 126

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:



178
179
180
181
182
183
184
185
186
# File 'lib/chef/provider/apt_repository.rb', line 178

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



220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/chef/provider/apt_repository.rb', line 220

def keyserver_install_cmd(key, keyserver)
  cmd = "apt-key adv --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



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

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:



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

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



163
164
165
166
167
168
169
# File 'lib/chef/provider/apt_repository.rb', line 163

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



284
285
286
287
288
289
290
# File 'lib/chef/provider/apt_repository.rb', line 284

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