cloudstack_client 1.6.0 → 2.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 +4 -4
- data/.github/dependabot.yml +23 -0
- data/.github/workflows/ci.yml +29 -4
- data/.github/workflows/release.yml +9 -11
- data/.gitignore +4 -0
- data/.rubocop.yml +33 -0
- data/.rubocop_todo.yml +572 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +5 -0
- data/README.md +105 -29
- data/Rakefile +17 -2
- data/cloudstack_client.gemspec +23 -4
- data/lib/cloudstack_client/api.rb +39 -14
- data/lib/cloudstack_client/cli.rb +1 -1
- data/lib/cloudstack_client/client.rb +4 -1
- data/lib/cloudstack_client/configuration.rb +14 -2
- data/lib/cloudstack_client/connection.rb +12 -56
- data/lib/cloudstack_client/error.rb +1 -0
- data/lib/cloudstack_client/request_handling.rb +91 -0
- data/lib/cloudstack_client/version.rb +1 -1
- data/test/api_test.rb +23 -1
- data/test/client_test.rb +147 -13
- data/test/configuration_test.rb +84 -0
- data/test/connection_test.rb +364 -0
- data/test/data/cloudstack-empty.yml +3 -0
- data/test/data/cloudstack-incomplete.yml +5 -0
- data/test/data/cloudstack-malformed.yml +5 -0
- data/test/data/cloudstack-unsafe.yml +1 -0
- data/test/support/api_stubs.rb +85 -0
- data/test/test_helper.rb +38 -1
- data/test/utils_test.rb +62 -0
- metadata +130 -23
- data/.travis.yml +0 -8
- data/Gemfile.lock +0 -27
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
# Helpers for stubbing CloudStack HTTP responses.
|
|
4
|
+
#
|
|
5
|
+
# Every CloudStack request carries a generated `signature` query parameter, so
|
|
6
|
+
# stubs match on the `command` parameter with `hash_including` rather than on a
|
|
7
|
+
# full URL.
|
|
8
|
+
module ApiStubs
|
|
9
|
+
API_ENDPOINT = "https://cloudstack.test/client/api".freeze
|
|
10
|
+
|
|
11
|
+
# WebMock matches a bare URL only against requests with no query string, and
|
|
12
|
+
# every CloudStack request carries one. Match on the endpoint prefix instead.
|
|
13
|
+
ANY_REQUEST = %r{\Ahttps://cloudstack\.test/client/api}.freeze
|
|
14
|
+
|
|
15
|
+
# Stub a CloudStack command with an already-shaped response body.
|
|
16
|
+
#
|
|
17
|
+
# stub_command("listZones", "listzonesresponse" => { "count" => 1, ... })
|
|
18
|
+
def stub_command(command, body, status: 200)
|
|
19
|
+
stub_request(:get, API_ENDPOINT)
|
|
20
|
+
.with(query: hash_including("command" => command))
|
|
21
|
+
.to_return(
|
|
22
|
+
status: status,
|
|
23
|
+
body: JSON.generate(body),
|
|
24
|
+
headers: { "Content-Type" => "application/json" }
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Stub a command using CloudStack's usual envelope:
|
|
29
|
+
# { "<command>response" => { "count" => n, "<entity>" => [...] } }
|
|
30
|
+
def stub_list(command, entity, items, count: nil, extra: {})
|
|
31
|
+
inner = { "count" => count || items.size, entity => items }.merge(extra)
|
|
32
|
+
stub_command(command, { "#{command.downcase}response" => inner })
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Stub the initial async command, then the queryAsyncJobResult polls.
|
|
36
|
+
#
|
|
37
|
+
# `polls` is an array of jobstatus payloads returned in order.
|
|
38
|
+
def stub_async(command, job_id: "job-1", polls: [])
|
|
39
|
+
stub_command(command, { "#{command.downcase}response" => { "jobid" => job_id } })
|
|
40
|
+
|
|
41
|
+
responses = polls.map do |poll|
|
|
42
|
+
{
|
|
43
|
+
status: 200,
|
|
44
|
+
body: JSON.generate("queryasyncjobresultresponse" => poll),
|
|
45
|
+
headers: { "Content-Type" => "application/json" }
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
stub_request(:get, API_ENDPOINT)
|
|
50
|
+
.with(query: hash_including("command" => "queryAsyncJobResult"))
|
|
51
|
+
.to_return(*responses)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# A completed async job payload.
|
|
55
|
+
def job_success(result)
|
|
56
|
+
{ "jobstatus" => 1, "jobresult" => result }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# A failed async job payload.
|
|
60
|
+
def job_failure(errortext, code: 530)
|
|
61
|
+
{
|
|
62
|
+
"jobstatus" => 2,
|
|
63
|
+
"jobresultcode" => code,
|
|
64
|
+
"jobresult" => { "errortext" => errortext }
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# An in-flight async job payload.
|
|
69
|
+
def job_pending
|
|
70
|
+
{ "jobstatus" => 0 }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Parsed query parameters of the most recent request, for asserting on how
|
|
74
|
+
# a request was built rather than only on what came back.
|
|
75
|
+
def last_request_params
|
|
76
|
+
signature = WebMock::RequestRegistry.instance.requested_signatures.hash.keys.last
|
|
77
|
+
raise "no request was made" if signature.nil?
|
|
78
|
+
|
|
79
|
+
# CGI.parse was removed in Ruby 4.0; only CGI.escape/unescape remain.
|
|
80
|
+
URI.decode_www_form(signature.uri.query.to_s)
|
|
81
|
+
.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |(key, value), acc|
|
|
82
|
+
acc[key] << value
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
data/test/test_helper.rb
CHANGED
|
@@ -2,4 +2,41 @@ require "cloudstack_client"
|
|
|
2
2
|
|
|
3
3
|
require "minitest/spec"
|
|
4
4
|
require "minitest/autorun"
|
|
5
|
-
|
|
5
|
+
# Minitest 6 extracted Object#stub into the minitest-mock gem.
|
|
6
|
+
require "minitest/mock"
|
|
7
|
+
require "minitest/reporters"
|
|
8
|
+
|
|
9
|
+
require "webmock/minitest"
|
|
10
|
+
|
|
11
|
+
# No test may reach the network. An accidental live request should fail loudly
|
|
12
|
+
# rather than turn into a slow or flaky test.
|
|
13
|
+
WebMock.disable_net_connect!(allow_localhost: false)
|
|
14
|
+
|
|
15
|
+
Minitest::Reporters.use!(
|
|
16
|
+
ENV["CI"] ? Minitest::Reporters::ProgressReporter.new : Minitest::Reporters::SpecReporter.new
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
require_relative "support/api_stubs"
|
|
20
|
+
|
|
21
|
+
module TestHelpers
|
|
22
|
+
TEST_URL = "https://cloudstack.test/client/api".freeze
|
|
23
|
+
TEST_KEY = "test-key".freeze
|
|
24
|
+
TEST_SECRET = "test-secret".freeze
|
|
25
|
+
|
|
26
|
+
def fixture_path(*parts)
|
|
27
|
+
File.join(File.expand_path("data", __dir__), *parts)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# A client with no dynamically defined API methods: useful when the test
|
|
31
|
+
# targets Connection behaviour rather than the generated command methods.
|
|
32
|
+
def bare_client(options = {})
|
|
33
|
+
CloudstackClient::Client.new(
|
|
34
|
+
TEST_URL, TEST_KEY, TEST_SECRET, { no_api_methods: true }.merge(options)
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class Minitest::Spec
|
|
40
|
+
include TestHelpers
|
|
41
|
+
include ApiStubs
|
|
42
|
+
end
|
data/test/utils_test.rb
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require "test_helper"
|
|
2
|
+
|
|
3
|
+
describe CloudstackClient::Utils do
|
|
4
|
+
let(:utils) { Object.new.extend(CloudstackClient::Utils) }
|
|
5
|
+
|
|
6
|
+
describe "camel_case_to_underscore" do
|
|
7
|
+
it "converts a simple camel case command" do
|
|
8
|
+
_(utils.camel_case_to_underscore("listVirtualMachines"))
|
|
9
|
+
.must_equal "list_virtual_machines"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "splits a leading acronym from the following word" do
|
|
13
|
+
_(utils.camel_case_to_underscore("listVPCOfferings"))
|
|
14
|
+
.must_equal "list_vpc_offerings"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "handles a trailing acronym" do
|
|
18
|
+
_(utils.camel_case_to_underscore("listOsTypes")).must_equal "list_os_types"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "handles consecutive capitals followed by a word" do
|
|
22
|
+
_(utils.camel_case_to_underscore("createSSHKeyPair"))
|
|
23
|
+
.must_equal "create_ssh_key_pair"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "converts hyphens to underscores" do
|
|
27
|
+
_(utils.camel_case_to_underscore("some-command")).must_equal "some_command"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "leaves an already underscored name unchanged" do
|
|
31
|
+
_(utils.camel_case_to_underscore("list_apis")).must_equal "list_apis"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe "underscore_to_camel_case" do
|
|
36
|
+
it "converts an underscored name" do
|
|
37
|
+
_(utils.underscore_to_camel_case("list_virtual_machines"))
|
|
38
|
+
.must_equal "listVirtualMachines"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "returns a name without underscores unchanged" do
|
|
42
|
+
_(utils.underscore_to_camel_case("listApis")).must_equal "listApis"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "cannot restore acronym casing, because the conversion is lossy" do
|
|
46
|
+
# Documents a known limitation: "ssh" carries no record of "SSH".
|
|
47
|
+
# Api resolves underscored names through an index instead of relying
|
|
48
|
+
# on this method being a true inverse.
|
|
49
|
+
_(utils.underscore_to_camel_case("create_ssh_key_pair"))
|
|
50
|
+
.must_equal "createSshKeyPair"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe "round tripping real command names" do
|
|
55
|
+
it "round trips names without acronyms" do
|
|
56
|
+
%w[listVirtualMachines deployVirtualMachine listOsTypes listApis].each do |name|
|
|
57
|
+
underscored = utils.camel_case_to_underscore(name)
|
|
58
|
+
_(utils.underscore_to_camel_case(underscored)).must_equal name
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
metadata
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cloudstack_client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nik Wolfgramm
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: base64
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.1'
|
|
13
26
|
- !ruby/object:Gem::Dependency
|
|
14
27
|
name: rake
|
|
15
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -56,17 +69,108 @@ dependencies:
|
|
|
56
69
|
name: minitest
|
|
57
70
|
requirement: !ruby/object:Gem::Requirement
|
|
58
71
|
requirements:
|
|
59
|
-
- - "
|
|
72
|
+
- - ">="
|
|
60
73
|
- !ruby/object:Gem::Version
|
|
61
74
|
version: '5.14'
|
|
75
|
+
- - "<"
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '7'
|
|
62
78
|
type: :development
|
|
63
79
|
prerelease: false
|
|
64
80
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
81
|
requirements:
|
|
66
|
-
- - "
|
|
82
|
+
- - ">="
|
|
67
83
|
- !ruby/object:Gem::Version
|
|
68
84
|
version: '5.14'
|
|
69
|
-
|
|
85
|
+
- - "<"
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '7'
|
|
88
|
+
- !ruby/object:Gem::Dependency
|
|
89
|
+
name: minitest-reporters
|
|
90
|
+
requirement: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - "~>"
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '1.8'
|
|
95
|
+
type: :development
|
|
96
|
+
prerelease: false
|
|
97
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - "~>"
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '1.8'
|
|
102
|
+
- !ruby/object:Gem::Dependency
|
|
103
|
+
name: webmock
|
|
104
|
+
requirement: !ruby/object:Gem::Requirement
|
|
105
|
+
requirements:
|
|
106
|
+
- - "~>"
|
|
107
|
+
- !ruby/object:Gem::Version
|
|
108
|
+
version: '3.26'
|
|
109
|
+
type: :development
|
|
110
|
+
prerelease: false
|
|
111
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - "~>"
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: '3.26'
|
|
116
|
+
- !ruby/object:Gem::Dependency
|
|
117
|
+
name: benchmark
|
|
118
|
+
requirement: !ruby/object:Gem::Requirement
|
|
119
|
+
requirements:
|
|
120
|
+
- - ">="
|
|
121
|
+
- !ruby/object:Gem::Version
|
|
122
|
+
version: '0.3'
|
|
123
|
+
type: :development
|
|
124
|
+
prerelease: false
|
|
125
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
126
|
+
requirements:
|
|
127
|
+
- - ">="
|
|
128
|
+
- !ruby/object:Gem::Version
|
|
129
|
+
version: '0.3'
|
|
130
|
+
- !ruby/object:Gem::Dependency
|
|
131
|
+
name: rubocop
|
|
132
|
+
requirement: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - "~>"
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: '1.90'
|
|
137
|
+
type: :development
|
|
138
|
+
prerelease: false
|
|
139
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
140
|
+
requirements:
|
|
141
|
+
- - "~>"
|
|
142
|
+
- !ruby/object:Gem::Version
|
|
143
|
+
version: '1.90'
|
|
144
|
+
- !ruby/object:Gem::Dependency
|
|
145
|
+
name: rubocop-minitest
|
|
146
|
+
requirement: !ruby/object:Gem::Requirement
|
|
147
|
+
requirements:
|
|
148
|
+
- - "~>"
|
|
149
|
+
- !ruby/object:Gem::Version
|
|
150
|
+
version: '0.40'
|
|
151
|
+
type: :development
|
|
152
|
+
prerelease: false
|
|
153
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
154
|
+
requirements:
|
|
155
|
+
- - "~>"
|
|
156
|
+
- !ruby/object:Gem::Version
|
|
157
|
+
version: '0.40'
|
|
158
|
+
- !ruby/object:Gem::Dependency
|
|
159
|
+
name: rubocop-rake
|
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
|
161
|
+
requirements:
|
|
162
|
+
- - "~>"
|
|
163
|
+
- !ruby/object:Gem::Version
|
|
164
|
+
version: '0.7'
|
|
165
|
+
type: :development
|
|
166
|
+
prerelease: false
|
|
167
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
168
|
+
requirements:
|
|
169
|
+
- - "~>"
|
|
170
|
+
- !ruby/object:Gem::Version
|
|
171
|
+
version: '0.7'
|
|
172
|
+
description: A Ruby client for the Apache CloudStack API that builds its methods dynamically
|
|
173
|
+
from the CloudStack API definition, including an interactive console.
|
|
70
174
|
email:
|
|
71
175
|
- nik.wolfgramm@gmail.com
|
|
72
176
|
executables:
|
|
@@ -74,12 +178,14 @@ executables:
|
|
|
74
178
|
extensions: []
|
|
75
179
|
extra_rdoc_files: []
|
|
76
180
|
files:
|
|
181
|
+
- ".github/dependabot.yml"
|
|
77
182
|
- ".github/workflows/ci.yml"
|
|
78
183
|
- ".github/workflows/release.yml"
|
|
79
184
|
- ".gitignore"
|
|
80
|
-
- ".
|
|
185
|
+
- ".rubocop.yml"
|
|
186
|
+
- ".rubocop_todo.yml"
|
|
187
|
+
- CHANGELOG.md
|
|
81
188
|
- Gemfile
|
|
82
|
-
- Gemfile.lock
|
|
83
189
|
- LICENSE.txt
|
|
84
190
|
- README.md
|
|
85
191
|
- Rakefile
|
|
@@ -93,22 +199,33 @@ files:
|
|
|
93
199
|
- lib/cloudstack_client/configuration.rb
|
|
94
200
|
- lib/cloudstack_client/connection.rb
|
|
95
201
|
- lib/cloudstack_client/error.rb
|
|
202
|
+
- lib/cloudstack_client/request_handling.rb
|
|
96
203
|
- lib/cloudstack_client/utils.rb
|
|
97
204
|
- lib/cloudstack_client/version.rb
|
|
98
205
|
- test/api_test.rb
|
|
99
206
|
- test/benchmark.rb
|
|
100
207
|
- test/client_test.rb
|
|
101
208
|
- test/configuration_test.rb
|
|
209
|
+
- test/connection_test.rb
|
|
102
210
|
- test/data/0.42.json
|
|
103
211
|
- test/data/0.42.json.gz
|
|
104
212
|
- test/data/cloudstack-1.yml
|
|
105
213
|
- test/data/cloudstack-2.yml
|
|
214
|
+
- test/data/cloudstack-empty.yml
|
|
215
|
+
- test/data/cloudstack-incomplete.yml
|
|
216
|
+
- test/data/cloudstack-malformed.yml
|
|
217
|
+
- test/data/cloudstack-unsafe.yml
|
|
218
|
+
- test/support/api_stubs.rb
|
|
106
219
|
- test/test_helper.rb
|
|
220
|
+
- test/utils_test.rb
|
|
107
221
|
homepage: https://github.com/niwo/cloudstack_client
|
|
108
222
|
licenses:
|
|
109
223
|
- MIT
|
|
110
|
-
metadata:
|
|
111
|
-
|
|
224
|
+
metadata:
|
|
225
|
+
homepage_uri: https://github.com/niwo/cloudstack_client
|
|
226
|
+
bug_tracker_uri: https://github.com/niwo/cloudstack_client/issues
|
|
227
|
+
changelog_uri: https://github.com/niwo/cloudstack_client/releases
|
|
228
|
+
rubygems_mfa_required: 'true'
|
|
112
229
|
rdoc_options:
|
|
113
230
|
- "--line-numbers"
|
|
114
231
|
- "--inline-source"
|
|
@@ -118,24 +235,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
118
235
|
requirements:
|
|
119
236
|
- - ">="
|
|
120
237
|
- !ruby/object:Gem::Version
|
|
121
|
-
version:
|
|
238
|
+
version: '3.0'
|
|
122
239
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
240
|
requirements:
|
|
124
241
|
- - ">="
|
|
125
242
|
- !ruby/object:Gem::Version
|
|
126
243
|
version: '0'
|
|
127
244
|
requirements: []
|
|
128
|
-
rubygems_version:
|
|
129
|
-
signing_key:
|
|
245
|
+
rubygems_version: 4.0.18
|
|
130
246
|
specification_version: 4
|
|
131
247
|
summary: CloudStack API client written in Ruby
|
|
132
|
-
test_files:
|
|
133
|
-
- test/api_test.rb
|
|
134
|
-
- test/benchmark.rb
|
|
135
|
-
- test/client_test.rb
|
|
136
|
-
- test/configuration_test.rb
|
|
137
|
-
- test/data/0.42.json
|
|
138
|
-
- test/data/0.42.json.gz
|
|
139
|
-
- test/data/cloudstack-1.yml
|
|
140
|
-
- test/data/cloudstack-2.yml
|
|
141
|
-
- test/test_helper.rb
|
|
248
|
+
test_files: []
|
data/.travis.yml
DELETED
data/Gemfile.lock
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
PATH
|
|
2
|
-
remote: .
|
|
3
|
-
specs:
|
|
4
|
-
cloudstack_client (1.6.0)
|
|
5
|
-
|
|
6
|
-
GEM
|
|
7
|
-
remote: https://rubygems.org/
|
|
8
|
-
specs:
|
|
9
|
-
bond (0.5.1)
|
|
10
|
-
minitest (5.27.0)
|
|
11
|
-
rake (13.4.1)
|
|
12
|
-
ripl (0.7.1)
|
|
13
|
-
bond (~> 0.5.1)
|
|
14
|
-
thor (1.5.0)
|
|
15
|
-
|
|
16
|
-
PLATFORMS
|
|
17
|
-
x86_64-linux
|
|
18
|
-
|
|
19
|
-
DEPENDENCIES
|
|
20
|
-
cloudstack_client!
|
|
21
|
-
minitest (~> 5.14)
|
|
22
|
-
rake (~> 13.0)
|
|
23
|
-
ripl (~> 0.7)
|
|
24
|
-
thor (~> 1.1)
|
|
25
|
-
|
|
26
|
-
BUNDLED WITH
|
|
27
|
-
2.6.9
|