Class: Chef::Provider::Package::Yum

Inherits:
Chef::Provider::Package show all
Includes:
Mixin::GetSourceFromPackage
Defined in:
lib/chef/provider/package/yum.rb,
lib/chef/provider/package/yum/rpm_utils.rb,
lib/chef/provider/package/yum/yum_cache.rb

Defined Under Namespace

Classes: RPMDb, RPMDbPackage, RPMDependency, RPMPackage, RPMProvide, RPMRequire, RPMUtils, RPMVersion, YumCache

Constant Summary

Constants included from Mixin::ShellOut

Mixin::ShellOut::DEPRECATED_OPTIONS

Instance Attribute Summary

Attributes inherited from Chef::Provider::Package

#candidate_version

Attributes inherited from Chef::Provider

#action, #cookbook_name, #current_resource, #new_resource, #recipe_name, #run_context

Instance Method Summary collapse

Methods inherited from Chef::Provider::Package

#action_install, #action_purge, #action_reconfig, #action_remove, #as_array, #check_resource_semantics!, #define_resource_requirements, #expand_options, #get_preseed_file, #have_any_matching_version?, #multipackage_api_adapter, #preseed_package, #preseed_resource, #reconfig_package, #removing_package?, #target_version_already_installed?, #version_requirement_satisfied?, #whyrun_supported?

Methods included from Mixin::SubclassDirective

#subclass_directive

Methods included from Mixin::ShellOut

#run_command_compatible_options, #shell_out, #shell_out!, #shell_out_with_systems_locale, #shell_out_with_systems_locale!

Methods included from Mixin::Command

#chdir_or_tmpdir, #handle_command_failures, #output_of_command, #run_command, #run_command_and_return_stdout_stderr, #run_command_with_systems_locale

Methods included from Mixin::Command::Windows

#popen4

Methods included from Mixin::Command::Unix

#popen4

Methods inherited from Chef::Provider

#action_nothing, #check_resource_semantics!, #cleanup_after_converge, #converge_by, #converge_if_changed, #define_resource_requirements, #events, include_resource_dsl, include_resource_dsl_module, #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 DeprecatedLWRPClass

#const_missing, #deprecated_constants, #register_deprecated_lwrp_class

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 DSL::PlatformIntrospection

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

Constructor Details

#initialize(new_resource, run_context) ⇒ Yum

Returns a new instance of Yum.



36
37
38
39
40
41
# File 'lib/chef/provider/package/yum.rb', line 36

def initialize(new_resource, run_context)
  super

  @yum = YumCache.instance
  @yum.yum_binary = yum_binary
end

Instance Method Details

#action_upgradeObject

Keep upgrades from trying to install an older candidate version. Can happen when a new version is installed then removed from a repository, now the older available version shows up as a viable install candidate.

Can be done in upgrade_package but an upgraded from->to log message slips out

Hacky - better overall solution? Custom compare in Package provider?



330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/chef/provider/package/yum.rb', line 330

def action_upgrade
  # Could be uninstalled or have no candidate
  if @current_resource.version.nil? || !candidate_version_array.any?
    super
  elsif candidate_version_array.zip(current_version_array).any? do |c, i|
          RPMVersion.parse(c) > RPMVersion.parse(i)
        end
    super
  else
    Chef::Log.debug("#{@new_resource} is at the latest version - nothing to do")
  end
end

#archObject



65
66
67
68
69
70
71
# File 'lib/chef/provider/package/yum.rb', line 65

def arch
  if @new_resource.respond_to?("arch")
    @new_resource.arch
  else
    nil
  end
end

#arch_for_name(n) ⇒ Object

Extra attributes



54
55
56
57
58
59
60
61
62
63
# File 'lib/chef/provider/package/yum.rb', line 54

def arch_for_name(n)
  if @new_resource.respond_to?("arch")
    @new_resource.arch
  elsif @arch
    idx = package_name_array.index(n)
    as_array(@arch)[idx]
  else
    nil
  end
end

#flush_cacheObject



79
80
81
82
83
84
85
# File 'lib/chef/provider/package/yum.rb', line 79

def flush_cache
  if @new_resource.respond_to?("flush_cache")
    @new_resource.flush_cache
  else
    { :before => false, :after => false }
  end
end

#install_package(name, version) ⇒ Object



309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/chef/provider/package/yum.rb', line 309

def install_package(name, version)
  if @new_resource.source
    yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}")
  else
    install_remote_package(name, version)
  end

  if flush_cache[:after]
    @yum.reload
  else
    @yum.reload_installed
  end
end

#install_remote_package(name, version) ⇒ Object



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/chef/provider/package/yum.rb', line 243

