brightbox-cli 4.3.2 → 4.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c813c009698fa955e24ce5399a95d658f8e0f657881071fcea65a3349d958676
4
- data.tar.gz: b7d1cfd6faf802ea27599226aedf2632b7d33dff89b4a018b26bd4587cc5069a
3
+ metadata.gz: 20cc19a7e6458988359a604fa90d67570b83c124b40c265a5f9beb0d3bc3a199
4
+ data.tar.gz: 98072a6ccd8ab0dcf4a3e3ac7f27ecf6982a7f3cf275b86e979aae0fc12461d8
5
5
  SHA512:
6
- metadata.gz: ccd5f260951570479dacfbd428abb919a5ab35447d2699f05ff8552ba1c2fb0d3b27dfc9a749a3b0c9b0e4522d5a21b4bf13cecf5c13560798505548a10a9e4d
7
- data.tar.gz: '051800a849a0b1f62b6351866e3daa687016b4cb2b6eb62aa0dc7c057fb73896c6bc23a30dc7e0c01b5a9b5fc92c25a7a723daa0245e16cb733c4d40b10de03d'
6
+ metadata.gz: 1c949f6d0679c9d72b7c7f61a6a0fd68b0669452911dde7da845a6f7e9a388c5b2a253ab0219bf6228faf4bc51ac6accfc559d714425c18499525d47b84ab73e
7
+ data.tar.gz: 7975e2d18399cc35bcd49f474657f6766e5d694bdbf70a1d49b79288edcbc9c07ea1d54b0757bd992ef08c517d4eef0e2a684e9ebf102f4718f3c72bc31368f9
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ### v4.4.0 / 2023-01-26
2
+
3
+ [Full Changelog](https://github.com/brightbox/brightbox-cli/compare/v4.3.2...v4.4.0)
4
+
5
+ Enhancements:
6
+
7
+ * Added `configmaps` subcommand for manage config management.
8
+
1
9
  ### v4.3.2 / 2023-01-12
2
10
 
3
11
  [Full Changelog](https://github.com/brightbox/brightbox-cli/compare/v4.3.1...v4.3.2)
data/Gemfile.lock CHANGED
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- brightbox-cli (4.3.2)
4
+ brightbox-cli (4.4.0)
5
5
  dry-inflector (= 0.2.0)
6
- fog-brightbox (>= 1.9.1)
6
+ fog-brightbox (>= 1.10.0)
7
7
  fog-core (< 2.0)
8
8
  gli (~> 2.21)
9
9
  highline (~> 2.0)
@@ -20,13 +20,13 @@ GEM
20
20
  ast (2.4.2)
21
21
  builder (3.2.4)
22
22
  coderay (1.1.3)
23
- concurrent-ruby (1.1.10)
23
+ concurrent-ruby (1.2.0)
24
24
  crack (0.4.5)
25
25
  rexml
26
26
  diff-lcs (1.5.0)
27
27
  dry-inflector (0.2.0)
28
- excon (0.97.1)
29
- fog-brightbox (1.9.1)
28
+ excon (0.97.2)
29
+ fog-brightbox (1.10.0)
30
30
  dry-inflector
31
31
  fog-core (>= 1.45, < 3.0)
32
32
  fog-json
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
22
22
  s.require_paths = ["lib"]
23
23
 
24
- s.add_dependency "fog-brightbox", ">= 1.9.1"
24
+ s.add_dependency "fog-brightbox", ">= 1.10.0"
25
25
  s.add_dependency "fog-core", "< 2.0"
26
26
  s.add_dependency "gli", "~> 2.21"
27
27
  s.add_dependency "highline", "~> 2.0"
@@ -0,0 +1,183 @@
1
+ module Brightbox
2
+ desc I18n.t("configmaps.desc")
3
+ command [:configmaps] do |cmd|
4
+ cmd.default_command :list
5
+
6
+ cmd.desc I18n.t("configmaps.create.desc")
7
+ cmd.arg_name I18n.t("configmaps.args.one")
8
+ cmd.command [:create] do |c|
9
+ c.desc I18n.t("options.name.desc")
10
+ c.flag %i[n name]
11
+
12
+ c.desc I18n.t("configmaps.options.data_string")
13
+ c.flag %i[d data]
14
+
15
+ c.desc I18n.t("configmaps.options.data_file")
16
+ c.flag [:"data-file"]
17
+
18
+ c.action do |global_options, options, _args|
19
+ map_data = parse_configmap_data_options(options)
20
+
21
+ raise I18n.t("configmaps.create.data_required") if map_data.nil?
22
+
23
+ # Attempt to parse data as JSON but do not update
24
+ begin
25
+ JSON.parse(map_data)
26
+ rescue StandardError
27
+ raise I18n.t("configmaps.options.bad_data")
28
+ end
29
+
30
+ params = {
31
+ data: map_data
32
+ }
33
+ params[:name] = options[:name] if options[:name]
34
+
35
+ info I18n.t("configmaps.create.acting")
36
+ config_map = ConfigMap.create(params)
37
+
38
+ render_table([config_map], global_options)
39
+ end
40
+ end
41
+
42
+ cmd.desc I18n.t("configmaps.destroy.desc")
43
+ cmd.arg_name I18n.t("configmaps.args.many")
44
+ cmd.command [:destroy] do |c|
45
+ c.action do |_global_options, _options, args|
46
+ raise I18n.t("configmaps.args.specify_many_ids") if args.empty?
47
+
48
+ config_maps = ConfigMap.find_or_call(args) do |id|
49
+ raise I18n.t("configmaps.args.unknown_id", config_map: id)
50
+ end
51
+
52
+ config_maps.each do |config_map|
53
+ info I18n.t("configmaps.destroy.acting", config_map: config_map)
54
+
55
+ begin
56
+ config_map.destroy
57
+ rescue Brightbox::Api::Conflict
58
+ error I18n.t("configmaps.destroy.failed", config_map: id)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ cmd.desc I18n.t("configmaps.list.desc")
65
+ cmd.arg_name I18n.t("configmaps.args.optional")
66
+ cmd.command [:list] do |c|
67
+ c.action do |global_options, _options, args|
68
+ config_maps = ConfigMap.find_all_or_warn(args)
69
+
70
+ render_table(config_maps, global_options)
71
+ end
72
+ end
73
+
74
+ cmd.desc I18n.t("configmaps.show.desc")
75
+ cmd.arg_name I18n.t("configmaps.args.optional")
76
+ cmd.command [:show] do |c|
77
+ c.desc I18n.t("configmaps.options.data_output")
78
+ c.switch [:data], negatable: false, default: false
79
+
80
+ c.desc I18n.t("configmaps.options.data_format")
81
+ c.flag [:format]
82
+
83
+ c.action do |global_options, options, args|
84
+ if options[:data]
85
+ unless args.length == 1
86
+ raise I18n.t("configmaps.options.data_args")
87
+ end
88
+
89
+ cfg_id = args[0]
90
+
91
+ if cfg_id.nil? || !cfg_id.start_with?("cfg-")
92
+ raise I18n.t("configmaps.args.specify_one_id_first")
93
+ end
94
+
95
+ config_map = ConfigMap.find(cfg_id)
96
+ data(config_map.format_data(options[:format] || "json"))
97
+ else
98
+ if options[:format] && !options[:data]
99
+ raise I18n.t("configmaps.options.format_no_data")
100
+ end
101
+
102
+ config_maps = ConfigMap.find_all_or_warn(args)
103
+
104
+ table_opts = global_options.merge(
105
+ vertical: true
106
+ )
107
+ render_table(config_maps, table_opts)
108
+ end
109
+ end
110
+ end
111
+
112
+ cmd.desc I18n.t("configmaps.update.desc")
113
+ cmd.arg_name I18n.t("configmaps.args.one")
114
+ cmd.command [:update] do |c|
115
+ c.desc I18n.t("options.name.desc")
116
+ c.flag %i[n name]
117
+
118
+ c.desc I18n.t("configmaps.options.data_string")
119
+ c.flag %i[d data]
120
+
121
+ c.desc I18n.t("configmaps.options.data_file")
122
+ c.flag [:"data-file"]
123
+
124
+ c.action do |global_options, options, args|
125
+ cfg_id = args[0]
126
+
127
+ if cfg_id.nil? || !cfg_id.start_with?("cfg-")
128
+ raise I18n.t("configmaps.args.specify_one_id_first")
129
+ end
130
+
131
+ params = {}
132
+ params[:name] = options[:name] if options[:name]
133
+
134
+ map_data = parse_configmap_data_options(options)
135
+
136
+ if map_data
137
+ begin
138
+ map_data = JSON.parse(map_data)
139
+ rescue StandardError
140
+ raise I18n.t("configmaps.options.bad_data")
141
+ end
142
+
143
+ params[:data] = map_data
144
+ end
145
+
146
+ config_map = ConfigMap.find(cfg_id)
147
+
148
+ unless params.empty?
149
+ info I18n.t("configmaps.update.acting", config_map: config_map)
150
+ config_map.update(params)
151
+ end
152
+
153
+ render_table([config_map], global_options)
154
+ end
155
+ end
156
+ end
157
+
158
+ def parse_configmap_data_options(options)
159
+ if options[:data] && options[:"data-file"]
160
+ raise I18n.t("configmaps.options.multiple_data")
161
+ end
162
+
163
+ map_data = options[:data]
164
+ data_filename = options[:"data-file"]
165
+
166
+ if data_filename
167
+ file_handler = lambda do |file|
168
+ map_data = file.read
169
+ end
170
+
171
+ if data_filename == "-"
172
+ file_handler[$stdin]
173
+ else
174
+ File.open(data_filename, "r", &file_handler)
175
+ end
176
+
177
+ raise map_data.inspect if map_data.nil? || map_data == ""
178
+ end
179
+
180
+ map_data
181
+ end
182
+ module_function :parse_configmap_data_options
183
+ end
@@ -0,0 +1,53 @@
1
+ module Brightbox
2
+ class ConfigMap < Api
3
+ def self.require_account?; true; end
4
+
5
+ def self.all
6
+ conn.config_maps
7
+ end
8
+
9
+ def self.create(options)
10
+ new(conn.config_maps.create(options))
11
+ end
12
+
13
+ def self.get(id)
14
+ conn.config_maps.get(id)
15
+ end
16
+
17
+ def self.default_field_order
18
+ %i[id name]
19
+ end
20
+
21
+ def self.detailed_fields
22
+ %i[
23
+ id
24
+ name
25
+ ]
26
+ end
27
+
28
+ def format_data(format)
29
+ case format.to_sym
30
+ when :text
31
+ attributes[:data].map do |key, value|
32
+ "#{key.to_s.rjust(16)}: #{value}"
33
+ end.join("\n")
34
+ else
35
+ JSON.dump(data)
36
+ end
37
+ end
38
+
39
+ def to_row
40
+ {
41
+ id: attributes[:id],
42
+ name: attributes[:name],
43
+ data: attributes[:data]
44
+ }
45
+ end
46
+
47
+ def update(options)
48
+ self.class.conn.update_config_map(id, options)
49
+ reload
50
+ self
51
+ end
52
+ end
53
+ end
@@ -1,3 +1,3 @@
1
1
  module Brightbox
2
- VERSION = "4.3.2".freeze unless defined?(Brightbox::VERSION)
2
+ VERSION = "4.4.0".freeze unless defined?(Brightbox::VERSION)
3
3
  end
data/lib/brightbox_cli.rb CHANGED
@@ -53,6 +53,7 @@ module Brightbox
53
53
  autoload :FirewallRule, File.expand_path("brightbox-cli/firewall_rule", __dir__)
54
54
  autoload :FirewallRules, File.expand_path("brightbox-cli/firewall_rules", __dir__)
55
55
  autoload :Collaboration, File.expand_path("brightbox-cli/collaboration", __dir__)
56
+ autoload :ConfigMap, File.expand_path("brightbox-cli/config_map", __dir__)
56
57
  autoload :UserCollaboration, File.expand_path("brightbox-cli/user_collaboration", __dir__)
57
58
  autoload :DatabaseType, File.expand_path("brightbox-cli/database_type", __dir__)
58
59
  autoload :DatabaseServer, File.expand_path("brightbox-cli/database_server", __dir__)
data/locales/en.yml CHANGED
@@ -51,6 +51,39 @@ en:
51
51
  desc: Remove an API client from config
52
52
  user_add:
53
53
  desc: Add new user credentials to config
54
+ configmaps:
55
+ desc: Manage config maps
56
+ args:
57
+ one: <configmap>
58
+ optional: "[<configmap>...]"
59
+ many: <configmap>...
60
+ specify_one_id_first: You must specify the config map ID as the first argument
61
+ specify_many_ids: You must specify config map IDs as arguments
62
+ unknown_id: Couldn't find %{config_map}
63
+ options:
64
+ data_args: You can only access data for a single config map at a time
65
+ data_file: A path to a file contain a JSON object containing key/values, or '-' for STDIO
66
+ data_string: String representing a valid JSON object
67
+ data_output: Return the config map data key/values
68
+ data_format: "Format to return 'data' in: 'json' (default) or 'text'"
69
+ format_no_data: The 'format' option can only be used with 'data'
70
+ bad_data: Config map data was not valid JSON
71
+ multiple_data: Config map data can only be passed by either 'data' or 'data-file'
72
+ create:
73
+ desc: Create a config map
74
+ acting: Creating config map
75
+ data_required: Config map data is required as 'data' option
76
+ destroy:
77
+ desc: Destroy config maps
78
+ acting: Destroying %{config_map}
79
+ failed: Failed to destroy %{config_map}
80
+ list:
81
+ desc: List config maps
82
+ show:
83
+ desc: Show config maps
84
+ update:
85
+ desc: Update a config map
86
+ acting: Updating %{config_map}
54
87
  firewall:
55
88
  policies:
56
89
  desc: Manage firewall policies
@@ -0,0 +1,257 @@
1
+ require "spec_helper"
2
+
3
+ describe "brightbox configmaps create" do
4
+ let(:output) { FauxIO.new { Brightbox.run(argv) } }
5
+ let(:stdout) { output.stdout }
6
+ let(:stderr) { output.stderr }
7
+
8
+ let(:token) { SecureRandom.hex }
9
+
10
+ before do
11
+ config_from_contents(API_CLIENT_CONFIG_CONTENTS)
12
+
13
+ stub_request(:post, "http://api.brightbox.localhost/token")
14
+ .to_return(status: 200, body: JSON.dump(access_token: token))
15
+
16
+ Brightbox.config.reauthenticate
17
+ end
18
+
19
+ context "without options" do
20
+ let(:argv) { %w[configmaps create] }
21
+
22
+ it "does not error" do
23
+ expect { output }.to_not raise_error
24
+
25
+ expect(stderr).to match("Config map data is required as 'data' option")
26
+
27
+ expect(stdout).to eq("")
28
+ end
29
+ end
30
+
31
+ context "with 'name' and 'data'" do
32
+ let(:argv) { ["configmaps", "create", "--name", "tester", "--data", payload] }
33
+ let(:payload) { { key: "value" }.to_json }
34
+
35
+ before do
36
+ stub_request(:post, "http://api.brightbox.localhost/1.0/config_maps")
37
+ .with(headers: { "Content-Type" => "application/json" },
38
+ query: hash_including(account_id: "acc-12345"),
39
+ body: {
40
+ name: "tester",
41
+ data: {
42
+ key: "value"
43
+ }
44
+ })
45
+ .to_return(
46
+ status: 200,
47
+ body: {
48
+ id: "cfg-lk342",
49
+ name: "tester",
50
+ data: {
51
+ key: "value"
52
+ }
53
+ }.to_json
54
+ )
55
+ end
56
+
57
+ it "does not error" do
58
+ expect { output }.to_not raise_error
59
+
60
+ expect(stderr).not_to match("ERROR")
61
+
62
+ aggregate_failures do
63
+ expect(stdout).to match("id.*name")
64
+
65
+ expect(stdout).to match("cfg-lk342")
66
+ end
67
+ end
68
+ end
69
+
70
+ context "with 'data'" do
71
+ let(:argv) { ["configmaps", "create", "--data", payload] }
72
+ let(:payload) { { key: "value" }.to_json }
73
+
74
+ before do
75
+ stub_request(:post, "http://api.brightbox.localhost/1.0/config_maps")
76
+ .with(headers: { "Content-Type" => "application/json" },
77
+ query: hash_including(account_id: "acc-12345"),
78
+ body: {
79
+ data: {
80
+ key: "value"
81
+ }
82
+ }.to_json)
83
+ .to_return(
84
+ status: 200,
85
+ body: {
86
+ id: "cfg-lk342",
87
+ data: {
88
+ key: "value"
89
+ }
90
+ }.to_json
91
+ )
92
+ end
93
+
94
+ it "does not error" do
95
+ expect { output }.to_not raise_error
96
+
97
+ expect(stderr).not_to match("ERROR")
98
+
99
+ aggregate_failures do
100
+ expect(stdout).to match("id.*name")
101
+
102
+ expect(stdout).to match("cfg-lk342")
103
+ end
104
+ end
105
+
106
+ context "with invalid update" do
107
+ let(:payload) { "Not JSON!" }
108
+
109
+ it "does not error" do
110
+ expect { output }.to_not raise_error
111
+
112
+ expect(stderr).to eq("ERROR: Config map data was not valid JSON\n")
113
+
114
+ expect(stdout).to eq("")
115
+ end
116
+ end
117
+ end
118
+
119
+ context "with new map from 'data-file'" do
120
+ context "when filename is used" do
121
+ let(:argv) { ["configmaps", "create", "--data-file", data_file.path] }
122
+ let(:data_file) { Tempfile.open("config_map_test_data") }
123
+
124
+ around do |example|
125
+ data_file.write(payload)
126
+ data_file.close
127
+
128
+ example.run
129
+
130
+ data_file.unlink
131
+ end
132
+
133
+ context "with valid update" do
134
+ let(:payload) { { new_key: "new setting" }.to_json }
135
+
136
+ before do
137
+ stub_request(:post, "http://api.brightbox.localhost/1.0/config_maps")
138
+ .with(query: hash_including(account_id: "acc-12345"),
139
+ body: {
140
+ data: {
141
+ new_key: "new setting"
142
+ }
143
+ })
144
+ .to_return(
145
+ status: 200,
146
+ body: {
147
+ id: "cfg-s432l",
148
+ name: "",
149
+ data: {
150
+ new_key: "new setting"
151
+ }
152
+ }.to_json
153
+ )
154
+ end
155
+
156
+ it "does not error" do
157
+ expect { output }.to_not raise_error
158
+
159
+ expect(stderr).to eq("Creating config map\n")
160
+
161
+ aggregate_failures do
162
+ expect(stdout).to match("cfg-s432l")
163
+ end
164
+ end
165
+
166
+ context "with invalid update" do
167
+ let(:payload) { "Not JSON!" }
168
+
169
+ it "does not error" do
170
+ expect { output }.to_not raise_error
171
+
172
+ expect(stderr).to eq("ERROR: Config map data was not valid JSON\n")
173
+
174
+ expect(stdout).to eq("")
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ context "when '-' is used for STDIN" do
182
+ let(:argv) { ["configmaps", "create", "--data-file", "-", "cfg-stdin"] }
183
+
184
+ before do
185
+ stdin_data = StringIO.new
186
+ stdin_data.puts(payload)
187
+ stdin_data.rewind
188
+
189
+ $stdin = stdin_data
190
+ end
191
+
192
+ after do
193
+ $stdin = STDIN
194
+ end
195
+
196
+ context "with valid update" do
197
+ let(:payload) { { use_stdin: true }.to_json }
198
+
199
+ before do
200
+ stub_request(:post, "http://api.brightbox.localhost/1.0/config_maps")
201
+ .with(query: hash_including(account_id: "acc-12345"),
202
+ body: {
203
+ data: {
204
+ use_stdin: true
205
+ }
206
+ })
207
+ .to_return(
208
+ status: 200,
209
+ body: {
210
+ id: "cfg-mj53s",
211
+ name: "",
212
+ data: {
213
+ use_stdin: true
214
+ }
215
+ }.to_json
216
+ )
217
+ end
218
+
219
+ it "does not error" do
220
+ expect { output }.to_not raise_error
221
+
222
+ expect(stderr).to eq("Creating config map\n")
223
+
224
+ aggregate_failures do
225
+ expect(stdout).to match("cfg-mj53s")
226
+ end
227
+ end
228
+
229
+ context "with invalid update" do
230
+ let(:payload) { "Not JSON!" }
231
+
232
+ it "does not error" do
233
+ expect { output }.to_not raise_error
234
+
235
+ expect(stderr).to eq("ERROR: Config map data was not valid JSON\n")
236
+
237
+ expect(stdout).to eq("")
238
+ end
239
+ end
240
+ end
241
+ end
242
+
243
+ context "with mutually exclusive data options" do
244
+ let(:argv) { ["configmaps", "create", "--data", payload, "--data-file", "-"] }
245
+ let(:payload) { { new_key: "new value" }.to_json }
246
+
247
+ context "with invalid update" do
248
+ it "does not error" do
249
+ expect { output }.to_not raise_error
250
+
251
+ expect(stderr).to eq("ERROR: Config map data can only be passed by either 'data' or 'data-file'\n")
252
+
253
+ expect(stdout).to eq("")
254
+ end
255
+ end
256
+ end
257
+ end