mixlib-install 1.2.4 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +41 -0
- data/CHANGELOG.md +6 -2
- data/README.md +4 -15
- data/Rakefile +2 -12
- data/acceptance/.gitignore +7 -1
- data/acceptance/Gemfile +0 -8
- data/acceptance/README.md +6 -0
- data/acceptance/bourne/.acceptance/acceptance-cookbook/.gitignore +8 -0
- data/acceptance/{unstable → bourne}/.acceptance/acceptance-cookbook/metadata.rb +1 -0
- data/acceptance/bourne/.acceptance/acceptance-cookbook/recipes/destroy.rb +3 -0
- data/acceptance/bourne/.acceptance/acceptance-cookbook/recipes/provision.rb +11 -0
- data/acceptance/bourne/.acceptance/acceptance-cookbook/recipes/verify.rb +11 -0
- data/acceptance/bourne/inspec/verify.rb +3 -0
- data/acceptance/bourne/terraform/application.tf +78 -0
- data/acceptance/bourne/terraform/aws.tf +8 -0
- data/acceptance/bourne/terraform/variables.tf +27 -0
- data/acceptance/{current → powershell}/.acceptance/acceptance-cookbook/.gitignore +1 -1
- data/acceptance/{current → powershell}/.acceptance/acceptance-cookbook/metadata.rb +1 -0
- data/acceptance/powershell/.acceptance/acceptance-cookbook/recipes/destroy.rb +3 -0
- data/acceptance/powershell/.acceptance/acceptance-cookbook/recipes/provision.rb +12 -0
- data/acceptance/powershell/.acceptance/acceptance-cookbook/recipes/verify.rb +12 -0
- data/acceptance/powershell/inspec/verify.rb +3 -0
- data/acceptance/powershell/terraform/application.tf +89 -0
- data/acceptance/powershell/terraform/aws.tf +8 -0
- data/acceptance/powershell/terraform/variables.tf +16 -0
- data/ci/before-script.sh +31 -0
- data/ci/es-infrastructure.pem.enc +0 -0
- data/lib/mixlib/install.rb +0 -10
- data/lib/mixlib/install/backend.rb +2 -7
- data/lib/mixlib/install/backend/{artifactory.rb → package_router.rb} +52 -100
- data/lib/mixlib/install/generator/base.rb +1 -2
- data/lib/mixlib/install/generator/bourne.rb +1 -13
- data/lib/mixlib/install/generator/powershell.rb +2 -25
- data/lib/mixlib/install/generator/powershell/scripts/get_project_metadata.ps1.erb +1 -1
- data/lib/mixlib/install/options.rb +3 -17
- data/lib/mixlib/install/script_generator.rb +1 -0
- data/lib/mixlib/install/version.rb +1 -1
- data/mixlib-install.gemspec +4 -3
- metadata +51 -32
- data/acceptance/current/.acceptance/acceptance-cookbook/recipes/destroy.rb +0 -3
- data/acceptance/current/.acceptance/acceptance-cookbook/recipes/provision.rb +0 -3
- data/acceptance/current/.acceptance/acceptance-cookbook/recipes/verify.rb +0 -3
- data/acceptance/current/.kitchen.yml +0 -41
- data/acceptance/unstable/.acceptance/acceptance-cookbook/.gitignore +0 -2
- data/acceptance/unstable/.acceptance/acceptance-cookbook/recipes/destroy.rb +0 -3
- data/acceptance/unstable/.acceptance/acceptance-cookbook/recipes/provision.rb +0 -3
- data/acceptance/unstable/.acceptance/acceptance-cookbook/recipes/verify.rb +0 -3
- data/acceptance/unstable/.kitchen.yml +0 -41
- data/config.ru +0 -7
- data/lib/mixlib/install/backend/bintray.rb +0 -298
- data/lib/mixlib/install/backend/omnitruck.rb +0 -75
- data/lib/mixlib/install/generator/bourne/scripts/artifactory_urls.sh.erb +0 -28
- data/lib/mixlib/install/generator/powershell/scripts/get_project_metadata_for_artifactory.ps1.erb +0 -75
@@ -0,0 +1,8 @@
|
|
1
|
+
# Restrict operation of terraform to chef-es profile so that
|
2
|
+
# we do not create resources in other aws profiles.
|
3
|
+
# We assume user has configured standard aws credentials
|
4
|
+
# under ~/.aws/credentials or with $AWS_SHARED_CREDENTIALS_FILE
|
5
|
+
provider "aws" {
|
6
|
+
region = "${var.aws_region}"
|
7
|
+
profile = "chef-aws"
|
8
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Region to create infrastructure in
|
2
|
+
variable "aws_region" {
|
3
|
+
type = "string"
|
4
|
+
default = "us-west-2"
|
5
|
+
}
|
6
|
+
|
7
|
+
variable "aws_instance_type" {
|
8
|
+
type = "string"
|
9
|
+
default = "t2.micro"
|
10
|
+
}
|
11
|
+
|
12
|
+
variable "admin_password" {
|
13
|
+
description = "Set Windows Administrator password"
|
14
|
+
type = "string"
|
15
|
+
default = "Pas5w0rD"
|
16
|
+
}
|
data/ci/before-script.sh
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
set -evx
|
4
|
+
|
5
|
+
# undo travis gem and bundler config
|
6
|
+
for ruby_env_var in _ORIGINAL_GEM_PATH \
|
7
|
+
BUNDLE_BIN_PATH \
|
8
|
+
BUNDLE_GEMFILE \
|
9
|
+
GEM_HOME \
|
10
|
+
GEM_PATH \
|
11
|
+
GEM_ROOT \
|
12
|
+
RUBYLIB \
|
13
|
+
RUBYOPT \
|
14
|
+
RUBY_ENGINE \
|
15
|
+
RUBY_ROOT \
|
16
|
+
RUBY_VERSION
|
17
|
+
|
18
|
+
do
|
19
|
+
unset $ruby_env_var
|
20
|
+
done
|
21
|
+
|
22
|
+
# download terraform
|
23
|
+
wget "https://releases.hashicorp.com/terraform/0.7.4/terraform_0.7.4_linux_amd64.zip"
|
24
|
+
|
25
|
+
# inflate archive
|
26
|
+
unzip terraform_0.7.4_linux_amd64.zip -d bin
|
27
|
+
|
28
|
+
# decrypt pem
|
29
|
+
openssl aes-256-cbc -K $encrypted_e2edbb28e76c_key -iv $encrypted_e2edbb28e76c_iv -in ci/es-infrastructure.pem.enc -out es-infrastructure.pem -d
|
30
|
+
mkdir -p ~/.ssh
|
31
|
+
mv es-infrastructure.pem ~/.ssh
|
Binary file
|
data/lib/mixlib/install.rb
CHANGED
@@ -17,8 +17,6 @@
|
|
17
17
|
# limitations under the License.
|
18
18
|
#
|
19
19
|
|
20
|
-
raise "mixlib-install v1 has been deprecated, please update to v2+. See https://discourse.chef.io/t/end-of-life-announcement-for-chef-software-inc-s-bintray-account/9807/1 for more information."
|
21
|
-
|
22
20
|
require "mixlib/versioning"
|
23
21
|
require "mixlib/shellout"
|
24
22
|
|
@@ -185,13 +183,5 @@ module Mixlib
|
|
185
183
|
def self.install_ps1(context = {})
|
186
184
|
Mixlib::Install::Generator::PowerShell.install_ps1(context)
|
187
185
|
end
|
188
|
-
|
189
|
-
#
|
190
|
-
# Returns if unified_backend feature flag for mixlib-install is enabled
|
191
|
-
#
|
192
|
-
# @return [Boolean] true if feature is enabled, false otherwise.
|
193
|
-
def self.unified_backend?
|
194
|
-
!ENV["MIXLIB_INSTALL_UNIFIED_BACKEND"].nil?
|
195
|
-
end
|
196
186
|
end
|
197
187
|
end
|
@@ -16,8 +16,7 @@
|
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
18
|
|
19
|
-
require "mixlib/install/backend/
|
20
|
-
require "mixlib/install/backend/bintray"
|
19
|
+
require "mixlib/install/backend/package_router"
|
21
20
|
|
22
21
|
module Mixlib
|
23
22
|
class Install
|
@@ -31,11 +30,7 @@ module Mixlib
|
|
31
30
|
end
|
32
31
|
|
33
32
|
def self.backend(options)
|
34
|
-
|
35
|
-
Backend::Artifactory.new(options)
|
36
|
-
else
|
37
|
-
Backend::Bintray.new(options)
|
38
|
-
end
|
33
|
+
Backend::PackageRouter.new(options)
|
39
34
|
end
|
40
35
|
end
|
41
36
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
#
|
2
2
|
# Author:: Patrick Wright (<patrick@chef.io>)
|
3
|
-
# Copyright:: Copyright (c) 2015 Chef, Inc.
|
3
|
+
# Copyright:: Copyright (c) 2015-2016 Chef, Inc.
|
4
4
|
# License:: Apache License, Version 2.0
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -16,27 +16,21 @@
|
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
18
|
|
19
|
-
require "net/http"
|
20
19
|
require "json"
|
21
20
|
require "mixlib/install/artifact_info"
|
22
|
-
require "artifactory"
|
23
21
|
require "mixlib/install/backend/base"
|
24
22
|
require "mixlib/install/product"
|
23
|
+
require "net/http"
|
25
24
|
|
26
25
|
module Mixlib
|
27
26
|
class Install
|
28
27
|
class Backend
|
29
|
-
class
|
30
|
-
class ConnectionError < StandardError; end
|
31
|
-
class AuthenticationError < StandardError; end
|
28
|
+
class PackageRouter < Base
|
32
29
|
class NoArtifactsError < StandardError; end
|
33
30
|
|
34
|
-
ENDPOINT = "
|
31
|
+
ENDPOINT = "https://packages.chef.io".freeze
|
35
32
|
|
36
|
-
|
37
|
-
# server which is only available in Chef's internal network.
|
38
|
-
ARTIFACTORY_USERNAME = "mixlib-install".freeze
|
39
|
-
ARTIFACTORY_PASSWORD = "%mKPtzbT1JH1wm333kjkkjs39oeuFLgZ8vNoOdLBPd)TZAJsURs9w0HloWR$l6h".freeze
|
33
|
+
COMPAT_DOWNLOAD_URL_ENDPOINT = "http://packages.chef.io".freeze
|
40
34
|
|
41
35
|
# Create filtered list of artifacts
|
42
36
|
#
|
@@ -44,9 +38,9 @@ module Mixlib
|
|
44
38
|
# channel, product name, and product version.
|
45
39
|
def available_artifacts
|
46
40
|
artifacts = if options.latest_version?
|
47
|
-
|
41
|
+
latest_version
|
48
42
|
else
|
49
|
-
|
43
|
+
artifacts_for_version(options.product_version)
|
50
44
|
end
|
51
45
|
|
52
46
|
windows_artifact_fixup!(artifacts)
|
@@ -57,14 +51,7 @@ module Mixlib
|
|
57
51
|
#
|
58
52
|
# @return [Array<String>] Array of available versions
|
59
53
|
def available_versions
|
60
|
-
|
61
|
-
items.find(
|
62
|
-
{"repo": "omnibus-#{options.channel}-local"},
|
63
|
-
{"@omnibus.project": "#{omnibus_project}"},
|
64
|
-
{"name": {"$nmatch": "*.metadata.json" }}
|
65
|
-
).include("@omnibus.version", "artifact.module.build")
|
66
|
-
QUERY
|
67
|
-
items = artifactory_query(query)
|
54
|
+
items = get("/api/v1/#{options.channel}/#{omnibus_project}/versions")["results"]
|
68
55
|
|
69
56
|
# Filter out the partial builds if we are in :unstable channel
|
70
57
|
# In other channels we do not need to do this since all builds are
|
@@ -88,15 +75,15 @@ QUERY
|
|
88
75
|
# Get artifacts for the latest version, channel and product_name
|
89
76
|
#
|
90
77
|
# @return [Array<ArtifactInfo>] Array of info about found artifacts
|
91
|
-
def
|
78
|
+
def latest_version
|
92
79
|
# Get the list of builds from the REST api.
|
93
80
|
# We do this because a user in the readers group does not have
|
94
81
|
# permissions to run aql against builds.
|
95
|
-
builds =
|
82
|
+
builds = get("/api/v1/build/#{omnibus_project}")
|
96
83
|
|
97
84
|
if builds.nil?
|
98
85
|
raise NoArtifactsError, <<-MSG
|
99
|
-
Can not find any builds for #{options.product_name} in #{
|
86
|
+
Can not find any builds for #{options.product_name} in #{endpoint}.
|
100
87
|
MSG
|
101
88
|
end
|
102
89
|
|
@@ -117,7 +104,7 @@ Can not find any builds for #{options.product_name} in #{::Artifactory.endpoint}
|
|
117
104
|
# are using artifactory only for :unstable channel
|
118
105
|
builds["buildsNumbers"].each do |build|
|
119
106
|
version = build["uri"].delete("/")
|
120
|
-
artifacts =
|
107
|
+
artifacts = artifacts_for_version(version)
|
121
108
|
|
122
109
|
return artifacts unless artifacts.empty?
|
123
110
|
end
|
@@ -130,21 +117,20 @@ Can not find any builds for #{options.product_name} in #{::Artifactory.endpoint}
|
|
130
117
|
# Get artifacts for a given version, channel and product_name
|
131
118
|
#
|
132
119
|
# @return [Array<ArtifactInfo>] Array of info about found artifacts
|
133
|
-
def
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
120
|
+
def artifacts_for_version(version)
|
121
|
+
begin
|
122
|
+
results = get("/api/v1/#{options.channel}/#{omnibus_project}/#{version}/artifacts")["results"]
|
123
|
+
rescue Net::HTTPServerException => e
|
124
|
+
if e.message =~ /404/
|
125
|
+
return []
|
126
|
+
else
|
127
|
+
raise e
|
128
|
+
end
|
129
|
+
end
|
143
130
|
|
144
131
|
# Merge artifactory properties to a flat Hash
|
145
132
|
results.collect! do |result|
|
146
133
|
{
|
147
|
-
"artifactory_standard_path" => generate_artifactory_standard_path(result),
|
148
134
|
"filename" => result["name"],
|
149
135
|
}.merge(
|
150
136
|
map_properties(result["properties"])
|
@@ -156,26 +142,20 @@ items.find(
|
|
156
142
|
end
|
157
143
|
|
158
144
|
#
|
159
|
-
#
|
145
|
+
# GET request
|
160
146
|
#
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
end
|
147
|
+
def get(url)
|
148
|
+
uri = URI.parse(endpoint)
|
149
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
150
|
+
http.use_ssl = (uri.scheme == "https")
|
166
151
|
|
167
|
-
|
168
|
-
|
152
|
+
full_path = File.join(uri.path, url)
|
153
|
+
request = Net::HTTP::Get.new(full_path)
|
169
154
|
|
170
|
-
|
171
|
-
# Artifactory GET request
|
172
|
-
#
|
173
|
-
def get(url)
|
174
|
-
results = artifactory_request do
|
175
|
-
client.get(url)
|
176
|
-
end
|
155
|
+
res = http.request(request)
|
177
156
|
|
178
|
-
|
157
|
+
res.value
|
158
|
+
JSON.parse(res.body)
|
179
159
|
end
|
180
160
|
|
181
161
|
def create_artifact(artifact_map)
|
@@ -183,6 +163,8 @@ items.find(
|
|
183
163
|
artifact_map["omnibus.platform_version"])
|
184
164
|
|
185
165
|
chef_standard_path = generate_chef_standard_path(options.channel,
|
166
|
+
artifact_map["omnibus.project"],
|
167
|
+
artifact_map["omnibus.version"],
|
186
168
|
platform,
|
187
169
|
platform_version,
|
188
170
|
artifact_map["filename"]
|
@@ -196,13 +178,7 @@ items.find(
|
|
196
178
|
platform: platform,
|
197
179
|
platform_version: platform_version,
|
198
180
|
architecture: normalize_architecture(artifact_map["omnibus.architecture"]),
|
199
|
-
|
200
|
-
# feature flags.
|
201
|
-
url: if Mixlib::Install.unified_backend?
|
202
|
-
chef_standard_path
|
203
|
-
else
|
204
|
-
artifact_map["artifactory_standard_path"]
|
205
|
-
end
|
181
|
+
url: chef_standard_path
|
206
182
|
)
|
207
183
|
end
|
208
184
|
|
@@ -219,56 +195,32 @@ items.find(
|
|
219
195
|
end
|
220
196
|
|
221
197
|
# Generates a chef standard download uri in the form of
|
222
|
-
# http://endpoint/channel
|
223
|
-
def generate_chef_standard_path(channel, platform, platform_version, filename)
|
198
|
+
# http://endpoint/files/:channel/:project/:version/:platform/:platform_version/:file
|
199
|
+
def generate_chef_standard_path(channel, project, version, platform, platform_version, filename)
|
200
|
+
# For some older platform & platform_version combinations we need to
|
201
|
+
# use COMPAT_DOWNLOAD_URL_ENDPOINT since these versions have an
|
202
|
+
# OpenSSL version that can not verify the ENDPOINT based urls
|
203
|
+
base_url = case "#{platform}-#{platform_version}"
|
204
|
+
when "freebsd-9", "el-5", "solaris2-5.9", "solaris2-5.10"
|
205
|
+
COMPAT_DOWNLOAD_URL_ENDPOINT
|
206
|
+
else
|
207
|
+
endpoint
|
208
|
+
end
|
209
|
+
|
224
210
|
uri = []
|
225
|
-
uri <<
|
211
|
+
uri << base_url.sub(/\/$/, "")
|
212
|
+
uri << "files"
|
226
213
|
uri << channel
|
214
|
+
uri << project
|
215
|
+
uri << version
|
227
216
|
uri << platform
|
228
217
|
uri << platform_version
|
229
218
|
uri << filename
|
230
219
|
uri.join("/")
|
231
220
|
end
|
232
221
|
|
233
|
-
# Generates an artifactory standard download uri
|
234
|
-
def generate_artifactory_standard_path(result)
|
235
|
-
uri = []
|
236
|
-
uri << endpoint.sub(/\/$/, "")
|
237
|
-
uri << result["repo"]
|
238
|
-
uri << result["path"]
|
239
|
-
uri << result["name"]
|
240
|
-
uri.join("/")
|
241
|
-
end
|
242
|
-
|
243
|
-
def client
|
244
|
-
@client ||= ::Artifactory::Client.new(
|
245
|
-
endpoint: endpoint,
|
246
|
-
username: ARTIFACTORY_USERNAME,
|
247
|
-
password: ARTIFACTORY_PASSWORD
|
248
|
-
)
|
249
|
-
end
|
250
|
-
|
251
222
|
def endpoint
|
252
|
-
@endpoint ||= ENV.fetch("
|
253
|
-
end
|
254
|
-
|
255
|
-
def artifactory_request
|
256
|
-
begin
|
257
|
-
results = yield
|
258
|
-
rescue Errno::ETIMEDOUT, ::Artifactory::Error::ConnectionError
|
259
|
-
raise ConnectionError, <<-MSG
|
260
|
-
Artifactory endpoint '#{endpoint}' is unreachable. Check that
|
261
|
-
the endpoint is correct and there is an open connection to Chef's private network.
|
262
|
-
MSG
|
263
|
-
rescue ::Artifactory::Error::HTTPError => e
|
264
|
-
if e.code == 401 && e.message =~ /Bad credentials/
|
265
|
-
raise AuthenticationError, "Artifactory server denied credentials."
|
266
|
-
else
|
267
|
-
raise e
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
results
|
223
|
+
@endpoint ||= ENV.fetch("PACKAGE_ROUTER_ENDPOINT", ENDPOINT)
|
272
224
|
end
|
273
225
|
|
274
226
|
def omnibus_project
|
@@ -17,7 +17,6 @@
|
|
17
17
|
|
18
18
|
require "erb"
|
19
19
|
require "ostruct"
|
20
|
-
require "mixlib/install/backend/omnitruck"
|
21
20
|
|
22
21
|
module Mixlib
|
23
22
|
class Install
|
@@ -47,7 +46,7 @@ module Mixlib
|
|
47
46
|
# and returnt the contents of the script
|
48
47
|
if File.exist? "#{script_path}.erb"
|
49
48
|
# Default values to use incase they are not set in the context
|
50
|
-
context[:base_url] ||=
|
49
|
+
context[:base_url] ||= "https://omnitruck.chef.io/"
|
51
50
|
|
52
51
|
context_object = OpenStruct.new(context).instance_eval { binding }
|
53
52
|
ERB.new(File.read("#{script_path}.erb")).result(context_object)
|
@@ -16,7 +16,6 @@
|
|
16
16
|
#
|
17
17
|
|
18
18
|
require "mixlib/install/generator/base"
|
19
|
-
require "mixlib/install/backend/artifactory"
|
20
19
|
|
21
20
|
module Mixlib
|
22
21
|
class Install
|
@@ -46,13 +45,7 @@ module Mixlib
|
|
46
45
|
install_command << get_script("helpers.sh")
|
47
46
|
install_command << render_variables
|
48
47
|
install_command << get_script("platform_detection.sh")
|
49
|
-
|
50
|
-
# urls for the packages here.
|
51
|
-
if options.for_unstable?
|
52
|
-
install_command << artifactory_urls
|
53
|
-
else
|
54
|
-
install_command << get_script("fetch_metadata.sh")
|
55
|
-
end
|
48
|
+
install_command << get_script("fetch_metadata.sh")
|
56
49
|
install_command << get_script("fetch_package.sh")
|
57
50
|
install_command << get_script("install_package.sh")
|
58
51
|
|
@@ -66,11 +59,6 @@ version=#{options.product_version}
|
|
66
59
|
channel=#{options.channel}
|
67
60
|
EOS
|
68
61
|
end
|
69
|
-
|
70
|
-
def artifactory_urls
|
71
|
-
artifacts = Mixlib::Install::Backend::Artifactory.new(options).info
|
72
|
-
get_script("artifactory_urls.sh", artifacts: artifacts)
|
73
|
-
end
|
74
62
|
end
|
75
63
|
end
|
76
64
|
end
|
@@ -46,13 +46,7 @@ module Mixlib
|
|
46
46
|
def install_command
|
47
47
|
install_project_module = []
|
48
48
|
install_project_module << get_script("helpers.ps1")
|
49
|
-
|
50
|
-
# urls for the packages here.
|
51
|
-
install_project_module << if options.for_unstable?
|
52
|
-
artifactory_urls
|
53
|
-
else
|
54
|
-
get_script("get_project_metadata.ps1")
|
55
|
-
end
|
49
|
+
install_project_module << get_script("get_project_metadata.ps1")
|
56
50
|
install_project_module << get_script("install_project.ps1")
|
57
51
|
|
58
52
|
install_command = []
|
@@ -73,26 +67,9 @@ module Mixlib
|
|
73
67
|
self.class.ps1_modularize(module_body, module_name)
|
74
68
|
end
|
75
69
|
|
76
|
-
def artifactory_urls
|
77
|
-
get_script("get_project_metadata_for_artifactory.ps1",
|
78
|
-
artifacts: Array(artifacts))
|
79
|
-
end
|
80
|
-
|
81
|
-
def artifacts
|
82
|
-
@artifacts ||= Mixlib::Install::Backend::Artifactory.new(options).info
|
83
|
-
end
|
84
|
-
|
85
|
-
def product_version
|
86
|
-
if options.for_unstable?
|
87
|
-
artifacts.first.version
|
88
|
-
else
|
89
|
-
options.product_version
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
70
|
def render_command
|
94
71
|
cmd = "install -project #{options.product_name}"
|
95
|
-
cmd << " -version #{product_version}"
|
72
|
+
cmd << " -version #{options.product_version}"
|
96
73
|
cmd << " -channel #{options.channel}"
|
97
74
|
cmd << " -architecture #{options.architecture}" if options.architecture
|
98
75
|
cmd << "\n"
|