File: base.rb

package info (click to toggle)
ruby-mixlib-install 3.12.16-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 193,056 kB
  • sloc: ruby: 3,843; sh: 664; makefile: 4
file content (249 lines) | stat: -rw-r--r-- 9,706 bytes parent folder | download
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
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
242
243
244
245
246
247
248
249
#
# Author:: Patrick Wright (<patrick@chef.io>)
# Copyright:: Copyright (c) 2016-2018 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_relative "../util"

module Mixlib
  class Install
    class Backend
      class ArtifactsNotFound < StandardError; end

      class Base
        attr_reader :options

        def initialize(options)
          @options = options
        end

        #
        # Returns the list of artifacts from the configured backend based on the
        # configured product_name, product_version and channel.
        #
        # @abstract Subclasses should define this method.
        #
        # @return Array<ArtifactInfo>
        #   List of ArtifactInfo objects for the available artifacts.
        def available_artifacts
          raise "Must implement available_artifacts method that returns Array<ArtifactInfo>"
        end

        #
        # Returns the list of available versions for a given product_name
        # and channel.
        #
        # @abstract Subclasses should define this method.
        #           Currently this method is only available in the Artifactory
        #           subclass.
        #
        # @return Array<String>
        #   List of available versions as strings.
        def available_versions
          raise "available_versions API is only available for Artifactory backend."
        end

        #
        # See #filter_artifacts
        def info
          filter_artifacts(available_artifacts)
        end

        #
        # Returns true if platform filters are available, false otherwise.
        #
        # Note that we assume #set_platform_info method is used on the Options
        # class to set the platform options.
        #
        # @return TrueClass, FalseClass
        def platform_filters_available?
          !options.platform.nil?
        end

        #
        # Filters and returns the available artifacts based on the configured
        # platform filtering options.
        #
        # @return ArtifactInfo, Array<ArtifactInfo>, ArtifactsNotFound
        #   If the result is a single artifact, this returns ArtifactInfo.
        #   If the result is a list of artifacts, this returns Array<ArtifactInfo>.
        #   If no suitable artifact is found, this returns ArtifactsNotFound exception.
        def filter_artifacts(artifacts)
          return artifacts unless platform_filters_available?

          # First filter the artifacts based on the platform and architecture
          artifacts.select! do |a|
            a.platform == options.platform && a.architecture == options.architecture
          end

          # Now we are going to filter based on platform_version.
          # We will return the artifact with an exact match if available.
          # Otherwise we will search for a compatible artifact and return it
          # if the compat options is set.
          closest_compatible_artifact = nil

          artifacts.each do |a|
            return a if a.platform_version == options.platform_version

            # Calculate the closest compatible version.
            # For an artifact to be compatible it needs to be smaller than the
            # platform_version specified in options.
            # To find the closest compatible one we keep a max of the compatible
            # artifacts.

            # Remove "r2" from artifacts produced for windows since platform
            # versions with that ending break `to_f` comparisons.
            current_platform_version = a.platform_version.chomp("r2").to_f

            if closest_compatible_artifact.nil? ||
                (current_platform_version > closest_compatible_artifact.platform_version.to_f &&
                  current_platform_version < options.platform_version.to_f )
              closest_compatible_artifact = a
            end
          end

          # If the compat flag is set and if we have found a compatible artifact
          # we are going to use it.
          if options.platform_version_compatibility_mode && closest_compatible_artifact
            return closest_compatible_artifact
          end

          # Return an exception if we get to the end of the method
          raise ArtifactsNotFound, <<-EOF
No artifacts found matching criteria.
  product name: #{options.product_name}
  channel: #{options.channel}
  version: #{options.product_version}
  platform: #{options.platform}
  platform version: #{options.original_platform_version}
  architecture: #{options.architecture}
  compatibility mode: #{options.platform_version_compatibility_mode}