def install_remote_package(name, version)
  # Work around yum not exiting with an error if a package doesn't exist
  # for CHEF-2062
  all_avail = as_array(name).zip(as_array(version)).any? do |n, v|
    @yum.version_available?(n, v, arch_for_name(n))
  end
  method = log_method = nil
  methods = []
  if all_avail
    # More Yum fun:
    #
    # yum install of an old name+version will exit(1)
    # yum install of an old name+version+arch will exit(0) for some reason
    #
    # Some packages can be installed multiple times like the kernel
    as_array(name).zip(as_array(version)).each do |n, v|
      method = "install"
      log_method = "installing"
      idx = package_name_array.index(n)
      unless @yum.allow_multi_install.include?(n)
        if RPMVersion.parse(current_version_array[idx]) > RPMVersion.parse(v)
          # We allow downgrading only in the evenit of single-package
          # rules where the user explicitly allowed it
          if allow_downgrade
            method = "downgrade"
            log_method = "downgrading"
          else
            # we bail like yum when the package is older
            raise Chef::Exceptions::Package, "Installed package #{n}-#{current_version_array[idx]} is newer " +
              "than candidate package #{n}-#{v}"
          end
        end
      end
      # methods don't count for packages we won't be touching
      next if RPMVersion.parse(current_version_array[idx]) == RPMVersion.parse(v)
      methods << method
    end

    # We could split this up into two commands if we wanted to, but
    # for now, just don't support this.
    if methods.uniq.length > 1
      raise Chef::Exceptions::Package, "Multipackage rule #{name} has a mix of upgrade and downgrade packages. Cannot proceed."
    end

    repos = []
    pkg_string_bits = []
    as_array(name).zip(as_array(version)).each do |n, v|
      idx = package_name_array.index(n)
      a = arch_for_name(n)
      s = ""
      unless v == current_version_array[idx]
        s = "#{n}-#{v}#{yum_arch(a)}"
        repo = @yum.package_repository(n, v, a)
        repos << "#{s} from #{repo} repository"
        pkg_string_bits << s
      end
    end
    pkg_string = pkg_string_bits.join(" ")
    Chef::Log.info("#{@new_resource} #{log_method} #{repos.join(' ')}")
    yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{pkg_string}")
  else
    raise Chef::Exceptions::Package, "Version #{version} of #{name} not found. Did you specify both version " +
      "and release? (version-release, e.g. 1.84-10.fc6)"
  end
end

#load_current_resourceObject

Standard Provider methods for Parent



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/chef/provider/package/yum.rb', line 129

def load_current_resource
  if flush_cache[:before]
    @yum.reload
  end

  if @new_resource.options
    repo_control = []
    @new_resource.options.split.each do |opt|
      if opt =~ %r{--(enable|disable)repo=.+}
        repo_control << opt
      end
    end

    if repo_control.size > 0
      @yum.enable_extra_repo_control(repo_control.join(" "))
    else
      @yum.disable_extra_repo_control
    end
  else
    @yum.disable_extra_repo_control
  end

  # At this point package_name could be:
  #
  # 1) a package name, eg: "foo"
  # 2) a package name.arch, eg: "foo.i386"
  # 3) or a dependency, eg: "foo >= 1.1"

  # Check if we have name or name+arch which has a priority over a dependency
  package_name_array.each_with_index do |n, index|
    unless @yum.package_available?(n)
      # If they aren't in the installed packages they could be a dependency
      dep = parse_dependency(n, new_version_array[index])
      if dep
        if @new_resource.package_name.is_a?(Array)
          @new_resource.package_name(package_name_array - [n] + [dep.first])
          @new_resource.version(new_version_array - [new_version_array[index]] + [dep.last]) if dep.last
        else
          @new_resource.package_name(dep.first)
          @new_resource.version(dep.last) if dep.last
        end
      end
    end
  end

  @current_resource = Chef::Resource::YumPackage.new(@new_resource.name)
  @current_resource.package_name(@new_resource.package_name)

  installed_version = []
  @candidate_version = []
  @arch = []
  if @new_resource.source
    unless ::File.exists?(@new_resource.source)
      raise Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
    end

    Chef::Log.debug("#{@new_resource} checking rpm status")
    shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}", :timeout => Chef::Config[:yum_timeout]).stdout.each_line do |line|
      case line
      when /([\w\d_.-]+)\s([\w\d_.-]+)/
        @current_resource.package_name($1)
        @new_resource.version($2)
      end
    end
    @candidate_version << @new_resource.version
    installed_version << @yum.installed_version(@current_resource.package_name, arch)
  else

    package_name_array.each_with_index do |pkg, idx|
      # Don't overwrite an existing arch
      if arch
        name, parch = pkg, arch
      else
        name, parch = parse_arch(pkg)
        # if we parsed an arch from the name, update the name
        # to be just the package name.
        if parch
          if @new_resource.package_name.is_a?(Array)
            @new_resource.package_name[idx] = name
          else
            @new_resource.package_name(name)
            # only set the arch if it's a single package
            set_arch(parch)
          end
        end
      end

      if @new_resource.version
        new_resource =
          "#{@new_resource.package_name}-#{@new_resource.version}#{yum_arch(parch)}"
      else
        new_resource = "#{@new_resource.package_name}#{yum_arch(parch)}"
      end
      Chef::Log.debug("#{@new_resource} checking yum info for #{new_resource}")
      installed_version << @yum.installed_version(name, parch)
      @candidate_version << @yum.candidate_version(name, parch)
      @arch << parch
    end
  end

  if installed_version.size == 1
    @current_resource.version(installed_version[0])
    @candidate_version = @candidate_version[0]
    @arch = @arch[0]
  else
    @current_resource.version(installed_version)
  end

  Chef::Log.debug("#{@new_resource} installed version: #{installed_version || "(none)"} candidate version: " +
                  "#{@candidate_version || "(none)"}")

  @current_resource
