cloudconfig 0.1.1 → 0.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 +7 -0
- data/README.md +37 -9
- data/Rakefile +6 -0
- data/bin/cloudconfig +7 -5
- data/cloudconfig.gemspec +5 -5
- data/lib/cloudconfig/resources.rb +318 -166
- data/lib/cloudconfig/version.rb +1 -1
- data/test/helper/test_helper.rb +85 -9
- data/test/test.rb +126 -54
- metadata +60 -23
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f9fbb1c3690722f460de0b9f05d5b25089e72217
|
4
|
+
data.tar.gz: 76904f5715a14580d938762f661082089b7ed6ce
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2866cd11bbe9b214c0968ac885d460fc64bfae8da8635eb7e33ba281f8dce4c423e33601886bfa1b8b6c08d5a1dfd44a790b1cf6a08f752c48ef720ab600bf2f
|
7
|
+
data.tar.gz: da098dbdbf9f71bee3de73262cc8f99fdbf9b12b9d4587226e13fd247235b168da1e4b8de1dd5ab90fc4588670dd6786bc541383797543550843aff22132040a
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Cloudconfig
|
2
2
|
|
3
3
|
[](https://travis-ci.org/klarna/cloudconfig)
|
4
|
+
[](http://badge.fury.io/rb/cloudconfig)
|
4
5
|
|
5
6
|
Cloudconfig is an application that manages configurations for resources in Cloudstack.
|
6
7
|
|
@@ -41,7 +42,8 @@ Cloudconfig will configure resources in Cloudstack according to resources in yam
|
|
41
42
|
Resources that currently are handled by cloudconfig:
|
42
43
|
|
43
44
|
- Compute offerings (create, update, delete)
|
44
|
-
- Disk offerings (update)
|
45
|
+
- Disk offerings (create, update, delete)
|
46
|
+
- System offerings (create, update, delete)
|
45
47
|
- Host tags (create, delete)
|
46
48
|
- Storage tags (create, delete)
|
47
49
|
|
@@ -51,18 +53,26 @@ Resource files are expected to be structured in following manner in configured r
|
|
51
53
|
├── diskofferings.yaml
|
52
54
|
├── hosts.yaml
|
53
55
|
├── serviceofferings.yaml
|
54
|
-
|
56
|
+
├── storages.yaml
|
57
|
+
└── systemofferings.yaml
|
55
58
|
|
56
59
|
diskofferings.yaml:
|
57
60
|
|
58
61
|
DiskOfferings:
|
59
|
-
20gb:
|
60
|
-
|
61
|
-
|
62
|
+
20gb: { tags: "SSD",
|
63
|
+
displaytext: "20GB SSD Drive",
|
64
|
+
iscustomized: false,
|
65
|
+
disksize: 200 }
|
62
66
|
|
63
|
-
50GB:
|
64
|
-
|
65
|
-
|
67
|
+
50GB: { tags: "SSD",
|
68
|
+
displaytext: "50GB SSD Drive",
|
69
|
+
iscustomized: false,
|
70
|
+
disksize: 50 }
|
71
|
+
|
72
|
+
Custom: { tags: "",
|
73
|
+
displaytext: "Customized disk offering size",
|
74
|
+
iscustomized: true,
|
75
|
+
disksize: 0 }
|
66
76
|
|
67
77
|
hosts.yaml:
|
68
78
|
|
@@ -93,12 +103,30 @@ storage.yaml:
|
|
93
103
|
ssd: { tags: "ssd",
|
94
104
|
zonename: "example" }
|
95
105
|
|
106
|
+
systemofferings.yaml:
|
107
|
+
|
108
|
+
SystemOfferings:
|
109
|
+
console-proxy: { displaytext: "1vCPU, 500MHz, 1GB RAM",
|
110
|
+
cpunumber: 1,
|
111
|
+
cpuspeed: 500,
|
112
|
+
memory: 1024,
|
113
|
+
storagetype: "shared",
|
114
|
+
issystem: true,
|
115
|
+
systemvmtype: "consoleproxy" }
|
116
|
+
domain-router: { displaytext: "1vCPU, 500MHz, 256MB RAM",
|
117
|
+
cpunumber: 1,
|
118
|
+
cpuspeed: 500,
|
119
|
+
memory: 256,
|
120
|
+
storagetype: "shared",
|
121
|
+
issystem: true,
|
122
|
+
systemvmtype: "domainrouter" }
|
123
|
+
|
96
124
|
### Testing
|
97
125
|
|
98
126
|
Currently only unit testing:
|
99
127
|
|
100
128
|
bundle install
|
101
|
-
|
129
|
+
rake
|
102
130
|
|
103
131
|
## Contributing
|
104
132
|
|
data/Rakefile
CHANGED
data/bin/cloudconfig
CHANGED
@@ -29,12 +29,14 @@ module Cloudconfig
|
|
29
29
|
end
|
30
30
|
desc "update RESOURCE", "Updates RESOURCE from configuration yaml file."
|
31
31
|
long_desc <<-LONGDESC
|
32
|
-
`update serviceofferings` will configure service offerings.
|
33
|
-
\x5`update hosts` will configure hosts.
|
34
|
-
\x5`update storages` will configure storage pools.
|
35
|
-
\x5`update diskofferings` will configure disk offerings.
|
32
|
+
`update serviceofferings` will configure service offerings. (X)
|
33
|
+
\x5`update hosts` will configure hosts tags.
|
34
|
+
\x5`update storages` will configure storage pools tags.
|
35
|
+
\x5`update diskofferings` will configure disk offerings. (X)
|
36
|
+
\x5`update systemofferings` will configure system offerings. (X)
|
37
|
+
\x5`Marked with X` - The resource will be have a new ID number when updated, if there are more than 'displaytext', 'displayoffering' and 'sortkey' that have been given new values.
|
36
38
|
LONGDESC
|
37
|
-
option :delete, :desc => "delete option for service offerings, resources existing in CloudPlatform but not in configuation yaml file are deleted in CloudPlatform."
|
39
|
+
option :delete, :desc => "delete option for service offerings, disk offerings and system offerings - resources existing in CloudPlatform but not in configuation yaml file are deleted in CloudPlatform."
|
38
40
|
option :dryrun, :desc => "dry run option, changes that should be made are listed, but not performed."
|
39
41
|
def update(resource)
|
40
42
|
r = Resources.new(resource)
|
data/cloudconfig.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["katrin.nilsson@klarna.com", "carl.loa.odin@klarna.com", "olle.lundberg@klarna.com"]
|
11
11
|
spec.description = %q{Cloudconfig is an application that manages configurations for resources in Cloudstack.}
|
12
12
|
spec.summary = %q{Resource configuration manager for Cloudstack.}
|
13
|
-
spec.homepage = ""
|
13
|
+
spec.homepage = "https://rubygems.org/gems/cloudconfig"
|
14
14
|
spec.license = "Apache License, Version 2.0"
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
-
spec.add_development_dependency "rake", "~> 10.3.2"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.3", ">= 10.3.2"
|
23
23
|
|
24
|
-
spec.add_runtime_dependency "thor", "~> 0.19.1"
|
25
|
-
spec.add_runtime_dependency "user_config", "~> 0.0.4"
|
26
|
-
spec.add_runtime_dependency "cloudstack_ruby_client", "~> 1.0.1"
|
24
|
+
spec.add_runtime_dependency "thor", "~> 0.19", ">= 0.19.1"
|
25
|
+
spec.add_runtime_dependency "user_config", "~> 0.0", ">= 0.0.4"
|
26
|
+
spec.add_runtime_dependency "cloudstack_ruby_client", "~> 1.0", ">= 1.0.1"
|
27
27
|
end
|
@@ -5,170 +5,322 @@ require 'cloudstack_ruby_client'
|
|
5
5
|
require 'user_config'
|
6
6
|
|
7
7
|
module Cloudconfig
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
8
|
+
class Resources
|
9
|
+
|
10
|
+
|
11
|
+
attr_accessor :config, :delete, :dryrun, :resource, :client, :config_file
|
12
|
+
|
13
|
+
|
14
|
+
def initialize(resource)
|
15
|
+
@delete = false
|
16
|
+
@dryrun = false
|
17
|
+
@resource = resource
|
18
|
+
uconfig = UserConfig.new('.cloudconfig')
|
19
|
+
@config_file = uconfig['config.yaml']
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def update()
|
24
|
+
@client = create_cloudstack_client()
|
25
|
+
begin
|
26
|
+
resource_file, resource_cloud = define_yamlfile_and_cloudresource()
|
27
|
+
rescue Exception => error_msg
|
28
|
+
raise error_msg, "Resource could not be loaded from configuration file and/or the cloud."
|
29
|
+
end
|
30
|
+
r_updated, r_created, r_deleted = check_resource(resource_file, resource_cloud)
|
31
|
+
if @dryrun
|
32
|
+
puts "The following actions would be performed with this command:"
|
33
|
+
end
|
34
|
+
for updated in r_updated
|
35
|
+
begin
|
36
|
+
updated_resource, only_updated = update_resource(updated)
|
37
|
+
if !updated_resource.empty?
|
38
|
+
if only_updated
|
39
|
+
puts "Some values have been changed in the #{@resource} named #{updated_resource[0]}.\nOld values were:\n#{JSON.pretty_generate(updated_resource[1])}\nNew values are:\n#{JSON.pretty_generate(updated_resource[2])}"
|
40
|
+
else
|
41
|
+
puts "The #{@resource} named #{updated_resource[0]} has been recreated.\nOld values were:\n#{JSON.pretty_generate(updated_resource[1])}\nNew values are:\n#{JSON.pretty_generate(updated_resource[2])}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
rescue CreationError => error_msg
|
45
|
+
puts "#{updated[0]["name"]} could not be updated since: #{error_msg}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
for created in r_created
|
49
|
+
begin
|
50
|
+
created_resource = create_resource(created)
|
51
|
+
if !created_resource.empty?
|
52
|
+
puts "The #{@resource} named #{created_resource[0]} has been created"
|
53
|
+
end
|
54
|
+
rescue CreationError => error_msg
|
55
|
+
puts "#{created["name"]} could not be created since: #{error_msg}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
for deleted in r_deleted
|
59
|
+
deleted_resource = delete_resource(deleted)
|
60
|
+
if !deleted_resource.empty?
|
61
|
+
puts "The #{@resource} named #{deleted_resource[0]} has been deleted"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def create_cloudstack_client()
|
68
|
+
client = CloudstackRubyClient::Client.new(@config_file["url"], @config_file["api_key"], @config_file["secret_key"], true)
|
69
|
+
return client
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
# Save the current list of resources in resource_cloud, and the ones in the yaml file in resource_file
|
74
|
+
def define_yamlfile_and_cloudresource()
|
75
|
+
if @resource == "serviceofferings"
|
76
|
+
resource_title = "ServiceOfferings"
|
77
|
+
resource_cloud = @client.list_service_offerings()["serviceoffering"]
|
78
|
+
elsif @resource == "hosts"
|
79
|
+
resource_title = "Hosts"
|
80
|
+
resource_cloud = @client.list_hosts()["host"]
|
81
|
+
elsif @resource == "storages"
|
82
|
+
resource_title = "Storages"
|
83
|
+
resource_cloud = @client.list_storage_pools()["storagepool"]
|
84
|
+
elsif @resource == "diskofferings"
|
85
|
+
resource_title = "DiskOfferings"
|
86
|
+
resource_cloud = @client.list_disk_offerings()["diskoffering"]
|
87
|
+
elsif @resource == "systemofferings"
|
88
|
+
resource_title = "SystemOfferings"
|
89
|
+
resource_cloud = @client.list_service_offerings({"issystem" => true})["serviceoffering"]
|
90
|
+
end
|
91
|
+
resource_file = YAML.load_file("#{@config_file["resource_directory"]}/#{@resource}.yaml")["#{resource_title}"]
|
92
|
+
return resource_file, resource_cloud
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
# Compare resources in cloud and yaml file
|
97
|
+
def check_resource(resource_file, resource_cloud)
|
98
|
+
updated = Array.new
|
99
|
+
created = Array.new
|
100
|
+
deleted = Array.new
|
101
|
+
for r in resource_file
|
102
|
+
r_total = r[1].merge({"name" => "#{r[0]}"})
|
103
|
+
found = false
|
104
|
+
i = 0
|
105
|
+
# If the resource has not yet been found, compare names and update resource if names match but other parameters don't
|
106
|
+
while !found && i < resource_cloud.length
|
107
|
+
if resource_cloud[i]["name"] == r[0]
|
108
|
+
new_resource = resource_cloud[i].merge(r[1])
|
109
|
+
if resource_cloud[i] != new_resource
|
110
|
+
r_total = r_total.merge({"id" => "#{resource_cloud[i]["id"]}"})
|
111
|
+
# Update resource with new values, in first parameter, and send old values in second parameter
|
112
|
+
updated.push([r_total, resource_cloud[i]])
|
113
|
+
end
|
114
|
+
resource_cloud.delete_at(i)
|
115
|
+
found = true
|
116
|
+
else
|
117
|
+
i += 1
|
118
|
+
end
|
119
|
+
end
|
120
|
+
if (!found) && ((@resource == "serviceofferings") || (@resource == "diskofferings") || (@resource == "systemofferings"))
|
121
|
+
# Create resources
|
122
|
+
created.push(r_total)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
if delete && (resource_cloud.length > 0) && ((@resource == "serviceofferings") || (@resource == "diskofferings") || (@resource == "systemofferings"))
|
126
|
+
# Remove all resources that are not included in yaml file. (Only works for service offerings at the moment)
|
127
|
+
for r in resource_cloud
|
128
|
+
deleted.push(r)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
# Return resources that should be updated, created and deleted
|
132
|
+
return updated, created, deleted
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
def update_resource(res)
|
137
|
+
updated = true
|
138
|
+
updated_resource = Array.new
|
139
|
+
# 'res_union' contains the the parameters that the new, updated or recreated, resource should contain
|
140
|
+
res_union = Hash[res[1].to_a | res[0].to_a]
|
141
|
+
# 'res_diff' contains the parameters that differs from the existing resource, and represents what will be added/changed
|
142
|
+
res_diff, needs_recreation = required_changes(res)
|
143
|
+
if !res_diff.empty?
|
144
|
+
# All these resources could need recreation and are controlled, to see if all nedded requirements are met.
|
145
|
+
if (@resource == "serviceofferings") || (@resource == "systemofferings") || (@resource == "diskofferings")
|
146
|
+
if needs_recreation
|
147
|
+
updated = false
|
148
|
+
if has_recreated_resource?(res_union)
|
149
|
+
updated_resource.push(res[0]["name"], res[1], res_diff)
|
150
|
+
end
|
151
|
+
else
|
152
|
+
# Only an update is nedded
|
153
|
+
if (@resource == "serviceofferings") || (@resource == "systemofferings")
|
154
|
+
if !@dryrun
|
155
|
+
@client.update_service_offering(res_union)
|
156
|
+
end
|
157
|
+
elsif @resource == "diskofferings"
|
158
|
+
if !@dryrun
|
159
|
+
@client.update_disk_offering(res_union)
|
160
|
+
end
|
161
|
+
else
|
162
|
+
updated = false
|
163
|
+
end
|
164
|
+
end
|
165
|
+
# All these resources can only be updated.
|
166
|
+
elsif (@resource == "hosts") || (@resource == "storages")
|
167
|
+
if @resource == "hosts"
|
168
|
+
if !@dryrun
|
169
|
+
@client.update_host(res_union)
|
170
|
+
end
|
171
|
+
elsif @resource == "storages"
|
172
|
+
if !@dryrun
|
173
|
+
@client.update_storage_pool(res_union)
|
174
|
+
end
|
175
|
+
else
|
176
|
+
updated = false
|
177
|
+
end
|
178
|
+
end
|
179
|
+
else
|
180
|
+
updated = false
|
181
|
+
end
|
182
|
+
if updated
|
183
|
+
updated_resource.push(res[0]["name"], res[1], res_diff)
|
184
|
+
end
|
185
|
+
return updated_resource, updated
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
def required_changes(res)
|
190
|
+
# Find the parameters that differs
|
191
|
+
res_diff = Hash[(res[0].to_a) - (res[1].to_a)]
|
192
|
+
res_diff.delete_if { |param| param == "id" }
|
193
|
+
changes = res_diff.clone
|
194
|
+
# If the parameters that differs only contain the following keys, then the resource only needs an update. Otherwise, a recreation is required.
|
195
|
+
only_update_parameters = ["displaytext", "sortkey", "displayoffering"]
|
196
|
+
only_update_parameters.each { |searched_param| changes.delete_if { |actual_param| actual_param == searched_param } }
|
197
|
+
# Remove all changes that aren't actually changes, to avoid unnecessary updates
|
198
|
+
for param, value in changes
|
199
|
+
if (!res[1].has_key?(param) || (res[1][param] == "")) && (value == "")
|
200
|
+
changes.delete(param)
|
201
|
+
res_diff.delete(param)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
needs_recreation = false
|
205
|
+
if !changes.empty?
|
206
|
+
needs_recreation = true
|
207
|
+
end
|
208
|
+
return res_diff, needs_recreation
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
def has_recreated_resource?(res)
|
213
|
+
# The resource should only be deleted if it can be recreated, so a dryrun check needs to be made to assure this
|
214
|
+
actual_dryrun = @dryrun
|
215
|
+
@dryrun = true
|
216
|
+
created = create_resource(res)
|
217
|
+
if !created.empty?
|
218
|
+
# No errors occured, and an actual run can be made
|
219
|
+
@dryrun = actual_dryrun
|
220
|
+
deleted = delete_resource(res)
|
221
|
+
created = create_resource(res)
|
222
|
+
return true
|
223
|
+
end
|
224
|
+
@dryrun = actual_dryrun
|
225
|
+
return false
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
def create_resource(res)
|
230
|
+
check_for_creation_errors(res)
|
231
|
+
created = Array.new
|
232
|
+
created.push(res["name"])
|
233
|
+
if (@resource == "serviceofferings") || (@resource == "systemofferings")
|
234
|
+
if @resource == "systemofferings"
|
235
|
+
# All system offerings needs to include this
|
236
|
+
res["issystem"] = true
|
237
|
+
end
|
238
|
+
if !@dryrun
|
239
|
+
@client.create_service_offering(res)
|
240
|
+
end
|
241
|
+
elsif (@resource == "diskofferings")
|
242
|
+
# Parameter 'iscustomized' has different name ('customized') when creating resource, and parameter disksize create error if iscustomized is true
|
243
|
+
if res.has_key?("iscustomized") && res["iscustomized"] == true
|
244
|
+
res.delete("disksize")
|
245
|
+
end
|
246
|
+
res = res.merge({"customized" => res["iscustomized"]})
|
247
|
+
res.delete("iscustomized")
|
248
|
+
if !@dryrun
|
249
|
+
@client.create_disk_offering(res)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
created
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
def delete_resource(res)
|
257
|
+
deleted = Array.new
|
258
|
+
if (@resource == "serviceofferings") || (@resource == "systemofferings")
|
259
|
+
deleted.push(res["name"])
|
260
|
+
if !@dryrun
|
261
|
+
@client.delete_service_offering({"id" => "#{res["id"]}"})
|
262
|
+
end
|
263
|
+
elsif (@resource == "diskofferings")
|
264
|
+
deleted.push(res["name"])
|
265
|
+
if !@dryrun
|
266
|
+
@client.delete_disk_offering({"id" => "#{res["id"]}"})
|
267
|
+
end
|
268
|
+
end
|
269
|
+
deleted
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
def check_for_creation_errors(res)
|
274
|
+
errors = Array.new
|
275
|
+
if !res.has_key?("displaytext")
|
276
|
+
errors.push("'displaytext' has not been specified in configuration file.")
|
277
|
+
end
|
278
|
+
if @resource == "diskofferings"
|
279
|
+
if (!res.has_key?("iscustomized") || (res["iscustomized"] == false)) && (!res.has_key?("disksize") || (res["disksize"] == 0))
|
280
|
+
errors.push("'iscustomized' is unspecified or set to false and 'disksize' has not been specified, or has been specified to a value of 0 or below.")
|
281
|
+
end
|
282
|
+
elsif (@resource == "serviceofferings") || (@resource == "systemofferings")
|
283
|
+
if !res.has_key?("cpunumber") || !res.has_key?("cpuspeed") || !res.has_key?("memory") || (res["cpunumber"] <= 0) || (res["cpuspeed"] <= 0) || (res["memory"] <= 0)
|
284
|
+
errors.push("'cpunumber', 'cpuspeed' and/or 'memory' has not been defined, or is defined as 0 or below.")
|
285
|
+
end
|
286
|
+
if @resource == "systemofferings"
|
287
|
+
approved_systemvmtype = ["domainrouter", "consoleproxy", "secondarystoragevm"]
|
288
|
+
if !res.has_key?("systemvmtype") || !approved_systemvmtype.include?(res["systemvmtype"])
|
289
|
+
errors.push("'systemvmtype' is unspecified or set to a value not valid in Cloudconfig.")
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
if !errors.empty?
|
294
|
+
raise CreationError, errors
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
|
299
|
+
def list_resources()
|
300
|
+
@client = create_cloudstack_client()
|
301
|
+
resource_file, resource_cloud = define_yamlfile_and_cloudresource()
|
302
|
+
puts JSON.pretty_generate(resource_cloud)
|
303
|
+
end
|
304
|
+
|
305
|
+
def compare_resources()
|
306
|
+
@delete = true
|
307
|
+
@client = create_cloudstack_client()
|
308
|
+
resources = ["serviceofferings", "hosts", "storages", "diskofferings", "systemofferings"]
|
309
|
+
for r in resources
|
310
|
+
@res = r
|
311
|
+
resource_file, resource_cloud = define_yamlfile_and_cloudresource()
|
312
|
+
r_updated, r_created, r_deleted = check_resource(resource_file, resource_cloud)
|
313
|
+
puts "The following #{r} will be updated:"
|
314
|
+
r_updated.each{ |re| puts "\n#{re[0]["name"]}" }
|
315
|
+
puts "The following #{r} will be created:"
|
316
|
+
r_created.each{ |re| puts "\n#{re["name"]}" }
|
317
|
+
puts "The following #{r} will be deleted:"
|
318
|
+
r_deleted.each{ |re| puts "\n#{re["name"]}" }
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
end
|
323
|
+
|
324
|
+
class CreationError < Exception
|
325
|
+
end
|
174
326
|
end
|
data/lib/cloudconfig/version.rb
CHANGED
data/test/helper/test_helper.rb
CHANGED
@@ -4,15 +4,30 @@ class TestHelper
|
|
4
4
|
resource_cloud = [
|
5
5
|
{ "name" => "Resource-01",
|
6
6
|
"id" => 111,
|
7
|
-
"
|
7
|
+
"displaytext" => "Description A",
|
8
|
+
"tag" => "RES",
|
9
|
+
"cpunumber" => 1,
|
10
|
+
"cpuspeed" => 1,
|
11
|
+
"memory" => 1,
|
12
|
+
"systemvmtype" => "domainrouter",
|
13
|
+
"iscustomized" => false,
|
14
|
+
"disksize" => 1
|
8
15
|
},
|
9
16
|
{ "name" => "Resource-02",
|
10
17
|
"id" => 222,
|
11
|
-
"
|
18
|
+
"displaytext" => "Description B",
|
19
|
+
"tag" => "RES",
|
20
|
+
"cpunumber" => 1,
|
21
|
+
"cpuspeed" => 1,
|
22
|
+
"memory" => 1,
|
23
|
+
"systemvmtype" => "domainrouter",
|
24
|
+
"iscustomized" => false,
|
25
|
+
"disksize" => 1
|
12
26
|
},
|
13
27
|
{ "name" => "Resource-03",
|
14
28
|
"id" => 333,
|
15
|
-
"
|
29
|
+
"displaytext" => "Description C",
|
30
|
+
"tag" => "RAN",
|
16
31
|
}
|
17
32
|
]
|
18
33
|
return resource_cloud.to_a
|
@@ -20,17 +35,78 @@ class TestHelper
|
|
20
35
|
|
21
36
|
def get_resource_file()
|
22
37
|
resource_file = {
|
38
|
+
# Displaytext has changed in Resource-01
|
23
39
|
"Resource-01" => {
|
24
|
-
"
|
25
|
-
"tag" => "RES"
|
40
|
+
"displaytext" => "A different description",
|
41
|
+
"tag" => "RES",
|
42
|
+
"cpunumber" => 1,
|
43
|
+
"cpuspeed" => 1,
|
44
|
+
"memory" => 1,
|
45
|
+
"systemvmtype" => "domainrouter",
|
46
|
+
"iscustomized" => false,
|
47
|
+
"disksize" => 1
|
26
48
|
},
|
49
|
+
# Tags has changed in Resource-02
|
27
50
|
"Resource-02" => {
|
28
|
-
"
|
29
|
-
"tag" => "RAN"
|
51
|
+
"displaytext" => "Description B",
|
52
|
+
"tag" => "RAN",
|
53
|
+
"cpunumber" => 1,
|
54
|
+
"cpuspeed" => 1,
|
55
|
+
"memory" => 1,
|
56
|
+
"systemvmtype" => "domainrouter",
|
57
|
+
"iscustomized" => false,
|
58
|
+
"disksize" => 1
|
30
59
|
},
|
60
|
+
# Resource-03 has been deleted
|
61
|
+
# The resources below should be up for creation
|
62
|
+
# serviceofferings, systemofferings and diskofferings: Resource-04 should be created
|
31
63
|
"Resource-04" => {
|
32
|
-
"
|
33
|
-
"
|
64
|
+
"displaytext" => "Description D",
|
65
|
+
"cpunumber" => 1,
|
66
|
+
"cpuspeed" => 1,
|
67
|
+
"memory" => 1,
|
68
|
+
"systemvmtype" => "domainrouter",
|
69
|
+
"iscustomized" => false,
|
70
|
+
"disksize" => 1
|
71
|
+
},
|
72
|
+
# serviceofferings, systemofferings and diskofferings: Resouce-05 does not have 'displaytext' and should not be created
|
73
|
+
"Resource-05" => {
|
74
|
+
"cpunumber" => 1,
|
75
|
+
"cpuspeed" => 1,
|
76
|
+
"memory" => 1,
|
77
|
+
"systemvmtype" => "consoleproxy",
|
78
|
+
"iscustomized" => true
|
79
|
+
},
|
80
|
+
# serviceofferings and systemofferings: Resource-06 doas not have 'cpunumber' and should not be created
|
81
|
+
# diskofferings: Resource-06 should be created
|
82
|
+
"Resource-06" => {
|
83
|
+
"displaytext" => "Description F",
|
84
|
+
"cpuspeed" => 1,
|
85
|
+
"memory" => 1,
|
86
|
+
"systemvmtype" => "secondarystoragevm",
|
87
|
+
"iscustomized" => true
|
88
|
+
},
|
89
|
+
# serviceofferings and systemofferings: Resource-07 does not have valid 'cpuspeed' value and can not be created
|
90
|
+
# diskofferings: Resource-07 has 'iscustomized' = false but does not have 'disksize' and should not be created
|
91
|
+
"Resource-07" => {
|
92
|
+
"displaytext" => "Description G",
|
93
|
+
"cpunumber" => 1,
|
94
|
+
"cpuspeed" => 0,
|
95
|
+
"memory" => 1,
|
96
|
+
"systemvmtype" => "domainrouter",
|
97
|
+
"iscustomized" => false
|
98
|
+
},
|
99
|
+
# serviceofferings: Resource-08 should be created
|
100
|
+
# systemofferings: Resource-08 does not have a valid 'systemvmtype' and can not be created
|
101
|
+
# diskofferings: Resource-08 does not have a valid 'disksize'i and should not be created
|
102
|
+
"Resource-08" => {
|
103
|
+
"displaytext" => "Description H",
|
104
|
+
"cpunumber" => 1,
|
105
|
+
"cpuspeed" => 1,
|
106
|
+
"memory" => 1,
|
107
|
+
"systemvmtype" => "invalidsystemvmtype",
|
108
|
+
"iscustomized" => false,
|
109
|
+
"disksize" => 0
|
34
110
|
}
|
35
111
|
}
|
36
112
|
return resource_file
|
data/test/test.rb
CHANGED
@@ -11,7 +11,8 @@ class TestResources < Test::Unit::TestCase
|
|
11
11
|
@res = [Cloudconfig::Resources.new("serviceofferings"),
|
12
12
|
Cloudconfig::Resources.new("hosts"),
|
13
13
|
Cloudconfig::Resources.new("storages"),
|
14
|
-
Cloudconfig::Resources.new("diskofferings")
|
14
|
+
Cloudconfig::Resources.new("diskofferings"),
|
15
|
+
Cloudconfig::Resources.new("systemofferings")]
|
15
16
|
|
16
17
|
for r in @res
|
17
18
|
r.delete = false
|
@@ -24,81 +25,152 @@ class TestResources < Test::Unit::TestCase
|
|
24
25
|
|
25
26
|
def test_update_serviceofferings_delete_is_false
|
26
27
|
# Test array and hash in test_helper.rb, with delete option set to false in setup
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
assert_equal(0, del.length)
|
28
|
+
resource = @res[0]
|
29
|
+
upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
|
30
|
+
control_updates(upd, resource)
|
31
|
+
control_creates(cre, resource)
|
32
|
+
control_deletes(del, resource)
|
33
33
|
end
|
34
34
|
|
35
35
|
# Same test, with delete set to true
|
36
36
|
def test_update_serviceofferings_delete_is_true
|
37
|
-
@res[0]
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
37
|
+
resource = @res[0]
|
38
|
+
resource.delete = true
|
39
|
+
upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
|
40
|
+
control_updates(upd, resource)
|
41
|
+
control_creates(cre, resource)
|
42
|
+
control_deletes(del, resource)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_update_systemofferings_delete_is_false
|
46
|
+
resource = @res[4]
|
47
|
+
upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
|
48
|
+
control_updates(upd, resource)
|
49
|
+
control_creates(cre, resource)
|
50
|
+
control_deletes(del, resource)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_update_systemofferings_delete_is_true
|
54
|
+
resource = @res[4]
|
55
|
+
resource.delete = true
|
56
|
+
upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
|
57
|
+
control_updates(upd, resource)
|
58
|
+
control_creates(cre, resource)
|
59
|
+
control_deletes(del, resource)
|
45
60
|
end
|
46
61
|
|
47
62
|
def test_update_hosts_delete_is_false
|
48
|
-
|
49
|
-
upd, cre, del =
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
assert_equal(0, del.length)
|
63
|
+
resource = @res[1]
|
64
|
+
upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
|
65
|
+
control_updates(upd, resource)
|
66
|
+
control_creates(cre, resource)
|
67
|
+
control_deletes(del, resource)
|
54
68
|
end
|
55
69
|
|
56
|
-
# Same test, with delete set to true
|
57
70
|
def test_update_hosts_delete_is_true
|
58
|
-
@res[1]
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
71
|
+
resource = @res[1]
|
72
|
+
resource.delete = true
|
73
|
+
upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
|
74
|
+
control_updates(upd, resource)
|
75
|
+
control_creates(cre, resource)
|
76
|
+
control_deletes(del, resource)
|
64
77
|
end
|
65
78
|
|
66
79
|
def test_update_storages_delete_is_false
|
67
|
-
|
68
|
-
upd, cre, del =
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
assert_equal(0, del.length)
|
80
|
+
resource = @res[2]
|
81
|
+
upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
|
82
|
+
control_updates(upd, resource)
|
83
|
+
control_creates(cre, resource)
|
84
|
+
control_deletes(del, resource)
|
73
85
|
end
|
74
86
|
|
75
|
-
# Same test, with delete set to true
|
76
87
|
def test_update_storages_delete_is_true
|
77
|
-
@res[2]
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
88
|
+
resource = @res[2]
|
89
|
+
resource.delete = true
|
90
|
+
upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
|
91
|
+
control_updates(upd, resource)
|
92
|
+
control_creates(cre, resource)
|
93
|
+
control_deletes(del, resource)
|
83
94
|
end
|
84
95
|
|
85
96
|
def test_update_diskofferings_delete_is_false
|
86
|
-
|
87
|
-
upd, cre, del =
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
assert_equal(0, del.length)
|
97
|
+
resource = @res[3]
|
98
|
+
upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
|
99
|
+
control_updates(upd, resource)
|
100
|
+
control_creates(cre, resource)
|
101
|
+
control_deletes(del, resource)
|
92
102
|
end
|
93
103
|
|
94
|
-
# Same test, with delete set to true
|
95
104
|
def test_update_diskofferings_delete_is_true
|
96
|
-
@res[3]
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
105
|
+
resource = @res[3]
|
106
|
+
resource.delete = true
|
107
|
+
upd, cre, del = resource.check_resource(@r_test_file, @r_test_cloud)
|
108
|
+
control_updates(upd, resource)
|
109
|
+
control_creates(cre, resource)
|
110
|
+
control_deletes(del, resource)
|
111
|
+
end
|
112
|
+
|
113
|
+
def control_updates(updated_resources, resource)
|
114
|
+
assert_equal(2, updated_resources.length, "Two resources should be checked for updation")
|
115
|
+
updated_resources.each { |r| assert(["Resource-01", "Resource-02"].include?(r[0]["name"])) }
|
116
|
+
recreatable = false
|
117
|
+
# if resource is serviceofferings, diskofferings or systemofferings
|
118
|
+
if (resource == @res[0]) || (resource == @res[3]) || (resource == @res[4])
|
119
|
+
recreatable = true
|
120
|
+
end
|
121
|
+
for updated in updated_resources
|
122
|
+
r, only_updated = resource.update_resource(updated)
|
123
|
+
assert(!r.empty?, "A call to the update_resource method did not return an updated #{resource}")
|
124
|
+
if recreatable
|
125
|
+
if r[0] == "Resource-01"
|
126
|
+
assert(only_updated, "#{r[0]} should only be updated")
|
127
|
+
elsif r[0] == "Resource-02"
|
128
|
+
assert(!only_updated, "#{r[0]} should be recreated")
|
129
|
+
else
|
130
|
+
assert(false, "#{r[0]} should not be updated")
|
131
|
+
end
|
132
|
+
else
|
133
|
+
assert(only_updated, "#{resource} should only be updated")
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def control_creates(created_resources, resource)
|
139
|
+
if (resource == @res[0]) || (resource == @res[3]) || (resource == @res[4])
|
140
|
+
assert_equal(5, created_resources.length, "Five resources should be checked for creation")
|
141
|
+
created_resources.each { |r| assert(["Resource-04", "Resource-05", "Resource-06", "Resource-07", "Resource-08"].include?(r["name"])) }
|
142
|
+
should_be_created = Array.new
|
143
|
+
# if resource is serviceofferings, systemofferings or diskofferings
|
144
|
+
should_be_created.push("Resource-04")
|
145
|
+
# if resource is serviceofferings
|
146
|
+
if resource == @res[0]
|
147
|
+
should_be_created.push("Resource-08")
|
148
|
+
# if resource is diskofferings
|
149
|
+
elsif resource == @res[3]
|
150
|
+
should_be_created.push("Resource-06")
|
151
|
+
end
|
152
|
+
for created in created_resources
|
153
|
+
if should_be_created.include?(created["name"])
|
154
|
+
r = resource.create_resource(created)
|
155
|
+
assert_equal(1, r.length, "#{r[0]} should be created without any problems")
|
156
|
+
else
|
157
|
+
assert_raise Cloudconfig::CreationError do
|
158
|
+
r = resource.create_resource(created)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
else
|
163
|
+
assert(created_resources.empty?, "No #{resource} should be created, since the resource is not creatable")
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def control_deletes(deleted_resources, resource)
|
168
|
+
if resource.delete && ((resource == @res[0]) || (resource == @res[3]) || (resource == @res[4]))
|
169
|
+
assert_equal(1, deleted_resources.length, "One #{resource} should be deleted, since the resource is deletable and 'delete' is #{resource.delete}")
|
170
|
+
assert_equal("Resource-03", "#{deleted_resources[0]["name"]}")
|
171
|
+
else
|
172
|
+
assert(deleted_resources.empty?, "No #{resource} should be deleted, since the resource is not deletable")
|
173
|
+
end
|
102
174
|
end
|
103
175
|
|
104
176
|
end
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cloudconfig
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.4.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Katrin Nilsson
|
@@ -11,63 +10,102 @@ authors:
|
|
11
10
|
autorequire:
|
12
11
|
bindir: bin
|
13
12
|
cert_chain: []
|
14
|
-
date: 2014-
|
13
|
+
date: 2014-08-15 00:00:00.000000000 Z
|
15
14
|
dependencies:
|
16
15
|
- !ruby/object:Gem::Dependency
|
17
16
|
name: bundler
|
18
|
-
requirement:
|
19
|
-
none: false
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
20
18
|
requirements:
|
21
19
|
- - ~>
|
22
20
|
- !ruby/object:Gem::Version
|
23
21
|
version: '1.3'
|
24
22
|
type: :development
|
25
23
|
prerelease: false
|
26
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '1.3'
|
27
29
|
- !ruby/object:Gem::Dependency
|
28
30
|
name: rake
|
29
|
-
requirement:
|
30
|
-
none: false
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
31
32
|
requirements:
|
32
33
|
- - ~>
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '10.3'
|
36
|
+
- - ! '>='
|
33
37
|
- !ruby/object:Gem::Version
|
34
38
|
version: 10.3.2
|
35
39
|
type: :development
|
36
40
|
prerelease: false
|
37
|
-
version_requirements:
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '10.3'
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 10.3.2
|
38
49
|
- !ruby/object:Gem::Dependency
|
39
50
|
name: thor
|
40
|
-
requirement:
|
41
|
-
none: false
|
51
|
+
requirement: !ruby/object:Gem::Requirement
|
42
52
|
requirements:
|
43
53
|
- - ~>
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0.19'
|
56
|
+
- - ! '>='
|
44
57
|
- !ruby/object:Gem::Version
|
45
58
|
version: 0.19.1
|
46
59
|
type: :runtime
|
47
60
|
prerelease: false
|
48
|
-
version_requirements:
|
61
|
+
version_requirements: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0.19'
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.19.1
|
49
69
|
- !ruby/object:Gem::Dependency
|
50
70
|
name: user_config
|
51
|
-
requirement:
|
52
|
-
none: false
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
53
72
|
requirements:
|
54
73
|
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.0'
|
76
|
+
- - ! '>='
|
55
77
|
- !ruby/object:Gem::Version
|
56
78
|
version: 0.0.4
|
57
79
|
type: :runtime
|
58
80
|
prerelease: false
|
59
|
-
version_requirements:
|
81
|
+
version_requirements: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0.0'
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 0.0.4
|
60
89
|
- !ruby/object:Gem::Dependency
|
61
90
|
name: cloudstack_ruby_client
|
62
|
-
requirement:
|
63
|
-
none: false
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
64
92
|
requirements:
|
65
93
|
- - ~>
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '1.0'
|
96
|
+
- - ! '>='
|
66
97
|
- !ruby/object:Gem::Version
|
67
98
|
version: 1.0.1
|
68
99
|
type: :runtime
|
69
100
|
prerelease: false
|
70
|
-
version_requirements:
|
101
|
+
version_requirements: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ~>
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '1.0'
|
106
|
+
- - ! '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: 1.0.1
|
71
109
|
description: Cloudconfig is an application that manages configurations for resources
|
72
110
|
in Cloudstack.
|
73
111
|
email:
|
@@ -91,30 +129,29 @@ files:
|
|
91
129
|
- lib/cloudconfig/version.rb
|
92
130
|
- test/helper/test_helper.rb
|
93
131
|
- test/test.rb
|
94
|
-
homepage:
|
132
|
+
homepage: https://rubygems.org/gems/cloudconfig
|
95
133
|
licenses:
|
96
134
|
- Apache License, Version 2.0
|
135
|
+
metadata: {}
|
97
136
|
post_install_message:
|
98
137
|
rdoc_options: []
|
99
138
|
require_paths:
|
100
139
|
- lib
|
101
140
|
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
-
none: false
|
103
141
|
requirements:
|
104
142
|
- - ! '>='
|
105
143
|
- !ruby/object:Gem::Version
|
106
144
|
version: '0'
|
107
145
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
-
none: false
|
109
146
|
requirements:
|
110
147
|
- - ! '>='
|
111
148
|
- !ruby/object:Gem::Version
|
112
149
|
version: '0'
|
113
150
|
requirements: []
|
114
151
|
rubyforge_project:
|
115
|
-
rubygems_version:
|
152
|
+
rubygems_version: 2.3.0
|
116
153
|
signing_key:
|
117
|
-
specification_version:
|
154
|
+
specification_version: 4
|
118
155
|
summary: Resource configuration manager for Cloudstack.
|
119
156
|
test_files:
|
120
157
|
- test/helper/test_helper.rb
|