EOF
        end

        # On windows, if we do not have a native 64-bit package available
        # in the discovered artifacts, we will make 32-bit artifacts available
        # for 64-bit architecture.
        #
        # We also create new artifacts for windows 7, 8, 8.1 and 10
        #
        def windows_artifact_fixup!(artifacts)
          new_artifacts = [ ]
          native_artifacts = [ ]

          # We only return appx packages when a nano platform version is requested.
          if options.class::SUPPORTED_WINDOWS_NANO_VERSIONS.include?(options.original_platform_version)
            return artifacts.find_all { |a| a.appx_artifact? }

          # Otherwise, we only return msi artifacts and remove all appx packages
          else
            artifacts.delete_if { |a| a.appx_artifact? }
          end

          artifacts.each do |r|
            next if r.platform != "windows"

            # Store all native 64-bit artifacts and clone 32-bit artifacts to
            # be used as 64-bit.
            case r.architecture
            when "i386"
              new_artifacts << r.clone_with(architecture: "x86_64")
            when "x86_64"
              native_artifacts << r.clone
            else
              puts "Unknown architecture '#{r.architecture}' for windows."
            end
          end

          # Grab windows artifact for each architecture so we don't have to manipulate
          # the architecture extension in the filename of the url which changes based on product.
          # Don't want to deal with that!
          artifact_64 = artifacts.find { |a| a.platform == "windows" && a.architecture == "x86_64" }
          artifact_32 = artifacts.find { |a| a.platform == "windows" && a.architecture == "i386" }

          # Clone an existing 64-bit artifact
          new_artifacts.concat(clone_windows_desktop_artifacts(artifact_64)) if artifact_64
          # Clone an existing 32-bit artifact
          new_artifacts.concat(clone_windows_desktop_artifacts(artifact_32)) if artifact_32
          # Clone the 32 bit artifact when 64 bit doesn't exist
          new_artifacts.concat(clone_windows_desktop_artifacts(artifact_32, architecture: "x86_64")) if artifact_32 && !artifact_64

          # Now discard the cloned artifacts if we find an equivalent native
          # artifact
          native_artifacts.each do |r|
            new_artifacts.delete_if do |x|
              x.platform_version == r.platform_version
            end
          end

          # add the remaining cloned artifacts to the original set
          artifacts += new_artifacts
        end

        #
        # Normalizes platform and platform_version information that we receive.
        # There are a few entries that we historically published
        # that we need to normalize. They are:
        #   * solaris -> solaris2 & 10 -> 5.10 for solaris.
        #
        # @param [String] platform
        # @param [String] platform_version
        #
        # @return Array<String> [platform, platform_version]
        def normalize_platform(platform, platform_version)
          if platform == "solaris"
            platform = "solaris2"

            # Here platform_version is set to either 10 or 11 and we would like
            # to normalize that to 5.10 and 5.11.
            platform_version = "5.#{platform_version}"
          end

          [platform, platform_version]
        end

        private

        #
        # Custom map Chef's supported windows desktop versions to the server versions we currently build
        # See https://docs.chef.io/platforms.html
        #
        def map_custom_windows_desktop_versions(desktop_version)
          server_version = Util.map_windows_version(desktop_version)

          # Windows desktop 10 officially maps to server 2016.
          # However, we don't test on server 2016 at this time, so we default to 2012r2
          server_version = "2012r2" if server_version == "2016"

          server_version
        end

        #
        # Clone all supported Windows desktop artifacts from a base artifact
        # options hash allows overriding any valid attribute
        #
        def clone_windows_desktop_artifacts(base_artifact, options = {})
          @options.class::SUPPORTED_WINDOWS_DESKTOP_VERSIONS.collect do |dv|
            options[:platform_version] = dv
            options[:url] = base_artifact.url.gsub("\/#{base_artifact.platform_version}\/", "\/#{map_custom_windows_desktop_versions(dv)}\/")

            base_artifact.clone_with(options)
          end
        end
      end
    end
  end
end