end

#purge_package(name, version) ⇒ Object



368
369
370
# File 'lib/chef/provider/package/yum.rb', line 368

def purge_package(name, version)
  remove_package(name, version)
end

#remove_package(name, version) ⇒ Object



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
# File 'lib/chef/provider/package/yum.rb', line 347

def remove_package(name, version)
  if version
    remove_str = as_array(name).zip(as_array(version)).map do |n, v|
      a = arch_for_name(n)
      "#{[n, v].join('-')}#{yum_arch(a)}"
    end.join(" ")
  else
    remove_str = as_array(name).map do |n|
      a = arch_for_name(n)
      "#{n}#{yum_arch(a)}"
    end.join(" ")
  end
  yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")

  if flush_cache[:after]
    @yum.reload
  else
    @yum.reload_installed
  end
end

#set_arch(arch) ⇒ Object



73
74
75
76
77
# File 'lib/chef/provider/package/yum.rb', line 73

def set_arch(arch)
  if @new_resource.respond_to?("arch")
    @new_resource.arch(arch)
  end
end

#upgrade_package(name, version) ⇒ Object



343
344
345
# File 'lib/chef/provider/package/yum.rb', line 343

def upgrade_package(name, version)
  install_package(name, version)
end

#yum_arch(arch) ⇒ Object

Helpers



90
91
92
# File 'lib/chef/provider/package/yum.rb', line 90

def yum_arch(arch)
  arch ? ".#{arch}" : nil
end

#yum_binaryObject



43
44
45
46
47
48
49
# File 'lib/chef/provider/package/yum.rb', line 43

def yum_binary
  @yum_binary ||=
    begin
      yum_binary = new_resource.yum_binary if new_resource.is_a?(Chef::Resource::YumPackage)
      yum_binary ||= ::File.exist?("/usr/bin/yum-deprecated") ? "yum-deprecated" : "yum"
    end
end

#yum_command(command) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/chef/provider/package/yum.rb', line 94

def yum_command(command)
  command = "#{yum_binary} #{command}"
  Chef::Log.debug("#{@new_resource}: yum command: \"#{command}\"")
  status = shell_out_with_timeout(command, { :timeout => Chef::Config[:yum_timeout] })

  # This is fun: rpm can encounter errors in the %post/%postun scripts which aren't
  # considered fatal - meaning the rpm is still successfully installed. These issue
  # cause yum to emit a non fatal warning but still exit(1). As there's currently no
  # way to suppress this behavior and an exit(1) will break a Chef run we make an
  # effort to trap these and re-run the same install command - it will either fail a
  # second time or succeed.
  #
  # A cleaner solution would have to be done in python and better hook into
  # yum/rpm to handle exceptions as we see fit.
  if status.exitstatus == 1
    status.stdout.each_line do |l|
      # rpm-4.4.2.3 lib/psm.c line 2182
      if l =~ %r{^error: %(post|postun)\(.*\) scriptlet failed, exit status \d+$}
        Chef::Log.warn("#{@new_resource} caught non-fatal scriptlet issue: \"#{l}\". Can't trust yum exit status " +
                       "so running install again to verify.")
        status = shell_out_with_timeout(command, { :timeout => Chef::Config[:yum_timeout] })
        break
      end
    end
  end

  if status.exitstatus > 0
    command_output = "STDOUT: #{status.stdout}\nSTDERR: #{status.stderr}"
    raise Chef::Exceptions::Exec, "#{command} returned #{status.exitstatus}:\n#{command_output}"
  end
end