kitchen-provisioner-ice-temp 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +15 -0
- data/README.md +93 -0
- data/lib/kitchen/provisioner/chef_ice/version.rb +15 -0
- data/lib/kitchen/provisioner/chef_ice.rb +467 -0
- metadata +62 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 468d51adccc34199758691e4f870dc8871355fd30385f4566fce0ff45d13c37e
|
|
4
|
+
data.tar.gz: 0cc08b227af5a42025f618d83d45fcc7d4ed3a575bca390e669dccb5551816f8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: fc7ac161ce6246f10c1631dabac90620a56d6eb091291b629235e4ad3d3b539d4790ee14df3e2c79e80ba05eb57684a23a81dc1e8e90618e2fa8381f89e62989
|
|
7
|
+
data.tar.gz: 2cbadb303a0b7bc1ccd81a67beb977393f0ce197f908b0d27e5806462c35a52f718a42712386b1adcbe75366cd0188682b03cc2911b15e13671044028890b833
|
data/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
data/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# kitchen-provisioner-ice-temp
|
|
2
|
+
|
|
3
|
+
> **⚠️ This is a temporary stopgap gem.**
|
|
4
|
+
> It provides Chef ICE (Chef Infra Client 19+) support for Test Kitchen
|
|
5
|
+
> until a future Chef Workstation release ships with native chef-ice
|
|
6
|
+
> provisioning built in. Once you upgrade to that version of Chef
|
|
7
|
+
> Workstation, **uninstall this gem** and switch your `kitchen.yml` back
|
|
8
|
+
> to the built-in provisioner.
|
|
9
|
+
|
|
10
|
+
A [Test Kitchen](https://kitchen.ci/) provisioner for **Chef ICE** (Chef Infra Client 19+).
|
|
11
|
+
|
|
12
|
+
This is a thin layer on top of the existing `ChefInfra` provisioner from `kitchen-omnibus-chef`. It overrides only the install path to use the Chef commercial downloads API (or a direct URL). Everything else — cookbook upload, data bags, roles, config files, `chef-client --local-mode` execution — is inherited.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
gem install kitchen-provisioner-ice-temp
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or in your cookbook's `Gemfile`:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
gem "kitchen-provisioner-ice-temp"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Via Chef commercial downloads API
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
provisioner:
|
|
32
|
+
name: chef_ice
|
|
33
|
+
product_version: 19.2.12 # or "latest"
|
|
34
|
+
channel: stable
|
|
35
|
+
chef_license_key: "your-key" # or set CHEF_LICENSE_KEY env var
|
|
36
|
+
chef_license: accept
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Via direct download URL
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
provisioner:
|
|
43
|
+
name: chef_ice
|
|
44
|
+
download_url: https://my-mirror.example.com/chef-ice-19.2.12-1_amd64.deb
|
|
45
|
+
checksum: abc123... # optional SHA-256
|
|
46
|
+
chef_license: accept
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Skip installation (pre-baked image)
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
provisioner:
|
|
53
|
+
name: chef_ice
|
|
54
|
+
install_strategy: skip
|
|
55
|
+
chef_license: accept
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Configuration
|
|
59
|
+
|
|
60
|
+
All settings from the standard `chef_infra` / `chef_zero` provisioner are supported. Additional settings:
|
|
61
|
+
|
|
62
|
+
| Setting | Default | Description |
|
|
63
|
+
|---------|---------|-------------|
|
|
64
|
+
| `product_version` | `latest` | Chef ICE version |
|
|
65
|
+
| `channel` | `stable` | Release channel |
|
|
66
|
+
| `chef_license_key` | `ENV["CHEF_LICENSE_KEY"]` | License key for commercial API |
|
|
67
|
+
| `downloads_api_url` | `https://commercial-acceptance.downloads.chef.co` | API base URL |
|
|
68
|
+
| `download_url` | `nil` | Direct package URL (bypasses API) |
|
|
69
|
+
|
|
70
|
+
## Removal
|
|
71
|
+
|
|
72
|
+
When a future Chef Workstation ships with native chef-ice support:
|
|
73
|
+
|
|
74
|
+
1. **Uninstall the gem:**
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
chef exec gem uninstall kitchen-provisioner-ice-temp
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
2. **Update `kitchen.yml`** — the provisioner name `chef_ice` won't change, but check the Chef Workstation release notes in case the built-in provisioner uses a different name.
|
|
81
|
+
|
|
82
|
+
3. **Remove from Gemfile** (if listed):
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
# DELETE this line:
|
|
86
|
+
gem "kitchen-provisioner-ice-temp"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
That's it — all other configuration (`product_version`, `channel`, `chef_license_key`, etc.) should carry over to the built-in provisioner unchanged.
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
Apache-2.0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kitchen
|
|
4
|
+
module Provisioner
|
|
5
|
+
CHEF_ICE_VERSION = begin
|
|
6
|
+
dir = File.expand_path("../../..", __dir__)
|
|
7
|
+
if File.directory?(File.join(dir, ".git"))
|
|
8
|
+
tag = `git -C #{dir} describe --tags --match 'v*' 2>/dev/null`.strip
|
|
9
|
+
tag.empty? ? "0.0.0" : tag.sub(/^v/, "").sub(/-(\d+)-g/, '.\1.dev.')
|
|
10
|
+
else
|
|
11
|
+
Gem.loaded_specs["kitchen-provisioner-ice-temp"]&.version&.to_s || "0.0.0"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
# Test Kitchen provisioner for Chef ICE (Chef Infra Client 19+).
|
|
2
|
+
#
|
|
3
|
+
# Thin layer on top of the existing ChefInfra provisioner from
|
|
4
|
+
# kitchen-omnibus-chef. Only overrides the install path to use the
|
|
5
|
+
# Chef commercial downloads API or a direct download URL.
|
|
6
|
+
# Everything else (sandbox, config files, run_command, policyfile/berkshelf)
|
|
7
|
+
# is inherited from ChefInfra.
|
|
8
|
+
#
|
|
9
|
+
# Architecture
|
|
10
|
+
# ------------
|
|
11
|
+
# The package is downloaded in Ruby on the workstation during
|
|
12
|
+
# create_sandbox, placed in the sandbox directory, and uploaded to
|
|
13
|
+
# the node with the rest of the sandbox files (cookbooks, config, etc.).
|
|
14
|
+
#
|
|
15
|
+
# prepare_command (which runs AFTER the sandbox upload but BEFORE
|
|
16
|
+
# run_command) installs the local package file using the native
|
|
17
|
+
# package manager. No curl/wget is needed on the node, no URLs
|
|
18
|
+
# appear in the shell script, and the license key never leaves
|
|
19
|
+
# the workstation.
|
|
20
|
+
|
|
21
|
+
require "kitchen/provisioner/chef_infra"
|
|
22
|
+
require_relative "chef_ice/version"
|
|
23
|
+
require "json"
|
|
24
|
+
require "net/http"
|
|
25
|
+
require "uri"
|
|
26
|
+
require "fileutils"
|
|
27
|
+
require "digest"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
module Kitchen
|
|
32
|
+
module Provisioner
|
|
33
|
+
class ChefIce < ChefInfra
|
|
34
|
+
kitchen_provisioner_api_version 2
|
|
35
|
+
|
|
36
|
+
plugin_version Kitchen::Provisioner::CHEF_ICE_VERSION
|
|
37
|
+
|
|
38
|
+
# Do NOT set product_name — it triggers mixlib-install validation
|
|
39
|
+
# in the parent which doesn't know about chef-ice.
|
|
40
|
+
# Instead we set require_chef_omnibus to false and handle install ourselves.
|
|
41
|
+
default_config :require_chef_omnibus, false
|
|
42
|
+
|
|
43
|
+
# Commercial downloads API base URL
|
|
44
|
+
default_config :downloads_api_url, "https://commercial-acceptance.downloads.chef.co"
|
|
45
|
+
|
|
46
|
+
# Licence key for the commercial downloads API
|
|
47
|
+
default_config :chef_license_key do |_p|
|
|
48
|
+
ENV["CHEF_LICENSE_KEY"]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
default_config :product_version, "latest"
|
|
52
|
+
default_config :channel, "stable"
|
|
53
|
+
default_config :install_strategy, "once" # once, always, skip
|
|
54
|
+
default_config :download_url, nil
|
|
55
|
+
default_config :checksum, nil
|
|
56
|
+
|
|
57
|
+
# Skip the enterprise gem delegation dance from ChefInfra —
|
|
58
|
+
# we ARE the replacement provisioner.
|
|
59
|
+
def self.new(config = {})
|
|
60
|
+
allocate.tap { |i| i.send(:initialize, config) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# --- sandbox --------------------------------------------------
|
|
64
|
+
|
|
65
|
+
# Download the chef-ice package into the sandbox so it gets
|
|
66
|
+
# uploaded alongside cookbooks, data bags, etc.
|
|
67
|
+
def create_sandbox
|
|
68
|
+
super
|
|
69
|
+
return if config[:install_strategy] == "skip"
|
|
70
|
+
|
|
71
|
+
prepare_package
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# --- commands sent to the node --------------------------------
|
|
75
|
+
|
|
76
|
+
# install_command runs BEFORE the sandbox upload.
|
|
77
|
+
# We only use it for the guard check — actual install happens
|
|
78
|
+
# in prepare_command after the package file has been uploaded.
|
|
79
|
+
def install_command
|
|
80
|
+
return if config[:install_strategy] == "skip"
|
|
81
|
+
|
|
82
|
+
prefix_command(wrap_shell_code(install_guard))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# prepare_command runs AFTER sandbox upload and BEFORE run_command.
|
|
86
|
+
# The package file(s) are now on the node under root_path.
|
|
87
|
+
def prepare_command
|
|
88
|
+
return if config[:install_strategy] == "skip"
|
|
89
|
+
return unless @package_files && !@package_files.empty?
|
|
90
|
+
|
|
91
|
+
prefix_command(wrap_shell_code(install_package_script))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Skip the parent's license check which goes through
|
|
95
|
+
# license-acceptance gem with product names it may not recognise.
|
|
96
|
+
def check_license
|
|
97
|
+
# chef-ice license is handled by chef_license / chef_license_key
|
|
98
|
+
# config passed to chef-client at run time.
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
# ---------------------------------------------------------------
|
|
104
|
+
# Package download (runs on workstation during create_sandbox)
|
|
105
|
+
# ---------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
# Decide which path to take and download the package into the
|
|
108
|
+
# sandbox directory.
|
|
109
|
+
def prepare_package
|
|
110
|
+
if config[:download_url]
|
|
111
|
+
download_from_url
|
|
112
|
+
elsif config[:chef_license_key]
|
|
113
|
+
download_from_api
|
|
114
|
+
else
|
|
115
|
+
raise UserError,
|
|
116
|
+
"chef_license_key is required to download chef-ice from the " \
|
|
117
|
+
"commercial downloads API. Set it in kitchen.yml or via the " \
|
|
118
|
+
"CHEF_LICENSE_KEY env var. Alternatively, set download_url to " \
|
|
119
|
+
"point at a local package."
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Download from a user-supplied direct URL.
|
|
124
|
+
def download_from_url
|
|
125
|
+
url = config[:download_url]
|
|
126
|
+
name = File.basename(URI.parse(url).path)
|
|
127
|
+
dest = File.join(sandbox_path, name)
|
|
128
|
+
|
|
129
|
+
info("Downloading chef-ice from #{url}")
|
|
130
|
+
http_download(URI.parse(url), dest)
|
|
131
|
+
verify_sha256(dest, config[:checksum]) if config[:checksum]
|
|
132
|
+
|
|
133
|
+
@package_files = [{ filename: name }]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Walk the commercial downloads API and download the correct
|
|
137
|
+
# package(s) for the target platform into the sandbox.
|
|
138
|
+
def download_from_api
|
|
139
|
+
version = resolve_version
|
|
140
|
+
packages = fetch_packages(version)
|
|
141
|
+
|
|
142
|
+
info("Chef ICE #{version} (license: ****)")
|
|
143
|
+
|
|
144
|
+
# Only download formats the target platform can use.
|
|
145
|
+
formats = windows_os? ? %w[msi] : %w[rpm deb]
|
|
146
|
+
platform = windows_os? ? "windows" : "linux"
|
|
147
|
+
|
|
148
|
+
@package_files = []
|
|
149
|
+
|
|
150
|
+
packages.each do |arch, pm_map|
|
|
151
|
+
formats.each do |pm|
|
|
152
|
+
detail = pm_map[pm]
|
|
153
|
+
next unless detail
|
|
154
|
+
|
|
155
|
+
url = detail["url"]
|
|
156
|
+
sha256 = detail["sha256"]
|
|
157
|
+
|
|
158
|
+
# Check the shared Chef Workstation cache first.
|
|
159
|
+
# Layout: ~/.chef/cached-packages/{platform}/{arch}/{pm}/chef-ice/{version}/
|
|
160
|
+
cache_subdir = File.join(platform, arch, pm, "chef-ice", version)
|
|
161
|
+
cached = find_in_cache(cache_subdir, sha256)
|
|
162
|
+
|
|
163
|
+
if cached
|
|
164
|
+
filename = File.basename(cached)
|
|
165
|
+
dest = File.join(sandbox_path, filename)
|
|
166
|
+
info(" Using cached #{filename}")
|
|
167
|
+
FileUtils.cp(cached, dest)
|
|
168
|
+
else
|
|
169
|
+
# Download to a temp location, resolve real filename from
|
|
170
|
+
# Content-Disposition header, then move into cache.
|
|
171
|
+
info(" Downloading chef-ice #{version} #{pm} (#{arch})")
|
|
172
|
+
tmpfile = File.join(sandbox_path, "chef-ice-download.tmp")
|
|
173
|
+
real_name = http_download(URI.parse(url), tmpfile)
|
|
174
|
+
|
|
175
|
+
# Use Content-Disposition filename if we got one,
|
|
176
|
+
# otherwise fall back to a constructed name.
|
|
177
|
+
filename = real_name || "chef-ice-#{version}-#{arch}.#{pm}"
|
|
178
|
+
dest = File.join(sandbox_path, filename)
|
|
179
|
+
FileUtils.mv(tmpfile, dest) if File.exist?(tmpfile)
|
|
180
|
+
|
|
181
|
+
verify_sha256(dest, sha256) if sha256 && !sha256.empty?
|
|
182
|
+
store_in_cache(dest, cache_subdir, sha256)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
@package_files << { arch: arch, pm: pm, filename: filename }
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
if @package_files.empty?
|
|
190
|
+
raise UserError, "No downloadable packages found for chef-ice #{version}"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# ---------------------------------------------------------------
|
|
195
|
+
# API helpers (run on workstation — key never sent to the node)
|
|
196
|
+
# ---------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
# GET a JSON endpoint from the commercial downloads API.
|
|
199
|
+
def api_get(path, params = {})
|
|
200
|
+
params["license_id"] = config[:chef_license_key]
|
|
201
|
+
uri = URI.parse("#{config[:downloads_api_url]}#{path}")
|
|
202
|
+
uri.query = URI.encode_www_form(params)
|
|
203
|
+
|
|
204
|
+
response = http_get_follow(uri, 5)
|
|
205
|
+
|
|
206
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
207
|
+
safe_uri = uri.to_s.gsub(config[:chef_license_key].to_s, "****")
|
|
208
|
+
raise UserError,
|
|
209
|
+
"Chef downloads API returned #{response.code}: " \
|
|
210
|
+
"#{response.body.strip} (#{safe_uri})"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
JSON.parse(response.body)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Resolve "latest" to a concrete version string.
|
|
217
|
+
def resolve_version
|
|
218
|
+
version = config[:product_version]
|
|
219
|
+
return version unless version.nil? || version == "latest"
|
|
220
|
+
|
|
221
|
+
channel = config[:channel]
|
|
222
|
+
versions = api_get("/#{channel}/chef-ice/versions/all")
|
|
223
|
+
|
|
224
|
+
raise UserError, "No versions found for chef-ice on #{channel} channel" if versions.empty?
|
|
225
|
+
|
|
226
|
+
versions.sort_by { |v| Gem::Version.new(v) }.last
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Fetch the packages map and merge all platform keys.
|
|
230
|
+
# Returns: { "x86_64" => { "rpm" => { ... }, "deb" => { ... } }, ... }
|
|
231
|
+
def fetch_packages(version)
|
|
232
|
+
channel = config[:channel]
|
|
233
|
+
response = api_get("/#{channel}/chef-ice/packages", "v" => version)
|
|
234
|
+
|
|
235
|
+
merged = {}
|
|
236
|
+
response.each_value do |arch_map|
|
|
237
|
+
next unless arch_map.is_a?(Hash)
|
|
238
|
+
|
|
239
|
+
arch_map.each do |arch, pm_map|
|
|
240
|
+
merged[arch] ||= {}
|
|
241
|
+
merged[arch].merge!(pm_map) if pm_map.is_a?(Hash)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
raise UserError, "No packages found for chef-ice #{version}" if merged.empty?
|
|
246
|
+
|
|
247
|
+
merged
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# ---------------------------------------------------------------
|
|
253
|
+
# HTTP helpers
|
|
254
|
+
# ---------------------------------------------------------------
|
|
255
|
+
|
|
256
|
+
# GET with redirect following — returns the response object.
|
|
257
|
+
def http_get_follow(uri, limit)
|
|
258
|
+
raise UserError, "Too many HTTP redirects" if limit <= 0
|
|
259
|
+
|
|
260
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
261
|
+
http.use_ssl = (uri.scheme == "https")
|
|
262
|
+
http.open_timeout = 10
|
|
263
|
+
http.read_timeout = 30
|
|
264
|
+
|
|
265
|
+
response = http.request(Net::HTTP::Get.new(uri))
|
|
266
|
+
|
|
267
|
+
case response
|
|
268
|
+
when Net::HTTPRedirection
|
|
269
|
+
http_get_follow(URI.parse(response["location"]), limit - 1)
|
|
270
|
+
else
|
|
271
|
+
response
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Stream a URI to a local file, following redirects.
|
|
276
|
+
# Returns the filename from Content-Disposition header if present, else nil.
|
|
277
|
+
def http_download(uri, dest, limit = 5)
|
|
278
|
+
raise UserError, "Too many HTTP redirects downloading #{File.basename(dest)}" if limit <= 0
|
|
279
|
+
|
|
280
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
281
|
+
request = Net::HTTP::Get.new(uri)
|
|
282
|
+
http.request(request) do |response|
|
|
283
|
+
case response
|
|
284
|
+
when Net::HTTPRedirection
|
|
285
|
+
return http_download(URI.parse(response["location"]), dest, limit - 1)
|
|
286
|
+
when Net::HTTPSuccess
|
|
287
|
+
File.open(dest, "wb") do |f|
|
|
288
|
+
response.read_body { |chunk| f.write(chunk) }
|
|
289
|
+
end
|
|
290
|
+
# Extract filename from Content-Disposition if present
|
|
291
|
+
cd = response["content-disposition"]
|
|
292
|
+
if cd && cd =~ /filename=["']?([^"';\s]+)/
|
|
293
|
+
return $1
|
|
294
|
+
end
|
|
295
|
+
return nil
|
|
296
|
+
else
|
|
297
|
+
raise UserError,
|
|
298
|
+
"Failed to download #{File.basename(dest)}: HTTP #{response.code}"
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Verify a file matches an expected SHA-256 hex digest.
|
|
305
|
+
def verify_sha256(path, expected)
|
|
306
|
+
actual = Digest::SHA256.file(path).hexdigest
|
|
307
|
+
return if actual == expected
|
|
308
|
+
|
|
309
|
+
raise UserError,
|
|
310
|
+
"Checksum mismatch for #{File.basename(path)}: " \
|
|
311
|
+
"expected #{expected}, got #{actual}"
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# ---------------------------------------------------------------
|
|
315
|
+
# Package cache — shared with chef-pkg at ~/.chef/cached-packages/
|
|
316
|
+
#
|
|
317
|
+
# Layout:
|
|
318
|
+
# ~/.chef/cached-packages/{platform}/{arch}/{pm}/chef-ice/{version}/
|
|
319
|
+
# chef-ice-19.2.12-1.amzn2.x86_64.rpm
|
|
320
|
+
# chef-ice-19.2.12-1.amzn2.x86_64.rpm.sha256
|
|
321
|
+
# ---------------------------------------------------------------
|
|
322
|
+
|
|
323
|
+
CACHE_ROOT = File.join(Dir.home, ".chef", "cached-packages")
|
|
324
|
+
|
|
325
|
+
# Find a cached package file by scanning the cache subdirectory
|
|
326
|
+
# for a file whose .sha256 sidecar matches the expected digest.
|
|
327
|
+
# Returns the full path to the cached file, or nil.
|
|
328
|
+
def find_in_cache(subdir, expected_sha256)
|
|
329
|
+
dir = File.join(CACHE_ROOT, subdir)
|
|
330
|
+
return nil unless File.directory?(dir)
|
|
331
|
+
return nil if expected_sha256.nil? || expected_sha256.empty?
|
|
332
|
+
|
|
333
|
+
Dir.glob(File.join(dir, "*")).each do |path|
|
|
334
|
+
next if path.end_with?(".sha256")
|
|
335
|
+
|
|
336
|
+
sidecar = "#{path}.sha256"
|
|
337
|
+
next unless File.exist?(sidecar)
|
|
338
|
+
next unless File.read(sidecar).strip == expected_sha256
|
|
339
|
+
|
|
340
|
+
return path
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
nil
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Store a downloaded file in the cache with a .sha256 sidecar.
|
|
347
|
+
def store_in_cache(source, subdir, sha256)
|
|
348
|
+
return if sha256.nil? || sha256.empty?
|
|
349
|
+
|
|
350
|
+
dir = File.join(CACHE_ROOT, subdir)
|
|
351
|
+
FileUtils.mkdir_p(dir)
|
|
352
|
+
dest = File.join(dir, File.basename(source))
|
|
353
|
+
FileUtils.cp(source, dest)
|
|
354
|
+
File.write("#{dest}.sha256", sha256)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# ---------------------------------------------------------------
|
|
358
|
+
# Shell script generators (run on the node)
|
|
359
|
+
# ---------------------------------------------------------------
|
|
360
|
+
|
|
361
|
+
# Guard clause: skip install if chef-client already present.
|
|
362
|
+
def install_guard
|
|
363
|
+
<<~SH
|
|
364
|
+
if [ -x "#{config[:chef_client_path]}" ]; then
|
|
365
|
+
echo "-----> Chef ICE already installed, skipping"
|
|
366
|
+
exit 0
|
|
367
|
+
fi
|
|
368
|
+
SH
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# Generate a shell script that installs the package file(s)
|
|
372
|
+
# already uploaded to root_path on the node.
|
|
373
|
+
def install_package_script
|
|
374
|
+
if @package_files.length == 1 && !@package_files[0][:arch]
|
|
375
|
+
# Single file from download_url — install directly
|
|
376
|
+
remote = remote_path_join(config[:root_path], @package_files[0][:filename])
|
|
377
|
+
return install_single_package(remote)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# Multiple arch/pm files from the API — pick the right one at runtime
|
|
381
|
+
install_multi_package
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Install a single known package file.
|
|
385
|
+
def install_single_package(remote_pkg)
|
|
386
|
+
<<~SH
|
|
387
|
+
echo "-----> Installing Chef ICE from uploaded package"
|
|
388
|
+
case "#{remote_pkg}" in
|
|
389
|
+
*.deb) #{sudo("dpkg")} -i "#{remote_pkg}" || #{sudo("apt-get")} install -fy ;;
|
|
390
|
+
*.rpm)
|
|
391
|
+
if command -v dnf >/dev/null 2>&1; then #{sudo("dnf")} install -y "#{remote_pkg}"
|
|
392
|
+
elif command -v yum >/dev/null 2>&1; then #{sudo("yum")} install -y "#{remote_pkg}"
|
|
393
|
+
else #{sudo("rpm")} -Uvh "#{remote_pkg}"
|
|
394
|
+
fi ;;
|
|
395
|
+
*.msi) msiexec /qn /i "#{remote_pkg}" ;;
|
|
396
|
+
*) echo "ERROR: unknown package format" >&2; exit 1 ;;
|
|
397
|
+
esac
|
|
398
|
+
SH
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# Build a shell script with arch detection and pm detection that
|
|
402
|
+
# picks the correct package file from the set uploaded to root_path.
|
|
403
|
+
def install_multi_package
|
|
404
|
+
root = config[:root_path]
|
|
405
|
+
|
|
406
|
+
arch_branches = {}
|
|
407
|
+
@package_files.each do |pf|
|
|
408
|
+
arch_branches[pf[:arch]] ||= []
|
|
409
|
+
arch_branches[pf[:arch]] << pf
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
script = []
|
|
413
|
+
script << <<~SH
|
|
414
|
+
echo "-----> Installing Chef ICE from uploaded package"
|
|
415
|
+
machine="$(uname -m)"
|
|
416
|
+
case "$machine" in
|
|
417
|
+
x86_64|amd64) arch="x86_64" ;;
|
|
418
|
+
aarch64|arm64) arch="aarch64" ;;
|
|
419
|
+
*) echo "ERROR: unsupported architecture $machine" >&2; exit 1 ;;
|
|
420
|
+
esac
|
|
421
|
+
SH
|
|
422
|
+
|
|
423
|
+
case_lines = []
|
|
424
|
+
arch_branches.each do |arch, files|
|
|
425
|
+
pm_lines = []
|
|
426
|
+
files.each do |pf|
|
|
427
|
+
remote = remote_path_join(root, pf[:filename])
|
|
428
|
+
keyword = pm_lines.empty? ? "if" : "elif"
|
|
429
|
+
case pf[:pm]
|
|
430
|
+
when "rpm"
|
|
431
|
+
pm_lines << " #{keyword} command -v rpm >/dev/null 2>&1; then"
|
|
432
|
+
pm_lines << " if command -v dnf >/dev/null 2>&1; then #{sudo("dnf")} install -y \"#{remote}\""
|
|
433
|
+
pm_lines << " elif command -v yum >/dev/null 2>&1; then #{sudo("yum")} install -y \"#{remote}\""
|
|
434
|
+
pm_lines << " else #{sudo("rpm")} -Uvh \"#{remote}\""
|
|
435
|
+
pm_lines << " fi"
|
|
436
|
+
when "deb"
|
|
437
|
+
pm_lines << " #{keyword} command -v dpkg >/dev/null 2>&1; then"
|
|
438
|
+
pm_lines << " #{sudo("dpkg")} -i \"#{remote}\" || #{sudo("apt-get")} install -fy"
|
|
439
|
+
when "msi"
|
|
440
|
+
pm_lines << " #{keyword} command -v msiexec >/dev/null 2>&1; then"
|
|
441
|
+
pm_lines << " msiexec /qn /i \"#{remote}\""
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
unless pm_lines.empty?
|
|
445
|
+
pm_lines << " else"
|
|
446
|
+
pm_lines << " echo \"ERROR: no supported package manager found\" >&2"
|
|
447
|
+
pm_lines << " exit 1"
|
|
448
|
+
pm_lines << " fi"
|
|
449
|
+
end
|
|
450
|
+
case_lines << " #{arch})\n#{pm_lines.join("\n")}\n ;;"
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
script << <<~SH
|
|
454
|
+
case "$arch" in
|
|
455
|
+
#{case_lines.join("\n")}
|
|
456
|
+
*)
|
|
457
|
+
echo "ERROR: no chef-ice package for architecture $arch" >&2
|
|
458
|
+
exit 1
|
|
459
|
+
;;
|
|
460
|
+
esac
|
|
461
|
+
SH
|
|
462
|
+
|
|
463
|
+
script.join("\n")
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: kitchen-provisioner-ice-temp
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Richard Nixon
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-30 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: A temporary stopgap Test Kitchen provisioner for chef-ice (chef-client
|
|
14
|
+
19+). Uninstall this gem once Chef Workstation ships with native chef-ice provisioning
|
|
15
|
+
built in. See README.md for removal instructions.
|
|
16
|
+
email:
|
|
17
|
+
- richard.nixon@btinternet.com
|
|
18
|
+
executables: []
|
|
19
|
+
extensions: []
|
|
20
|
+
extra_rdoc_files: []
|
|
21
|
+
files:
|
|
22
|
+
- LICENSE
|
|
23
|
+
- README.md
|
|
24
|
+
- lib/kitchen/provisioner/chef_ice.rb
|
|
25
|
+
- lib/kitchen/provisioner/chef_ice/version.rb
|
|
26
|
+
homepage: https://github.com/trickyearlobe-chef/kitchen-provisioner-ice-temp
|
|
27
|
+
licenses:
|
|
28
|
+
- Apache-2.0
|
|
29
|
+
metadata:
|
|
30
|
+
rubygems_mfa_required: 'true'
|
|
31
|
+
source_code_uri: https://github.com/trickyearlobe-chef/kitchen-provisioner-ice-temp
|
|
32
|
+
bug_tracker_uri: https://github.com/trickyearlobe-chef/kitchen-provisioner-ice-temp/issues
|
|
33
|
+
post_install_message: |
|
|
34
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
35
|
+
│ kitchen-provisioner-ice-temp is a TEMPORARY stopgap gem. │
|
|
36
|
+
│ │
|
|
37
|
+
│ Once Chef Workstation ships with native chef-ice support, │
|
|
38
|
+
│ uninstall this gem and switch to the built-in provisioner: │
|
|
39
|
+
│ │
|
|
40
|
+
│ chef exec gem uninstall kitchen-provisioner-ice-temp │
|
|
41
|
+
│ │
|
|
42
|
+
│ See README.md for full migration instructions. │
|
|
43
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
44
|
+
rdoc_options: []
|
|
45
|
+
require_paths:
|
|
46
|
+
- lib
|
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '3.1'
|
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '0'
|
|
57
|
+
requirements: []
|
|
58
|
+
rubygems_version: 3.5.22
|
|
59
|
+
signing_key:
|
|
60
|
+
specification_version: 4
|
|
61
|
+
summary: TEMPORARY Test Kitchen provisioner for Chef ICE (Chef Infra Client 19+)
|
|
62
|
+
test_files: []
|