kitchen-rackspace 0.21.2 → 0.22.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.
data/README.md CHANGED
@@ -1,95 +1,342 @@
1
- # Kitchen::Rackspace
1
+ # kitchen-rackspace
2
2
 
3
- [![Gem Version](https://img.shields.io/gem/v/kitchen-rackspace.svg)][gem]
3
+ [![Gem Version](https://img.shields.io/gem/v/kitchen-rackspace.svg)](https://rubygems.org/gems/kitchen-rackspace)
4
4
 
5
- [gem]: https://rubygems.org/gems/kitchen-rackspace
5
+ A [Test Kitchen](https://kitchen.ci/) driver that creates and destroys [Rackspace Cloud Servers](https://www.rackspace.com/cloud/servers), so you can test your cookbooks and infrastructure code on Rackspace instances.
6
6
 
7
- A Rackspace Cloud Servers driver for Test Kitchen!
7
+ > **This project is no longer under active development.** It has no active
8
+ > maintainers. The driver may continue to work for some or all use cases, but
9
+ > issues filed on GitHub will most likely not be triaged. If you are interested
10
+ > in maintaining it, come and talk to us in `#test-kitchen` on
11
+ > [Chef Community Slack](https://community-slack.chef.io/).
8
12
 
9
- Shamelessly copied from [Fletcher Nichol](https://github.com/fnichol)'s
10
- awesome work on an [EC2 driver](https://github.com/opscode/kitchen-ec2).
13
+ <!-- -->
11
14
 
12
- ## Status
15
+ > **This driver targets Legacy Rackspace Cloud, not OpenStack Flex.** It
16
+ > authenticates against Rackspace Cloud Identity v2.0, which is the API of the
17
+ > original Rackspace Public Cloud.
18
+ >
19
+ > In 2025 Rackspace launched [Rackspace OpenStack Flex][flex] — now branded
20
+ > simply "Rackspace Cloud" — built on vanilla OpenStack with Keystone v3. That
21
+ > platform is **not** supported by this driver. Use
22
+ > [kitchen-openstack][kitchen-openstack] instead, with `openstack_auth_url` set
23
+ > to `https://keystone.api.<region>.rackspacecloud.com/v3` (regions include
24
+ > `sjc3`, `iad3`, and `dfw3`).
25
+ >
26
+ > Rackspace continues to operate and maintain the legacy platform, so this
27
+ > driver still works against it, but Rackspace has said that new development is
28
+ > focused on Flex.
13
29
 
14
- This software project is no longer under active development as it has no active maintainers. The software may continue to work for some or all use cases, but issues filed in GitHub will most likely not be triaged. If a new maintainer is interested in working on this project please come chat with us in #test-kitchen on Chef Community Slack.
30
+ [flex]: https://docs.rackspace.com/docs/rackspace-openstack-flex-vs-rackspace-cloud
31
+ [kitchen-openstack]: https://github.com/test-kitchen/kitchen-openstack
32
+
33
+ <!-- -->
34
+
35
+ > This documentation uses [Cinc Workstation](https://cinc.sh/) and the `cinc` commands throughout. Everything here works identically with Chef Workstation — see [Using with Chef](#using-with-chef).
36
+
37
+ ## Requirements
38
+
39
+ - Ruby 3.1 or later (already satisfied if you use Cinc Workstation)
40
+ - A Rackspace Cloud account, with its username and API key
41
+ - An SSH public key, used to grant access to the created server
15
42
 
16
43
  ## Installation
17
44
 
18
- Add this line to your application's Gemfile:
45
+ This driver ships as part of [Cinc Workstation](https://cinc.sh/start/workstation/). If you have Cinc Workstation installed, there is nothing else to install.
19
46
 
20
- ```ruby
21
- gem 'kitchen-rackspace'
47
+ To install it into a standalone Ruby:
48
+
49
+ ```sh
50
+ gem install kitchen-rackspace
22
51
  ```
23
52
 
24
- And then execute:
53
+ Or with Bundler, add it to your `Gemfile`:
25
54
 
26
- ```shell
27
- bundle
55
+ ```ruby
56
+ gem "kitchen-rackspace"
28
57
  ```
29
58
 
30
- Or install it yourself as:
59
+ ...then run `bundle install`.
31
60
 
32
- ```shell
33
- gem install kitchen-rackspace
61
+ ## Authentication
62
+
63
+ Credentials come from either the driver options or the environment. The
64
+ environment is preferred, so credentials stay out of `kitchen.yml`:
65
+
66
+ ```sh
67
+ export RACKSPACE_USERNAME="myuser" # or OS_USERNAME
68
+ export RACKSPACE_API_KEY="myapikey" # or OS_PASSWORD
69
+ export RACKSPACE_REGION="dfw" # or OS_REGION_NAME
34
70
  ```
35
71
 
36
- ## Usage
72
+ If `RACKSPACE_REGION` is unset, the driver defaults to `dfw`.
37
73
 
38
- Provide, at a minimum, the required driver options in your `.kitchen.yml` file:
74
+ ## Quick Start
39
75
 
40
76
  ```yaml
77
+ ---
41
78
  driver:
42
79
  name: rackspace
43
- rackspace_username: [YOUR RACKSPACE CLOUD USERNAME]
44
- rackspace_api_key: [YOUR RACKSPACE CLOUD API KEY]
45
- require_chef_omnibus: [e.g. 'true' or a version number if you need Chef]
80
+ image_id: 09de0a66-3156-48b4-90a5-1cf25a905207 # see Choosing an image
81
+
82
+ provisioner:
83
+ name: cinc_infra
84
+
85
+ verifier:
86
+ name: cinc_auditor
87
+
46
88
  platforms:
47
- - name: [A PLATFORM NAME, e.g. 'centos-6']
89
+ - name: ubuntu-22.04
90
+
91
+ suites:
92
+ - name: default
93
+ run_list:
94
+ - recipe[my_cookbook::default]
95
+ ```
96
+
97
+ With credentials in the environment, that is enough: the driver builds the
98
+ image you named on a 2 GB General Purpose flavor, using an SSH key from your
99
+ `~/.ssh` directory.
100
+
101
+ > **Set `image_id` yourself.** The driver ships a table that maps platform
102
+ > names like `ubuntu-22.04` to image IDs, but it has not been refreshed since
103
+ > 2016, so most modern platform names are not in it and the build fails with
104
+ > `image_id` missing. See [Choosing an image](#choosing-an-image).
105
+
106
+ Then run the full test cycle:
107
+
108
+ ```sh
109
+ cinc kitchen test
110
+ ```
111
+
112
+ Or step through it:
113
+
114
+ ```sh
115
+ cinc kitchen create # build the Rackspace server
116
+ cinc kitchen converge # apply your cookbook
117
+ cinc kitchen verify # run your tests
118
+ cinc kitchen destroy # delete the server
119
+ ```
120
+
121
+ ## Choosing an image
122
+
123
+ `image_id` is a Rackspace image UUID, and it is the one option most people have
124
+ to set by hand.
125
+
126
+ The driver ships `data/images.json`, a table mapping Test Kitchen platform
127
+ names to image IDs so that a platform like `centos-7` resolves on its own.
128
+ **That table was last generated in 2016.** Its newest entries are Ubuntu 16.04,
129
+ CentOS 7, Debian 8, and Fedora 25. A modern platform name is not in it:
130
+
131
+ | Platform name | Resolves? |
132
+ | --- | --- |
133
+ | `ubuntu-16.04`, `centos-7`, `debian-8` | yes, to a 2016-era image |
134
+ | `ubuntu` (bare distro name) | yes, to Ubuntu 16.04 |
135
+ | `ubuntu-22.04`, `ubuntu-24.04`, `debian-12`, `rocky-9` | **no** |
136
+
137
+ When the platform name is not in the table, `image_id` has no default and
138
+ Test Kitchen fails validation. Set it explicitly.
139
+
140
+ ### Finding an image ID
141
+
142
+ Image IDs differ per region, so look them up in the region you build in:
143
+
144
+ ```sh
145
+ export RACKSPACE_USERNAME="myuser"
146
+ export RACKSPACE_API_KEY="myapikey"
147
+ export RACKSPACE_REGION="ord"
148
+
149
+ bundle exec ruby helpers/dump_image_list.rb
150
+ ```
151
+
152
+ That prints every image the account can see, with its ID and the platform names
153
+ it would answer to. Copy the ID you want into `image_id`.
154
+
155
+ Refreshing the bundled table is contributor work — see
156
+ [Maintaining the bundled data](CONTRIBUTING.md#maintaining-the-bundled-data).
157
+
158
+ ## Choosing a flavor
159
+
160
+ `flavor_id` selects CPU, memory, and disk. The default is `general1-2`.
161
+
162
+ ### General Purpose v1
163
+
164
+ The right class for almost every test workload:
165
+
166
+ | Flavor | RAM | vCPUs | Disk |
167
+ | --- | --- | --- | --- |
168
+ | `general1-1` | 1 GB | 1 | 20 GB |
169
+ | `general1-2` *(default)* | 2 GB | 2 | 40 GB |
170
+ | `general1-4` | 4 GB | 4 | 80 GB |
171
+ | `general1-8` | 8 GB | 8 | 160 GB |
172
+
173
+ ### Other current classes
174
+
175
+ | Class | Flavors | Shape |
176
+ | --- | --- | --- |
177
+ | I/O v1 | `io1-15`, `io1-30`, `io1-60`, `io1-90`, `io1-120` | 15–120 GB RAM, 4–32 vCPUs, 40 GB SSD |
178
+ | Compute v1 | `compute1-4`, `compute1-8`, `compute1-15`, `compute1-30`, `compute1-60` | CPU-weighted, no local data disk |
179
+ | Memory v1 | `memory1-15`, `memory1-30`, `memory1-60`, `memory1-120`, `memory1-240` | RAM-weighted, no local data disk |
180
+ | OnMetal | `onmetal-compute1`, `onmetal-io1`, `onmetal-memory1` | Single-tenant bare metal |
181
+
182
+ Compute v1 and Memory v1 flavors have no local data disk, so they need a Cloud
183
+ Block Storage volume to boot from. That is outside what this driver sets up.
184
+
185
+ ### Retired classes
186
+
187
+ Do not use these. Rackspace removed them from the Control Panel and has said
188
+ they will be discontinued; some are still visible in the API.
189
+
190
+ | Class | Flavors | Replaced by |
191
+ | --- | --- | --- |
192
+ | Standard | numeric IDs `2` through `8` | General Purpose v1 |
193
+ | Performance 1 | `performance1-1`, `performance1-2`, `performance1-4`, `performance1-8` | General Purpose v1 |
194
+ | Performance 2 | `performance2-15` … `performance2-120` | I/O v1 |
195
+
196
+ ### Listing what your account offers
197
+
198
+ Flavor availability varies by account and region:
199
+
200
+ ```sh
201
+ bundle exec ruby helpers/dump_flavor_list.rb
48
202
  ```
49
203
 
50
- By default, the driver will spawn a 1GB Performance server on the base image
51
- for your specified platform. Additional, optional overrides can be provided:
204
+ ## Configuration
205
+
206
+ All options below are set under the `driver:` key in `kitchen.yml`.
207
+
208
+ ### Credentials
209
+
210
+ | Option | Default | Description |
211
+ | --- | --- | --- |
212
+ | `rackspace_username` | `$RACKSPACE_USERNAME`, else `$OS_USERNAME` | Rackspace Cloud username. Required, from here or the environment. |
213
+ | `rackspace_api_key` | `$RACKSPACE_API_KEY`, else `$OS_PASSWORD` | Rackspace Cloud API key. Required, from here or the environment. |
214
+ | `rackspace_region` | `$RACKSPACE_REGION`, else `$OS_REGION_NAME`, else `"dfw"` | Region to build in, e.g. `dfw`, `ord`, `iad`, `lon`, `syd`, `hkg`. |
215
+ | `version` | `"v2"` | Rackspace Cloud Servers API version. |
216
+
217
+ ### Server
218
+
219
+ | Option | Default | Description |
220
+ | --- | --- | --- |
221
+ | `image_id` | *looked up from the platform name* | Image UUID to build from. The bundled lookup table is stale, so in practice set this yourself — see [Choosing an image](#choosing-an-image). |
222
+ | `flavor_id` | `"general1-2"` | Flavor, which determines CPU, memory, and disk — see [Choosing a flavor](#choosing-a-flavor). |
223
+ | `server_name` | *generated* | Name for the server. If unset, a unique name of at most 63 characters is generated from the base name, your username, the hostname, and a random string. |
224
+ | `user_data` | `nil` | Extra configuration data passed to the server at build time. |
225
+ | `config_drive` | `true` | Attach the read-only metadata config drive. |
226
+ | `no_passwd_lock` | `false` | Do not let the underlying fog library lock the root account. |
227
+
228
+ ### Networking
229
+
230
+ | Option | Default | Description |
231
+ | --- | --- | --- |
232
+ | `networks` | PublicNet and ServiceNet | Array of Rackspace network UUIDs to attach. |
233
+ | `servicenet` | `false` | Connect over the ServiceNet address rather than the public one. |
234
+ | `rackconnect_wait` | `false` | Wait for RackConnect to finish before continuing. Enable this if the account uses RackConnect. |
235
+ | `servicelevel_wait` | `false` | Wait for Managed Service Level automation to finish before continuing. |
236
+
237
+ ### SSH
238
+
239
+ | Option | Default | Description |
240
+ | --- | --- | --- |
241
+ | `public_key_path` | first key found in `~/.ssh` | Path to the SSH public key installed on the server. Searched in order: `id_rsa.pub`, `id_dsa.pub`, `identity.pub`, `id_ecdsa.pub`. |
242
+ | `username` | `"root"` | User to connect as. |
243
+ | `port` | `"22"` | SSH port. |
244
+
245
+ ### Waiting
246
+
247
+ | Option | Default | Description |
248
+ | --- | --- | --- |
249
+ | `wait_for` | `600` | Seconds to wait for the server to become available before timing out. |
250
+ | `no_ssh_tcp_check` | `false` | Skip the TCP check on the SSH port. Use when a firewall makes the check unreliable. |
251
+ | `no_ssh_tcp_check_sleep` | `120` | Seconds to sleep instead of checking, when `no_ssh_tcp_check` is enabled. |
252
+
253
+ ## Examples
254
+
255
+ ### A bigger instance in a specific region
52
256
 
53
257
  ```yaml
54
- image_id: [SERVER IMAGE ID]
55
- flavor_id: [SERVER FLAVOR ID]
56
- server_name: [A FRIENDLY SERVER NAME]
57
- public_key_path: [PATH TO YOUR PUBLIC SSH KEY]
58
- rackspace_region: [A VALID RACKSPACE DC/REGION]
59
- wait_for: [NUM OF SECONDS TO WAIT BEFORE TIMING OUT, DEFAULT 600]
60
- no_ssh_tcp_check: [DEFAULTS TO false, SKIPS TCP CHECK WHEN true]
61
- no_ssh_tcp_check_sleep: [NUM OF SECONDS TO SLEEP IF no_ssh_tcp_check IS SET]
62
- networks: [LIST OF RACKSPACE NETWORK UUIDS, DEFAULT PUBLICNET AND SERVICE NET]
63
- rackconnect_wait: ['true' IF USING RACKCONNECT TO WAIT FOR IT TO COMPLETE]
64
- servicelevel_wait: ['true' IF USING MANAGED SERVICE LEVEL AUTOMATION TO WAIT FOR IT TO COMPLETE]
65
- no_passwd_lock: ['true' IF FOG LIBRARY SHOULD NOT LOCK ROOT ACCOUNT]
66
- servicenet: ['true' IF USING THE SERVICENET IP ADDRESS TO CONNECT]
67
- config_drive: [DEFAULTS TO true, ENABLES READ-ONLY METADATA DRIVE]
68
- user_data: [EXTRA CONFIGURATION DATA FOR THE SERVER]
258
+ driver:
259
+ name: rackspace
260
+ rackspace_region: ord
261
+ image_id: 09de0a66-3156-48b4-90a5-1cf25a905207
262
+ flavor_id: general1-4
69
263
  ```
70
264
 
71
- You also have the option of providing some configs via environment variables:
265
+ Image IDs are per-region, so `image_id` and `rackspace_region` travel together.
266
+
267
+ ### Connecting over ServiceNet
72
268
 
73
- ```shell
74
- export RACKSPACE_USERNAME="user" # (or OS_USERNAME)
75
- export RACKSPACE_API_KEY="api_key" # (or OS_PASSWORD)
76
- export RACKSPACE_REGION="dfw" # (or OS_REGION_NAME)
269
+ ```yaml
270
+ driver:
271
+ name: rackspace
272
+ servicenet: true
273
+ networks:
274
+ - 11111111-1111-1111-1111-111111111111
77
275
  ```
78
276
 
79
- Some configs are also derived based on your .ssh directory, specifically the
80
- `public_key_path` setting is derived by searching for:
277
+ ### RackConnect accounts
278
+
279
+ Without this, the converge can start before RackConnect has finished wiring up
280
+ the server's networking.
281
+
282
+ ```yaml
283
+ driver:
284
+ name: rackspace
285
+ rackconnect_wait: true
286
+ servicelevel_wait: true
287
+ ```
81
288
 
82
- - `~/.ssh/id_rsa.pub`
83
- - `~/.ssh/id_dsa.pub`
84
- - `~/.ssh/identity.pub`
85
- - `~/.ssh/id_ecdsa.pub`
289
+ ### A specific SSH key
290
+
291
+ ```yaml
292
+ driver:
293
+ name: rackspace
294
+ public_key_path: ~/.ssh/kitchen_rackspace.pub
295
+
296
+ transport:
297
+ ssh_key: ~/.ssh/kitchen_rackspace
298
+ ```
299
+
300
+ ### Slow builds, or a firewall in the way
301
+
302
+ ```yaml
303
+ driver:
304
+ name: rackspace
305
+ wait_for: 1200
306
+ no_ssh_tcp_check: true
307
+ no_ssh_tcp_check_sleep: 180
308
+ ```
309
+
310
+ ## Using with Chef
311
+
312
+ This driver is not tied to Cinc. The examples above use Cinc Workstation and the `cinc_infra` provisioner, but the driver works exactly the same with [Chef Workstation](https://www.chef.io/downloads/tools/workstation) — run `kitchen` instead of `cinc kitchen`, and use `chef_infra` instead of `cinc_infra`:
313
+
314
+ ```yaml
315
+ provisioner:
316
+ name: chef_infra
317
+
318
+ verifier:
319
+ name: inspec
320
+ ```
321
+
322
+ No driver configuration changes are needed.
86
323
 
87
324
  ## Contributing
88
325
 
89
- 1. Fork it
90
- 2. `bundle install`
91
- 3. Create your feature branch (`git checkout -b my-new-feature`)
92
- 4. `bundle exec rake` must pass
93
- 5. Commit your changes (`git commit -am 'Add some feature'`)
94
- 6. Push to the branch (`git push origin my-new-feature`)
95
- 7. Create new Pull Request
326
+ This project has no active maintainers, so please read the status note at the
327
+ top before opening an issue. Pull requests are still welcome on
328
+ [GitHub](https://github.com/test-kitchen/kitchen-rackspace). See
329
+ [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and how to run the
330
+ tests.
331
+
332
+ The most useful contribution right now is a refresh of `data/images.json`,
333
+ which has not been regenerated since 2016 — see
334
+ [Maintaining the bundled data](CONTRIBUTING.md#maintaining-the-bundled-data).
335
+
336
+ ## Acknowledgements
337
+
338
+ Originally derived from [Fletcher Nichol](https://github.com/fnichol)'s work on the [EC2 driver](https://github.com/test-kitchen/kitchen-ec2).
339
+
340
+ ## License
341
+
342
+ Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details.
data/Rakefile CHANGED
@@ -9,7 +9,23 @@ RSpec::Core::RakeTask.new(:test, :tag) do |t, args|
9
9
  a << "--backtrace" if ENV["VERBOSE"] || ENV["DEBUG"]
10
10
  a << "--seed #{ENV["SEED"]}" if ENV["SEED"]
11
11
  a << "--tag #{args[:tag]}" if args[:tag]
12
- a << "--default-path test"
13
- a << "-I test/spec"
14
12
  end.join(" ")
15
13
  end
14
+
15
+ begin
16
+ require "yard"
17
+
18
+ # Options and the file list live in .yardopts so that a bare `yard` from the
19
+ # command line produces exactly what `rake doc` does.
20
+ YARD::Rake::YardocTask.new(:doc)
21
+
22
+ desc "List anything in lib/ that is still undocumented"
23
+ task :doc_coverage do
24
+ sh "yard stats --list-undoc"
25
+ end
26
+ rescue LoadError
27
+ desc "Generate YARD documentation (not installed)"
28
+ task :doc do
29
+ abort "YARD is not installed. Run: bundle install"
30
+ end
31
+ end
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Lists the flavors a Rackspace account can build, for setting `flavor_id`.
5
+ #
6
+ # Flavor availability varies by account and region, and Rackspace retires
7
+ # flavor classes over time, so this is the authoritative answer for a given
8
+ # account rather than the tables in the README.
9
+ #
10
+ # Usage:
11
+ #
12
+ # export RACKSPACE_USERNAME=myuser
13
+ # export RACKSPACE_API_KEY=myapikey
14
+ # export RACKSPACE_REGION=ord # optional, defaults to dfw
15
+ #
16
+ # bundle exec ruby helpers/dump_flavor_list.rb
17
+ #
18
+ # Run it under Bundler. fog-rackspace only loads against the fog-core version
19
+ # this gem pins; on a newer fog-core, `require "fog/rackspace"` raises NameError
20
+ # on its "CDN v2" service name.
21
+
22
+ require "fog/rackspace"
23
+
24
+ compute = Fog::Compute.new(
25
+ provider: "Rackspace",
26
+ rackspace_username: ENV.fetch("RACKSPACE_USERNAME"),
27
+ rackspace_api_key: ENV.fetch("RACKSPACE_API_KEY"),
28
+ rackspace_region: ENV.fetch("RACKSPACE_REGION", "dfw"),
29
+ version: :v2
30
+ )
31
+
32
+ flavors = compute.flavors.to_a
33
+ abort "No flavors visible to this account." if flavors.empty?
34
+
35
+ rows = flavors
36
+ .sort_by { |f| [f.ram.to_i, f.id.to_s] }
37
+ .map { |f| [f.id.to_s, "#{f.ram} MB", "#{f.vcpus} vCPU", "#{f.disk} GB"] }
38
+
39
+ header = %w{FLAVOR RAM VCPUS DISK}
40
+ widths = header.each_index.map do |i|
41
+ ([header[i]] + rows.map { |r| r[i] }).map(&:length).max
42
+ end
43
+
44
+ puts header.each_with_index.map { |h, i| h.ljust(widths[i]) }.join(" ")
45
+ puts widths.map { |w| "-" * w }.join(" ")
46
+ rows.each { |row| puts row.each_with_index.map { |c, i| c.ljust(widths[i]) }.join(" ") }
@@ -1,86 +1,117 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Regenerates data/images.json from the images a Rackspace account can see.
5
+ #
6
+ # The driver maps a Test Kitchen platform name (`ubuntu-22.04`) to a Rackspace
7
+ # image ID. That mapping lives in data/images.json and has to be refreshed
8
+ # whenever Rackspace publishes new base images.
9
+ #
10
+ # Usage:
11
+ #
12
+ # export RACKSPACE_USERNAME=myuser
13
+ # export RACKSPACE_API_KEY=myapikey
14
+ # export RACKSPACE_REGION=ord # optional, defaults to dfw
15
+ #
16
+ # bundle exec ruby helpers/dump_image_list.rb # human-readable table
17
+ # bundle exec ruby helpers/dump_image_list.rb --json # data/images.json content
18
+ #
19
+ # Run it under Bundler. fog-rackspace only loads against the fog-core version
20
+ # this gem pins; on a newer fog-core, `require "fog/rackspace"` raises NameError
21
+ # on its "CDN v2" service name.
22
+ #
23
+ # Image IDs are per-region, so regenerate from the region most users build in.
24
+
1
25
  require "fog/rackspace"
2
26
  require "json" unless defined?(JSON)
3
27
 
4
- def whole?(x) # rubocop:disable Naming/UncommunicativeMethodParamName
5
- (x - x.floor) < 1e-6
6
- end
7
-
8
- i_care_about = {
9
- "Arch Linux (PVHVM)" => %w{arch arch-2016 arch-2016.8},
10
- "CentOS 7 (PVHVM)" => %w{centos centos-7},
11
- "CentOS 6 (PVHVM)" => %w{centos-6},
12
- "CoreOS (Stable)" => %w{coreos coreos-stable},
13
- "CoreOS (Beta)" => %w{coreos-beta},
14
- "CoreOS (Alpha)" => %w{coreos-alpha},
15
- "Debian 8 (Jessie) (PVHVM)" => %w{debian debian-8},
16
- "Debian 7 (Wheezy) (PVHVM)" => %w{debian-7},
17
- "Debian Testing (Stretch) (PVHVM)" => %w{debian-testing},
18
- "Debian Unstable (Sid) (PVHVM)" => %w{debian-unstable},
19
- "Fedora 25 (PVHVM)" => %w{fedora fedora-25},
20
- "Fedora 24 (PVHVM)" => %w{fedora-24},
21
- "FreeBSD 11 (PVHVM)" => %w{freebsd freebsd-11},
22
- "FreeBSD 10 (PVHVM)" => %w{freebsd-10},
23
- "Gentoo 15.3 (PVHVM)" => %w{gentoo gentoo-15 gentoo-15.3},
24
- "OpenSUSE Leap 42 (PVHVM)" => %w{opensuse opensuse-42},
25
- "Scientific Linux 7 (PVHVM)" => %w{scientific scientific-7},
26
- "Scientific Linux 6 (PVHVM)" => %w{scientific-6},
27
- "Ubuntu 16.04 LTS (Xenial Xerus) (PVHVM)" => %w{ubuntu ubuntu-16 ubuntu-16.04},
28
- "Ubuntu 14.04 LTS (Trusty Tahr) (PVHVM)" => %w{ubuntu-14 ubuntu-14.04},
29
- "Ubuntu 12.04 LTS (Precise Pangolin) (PVHVM)" => %w{ubuntu-12 ubuntu-12.04},
30
- "Vyatta Network OS 6.7R12" => %w{vyatta vyatta-6 vyatta-6.7 vyatta-6.7R12},
31
- "Windows Server 2012 R2" => %w{windows windows-2012 windows-2012R2},
32
- "Windows Server 2008 R2 SP1" => %w{windows-2008
33
- windows-2008R2
34
- windows-2008R2SP1},
35
- }
36
-
37
- names_to_clean = {
28
+ # Rackspace reports the distro in OpenStack metadata using reverse-DNS names.
29
+ # Map the ones that do not reduce to a sensible short name on their own.
30
+ DISTRO_NAMES = {
38
31
  "com.microsoft.server" => "windows",
39
- "org.fedoraproject" => "fedora",
40
32
  "org.archlinux" => "arch",
33
+ "org.fedoraproject" => "fedora",
41
34
  "org.scientificlinux" => "scientific",
42
- }
35
+ }.freeze
43
36
 
44
- compute = Fog::Compute.new(provider: "Rackspace",
45
- rackspace_username: ENV["RACKSPACE_USERNAME"],
46
- rackspace_api_key: ENV["RACKSPACE_API_KEY"],
47
- rackspace_region: "ORD")
37
+ # Builds the platform aliases for one image.
38
+ #
39
+ # An image advertising Ubuntu 22.04 yields "ubuntu-22.04", plus "ubuntu-22" as
40
+ # a convenience. The bare distro name ("ubuntu") is assigned separately, to the
41
+ # newest version found, so that it does not depend on iteration order.
42
+ #
43
+ # @param image [Fog::Compute::RackspaceV2::Image] the image to describe
44
+ # @return [Array(String, String, Array<String>), nil] distro, version, and
45
+ # aliases, or nil when the image carries no usable OpenStack metadata
46
+ def describe(image)
47
+ meta = image.metadata
48
+ distro_id = meta["org.openstack__1__os_distro"]
49
+ version = meta["org.openstack__1__os_version"]
50
+ return nil if distro_id.nil? || version.nil?
48
51
 
49
- aliases = i_care_about.values.flatten
50
- res = aliases.each_with_object({}) do |a, hsh|
51
- raise "Alias '#{a}' was listed twice" if hsh.include?(a)
52
+ distro = DISTRO_NAMES.fetch(distro_id) { distro_id.split(".").last }
52
53
 
53
- hsh[a] = nil
54
- hsh
54
+ aliases = ["#{distro}-#{version}"]
55
+ # "ubuntu-22" alongside "ubuntu-22.04", but not "centos-7" twice.
56
+ major = version.split(".").first
57
+ aliases << "#{distro}-#{major}" if major != version
58
+
59
+ [distro, version, aliases]
60
+ end
61
+
62
+ # Sorts version strings numerically, so that 22.04 beats 8 and 9 beats 10 is
63
+ # never asserted. Non-numeric segments (Vyatta's "6.7R12") sort last.
64
+ #
65
+ # @param version [String] an OS version
66
+ # @return [Array<Integer>] a comparable key
67
+ def version_key(version)
68
+ version.split(".").map(&:to_i)
55
69
  end
56
70
 
57
- compute.images.select { |i| i_care_about.key?(i.name) }.each do |img|
58
- image_metadata = img.metadata
71
+ compute = Fog::Compute.new(
72
+ provider: "Rackspace",
73
+ rackspace_username: ENV.fetch("RACKSPACE_USERNAME"),
74
+ rackspace_api_key: ENV.fetch("RACKSPACE_API_KEY"),
75
+ rackspace_region: ENV.fetch("RACKSPACE_REGION", "dfw"),
76
+ version: :v2
77
+ )
59
78
 
60
- if image_metadata["org.openstack__1__os_distro"] &&
61
- image_metadata["org.openstack__1__os_version"]
79
+ images = compute.images.to_a
80
+ abort "No images visible to this account." if images.empty?
62
81
 
63
- distro_id = image_metadata["org.openstack__1__os_distro"]
64
- version = image_metadata["org.openstack__1__os_version"]
82
+ if ARGV.include?("--json")
83
+ mapping = {}
84
+ newest = {}
65
85
 
66
- distro = if names_to_clean.include?(distro_id)
67
- names_to_clean[distro_id]
68
- else
69
- distro_id.split(".").last
70
- end
86
+ images.each do |image|
87
+ described = describe(image)
88
+ next if described.nil?
71
89
 
72
- res["#{distro}-#{version}"] = img.id
90
+ distro, version, aliases = described
91
+ aliases.each { |a| mapping[a] = image.id }
73
92
 
74
- # if it's a whole number non-zero version, also add
75
- # a dot-zero (centos-7 vs centos-7.0)
76
- res["#{distro}-#{version}.0"] = img.id if version != "0" && version =~ /^\s*\d+\s*$/
93
+ current = newest[distro]
94
+ if current.nil? || (version_key(version) <=> version_key(current[:version])) == 1
95
+ newest[distro] = { version:, id: image.id }
96
+ end
77
97
  end
78
98
 
79
- i_care_about[img.name].each { |a| res[a] = img.id }
80
- i_care_about.delete(img.name)
81
- end
99
+ # The bare distro name points at the newest version of that distro.
100
+ newest.each { |distro, info| mapping[distro] = info[:id] }
101
+
102
+ puts JSON.pretty_generate(mapping.sort.to_h)
103
+ else
104
+ rows = images.map do |image|
105
+ described = describe(image)
106
+ [image.id, image.name, described ? described[2].join(", ") : "-"]
107
+ end.sort_by { |row| row[1] }
82
108
 
83
- raise "Couldn't find some images we expected: #{i_care_about.keys}" unless i_care_about.empty?
109
+ widths = [0, 1, 2].map { |i| rows.map { |r| r[i].length }.max }
110
+ header = ["IMAGE ID", "NAME", "PLATFORM NAMES"]
111
+ widths = widths.each_with_index.map { |w, i| [w, header[i].length].max }
84
112
 
85
- # sort these to make them pretty
86
- puts JSON.pretty_generate(res.sort.to_h)
113
+ puts header.each_with_index.map { |h, i| h.ljust(widths[i]) }.join(" ")
114
+ puts widths.map { |w| "-" * w }.join(" ")
115
+ rows.each { |row| puts row.each_with_index.map { |c, i| c.ljust(widths[i]) }.join(" ") }
116
+ warn "\n#{rows.count} images. Re-run with --json to regenerate data/images.json."
117
+ end