summaryrefslogtreecommitdiff
path: root/lib/chef/provider/package/portage.rb
blob: e43e71f2104887c5a6aa17ad3b8edd40c184e45c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#
# Author:: Ezra Zygmuntowicz (<ezra@engineyard.com>)
# Copyright:: Copyright 2008-2016, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require "chef/provider/package"
require "chef/resource/portage_package"
require "chef/util/path_helper"

class Chef
  class Provider
    class Package
      class Portage < Chef::Provider::Package

        provides :package, platform: "gentoo"
        provides :portage_package

        PACKAGE_NAME_PATTERN = %r{(?:([^/]+)/)?([^/]+)}

        def load_current_resource
          @current_resource = Chef::Resource::Package.new(new_resource.name)
          current_resource.package_name(new_resource.package_name)

          category, pkg = /^#{PACKAGE_NAME_PATTERN}$/.match(new_resource.package_name)[1, 2]

          globsafe_category = category ? Chef::Util::PathHelper.escape_glob_dir(category) : nil
          globsafe_pkg = Chef::Util::PathHelper.escape_glob_dir(pkg)
          possibilities = Dir["/var/db/pkg/#{globsafe_category || '*'}/#{globsafe_pkg}-*"].map { |d| d.sub(%r{/var/db/pkg/}, "") }
          versions = possibilities.map do |entry|
            if entry =~ %r{[^/]+/#{Regexp.escape(pkg)}\-(\d[\.\d]*[a-z]?((_(alpha|beta|pre|rc|p)\d*)*)?(-r\d+)?)}
              [$&, $1]
            end
          end.compact

          if versions.size > 1
            atoms = versions.map(&:first).sort
            categories = atoms.map { |v| v.split("/")[0] }.uniq
            if !category && categories.size > 1
              raise Chef::Exceptions::Package, "Multiple packages found for #{new_resource.package_name}: #{atoms.join(' ')}. Specify a category."
            end
          elsif versions.size == 1
            current_resource.version(versions.first.last)
            Chef::Log.debug("#{new_resource} current version #{$1}")
          end

          current_resource
        end

        def parse_emerge(package, txt)
          availables = {}
          found_package_name = nil

          txt.each_line do |line|
            if line =~ /\*\s+#{PACKAGE_NAME_PATTERN}/
              found_package_name = $&.delete("*").strip
              if package =~ /\// # the category is specified
                if found_package_name == package
                  availables[found_package_name] = nil
                end
              else # the category is not specified
                if found_package_name.split("/").last == package
                  availables[found_package_name] = nil
                end
              end
            end

            if line =~ /Latest version available: (.*)/ && availables.key?(found_package_name)
              availables[found_package_name] = $1.strip
            end
          end

          if availables.size > 1
            # shouldn't happen if a category is specified so just use `package`
            raise Chef::Exceptions::Package, "Multiple emerge results found for #{package}: #{availables.keys.join(' ')}. Specify a category."
          end

          availables.values.first
        end

        def candidate_version
          return @candidate_version if @candidate_version

          status = shell_out_compact("emerge", "--color", "n", "--nospinner", "--search", new_resource.package_name.split("/").last)
          available, installed = parse_emerge(new_resource.package_name, status.stdout)
          @candidate_version = available

          unless status.exitstatus == 0
            raise Chef::Exceptions::Package, "emerge --search failed - #{status.inspect}!"
          end

          @candidate_version
        end

        def install_package(name, version)
          pkg = "=#{name}-#{version}"

          if version =~ /^\~(.+)/
            # If we start with a tilde
            pkg = "~#{name}-#{$1}"
          end

          shell_out_compact!( "emerge", "-g", "--color", "n", "--nospinner", "--quiet", options, pkg )
        end

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

        def remove_package(name, version)
          pkg = if version
                  "=#{new_resource.package_name}-#{version}"
                else
                  new_resource.package_name.to_s
                end

          shell_out_compact!( "emerge", "--unmerge", "--color", "n", "--nospinner", "--quiet", options, pkg )
        end

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

      end
    end
  end
end