kitchen-vmpool 0.1.1 → 0.2.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/README.md +3 -2
- data/exe/vra_create.rb +209 -0
- data/{vra_create_pool → exe/vra_create_pool} +42 -20
- data/kitchen-vmpool.gemspec +2 -1
- data/lib/kitchen/driver/vmpool.rb +55 -20
- data/lib/kitchen/driver/vmpool_stores/base_store.rb +53 -0
- data/lib/kitchen/driver/vmpool_stores/file_store.rb +2 -28
- data/lib/kitchen/driver/vmpool_stores/gitlab_commit_store.rb +82 -0
- data/lib/kitchen/driver/vmpool_stores/{gitlab_store.rb → gitlab_snippet_store.rb} +15 -22
- data/lib/kitchen-vmpool/version.rb +3 -0
- data/vmpool.yaml +1 -1
- metadata +11 -6
- data/vra_create.rb +0 -156
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e4fff8380ee9f18fec8ef01a1d609b54ebfbfc4
|
4
|
+
data.tar.gz: fdaa0e13db8f1efaeca8c49c0a41382f2a793457
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3ca03be5000e386e92defb694ac0c9d0efec5778124427c607c190c26b61284e7b33a85447907236955426820059afd313b65cc8b6bcfbf943e1278e56e393b
|
7
|
+
data.tar.gz: 5dc30b54179d657966a1ffdf12b071a58117d25df8533f44e3b72afa631744fa6790a6ab0aef68074a3cb2c3299251d7b6463fb939ccc18ca751476cd776624f
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# Kitchen::Vmpool
|
2
2
|
|
3
|
-
|
3
|
+
Provides the ability to select a memeber of a pool to use during a kitchen test. This allows you to instantly startup
|
4
|
+
a test instead of waiting for a machine to provision.
|
4
5
|
|
5
|
-
|
6
|
+
Pluggable backend is used for storing pool information.
|
6
7
|
|
7
8
|
## Installation
|
8
9
|
|
data/exe/vra_create.rb
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'vra'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'erb'
|
5
|
+
require 'highline/import'
|
6
|
+
require 'openssl'
|
7
|
+
require 'json'
|
8
|
+
require 'yaml'
|
9
|
+
|
10
|
+
# Purpose: Submits a single request to VRA for vm creation
|
11
|
+
|
12
|
+
|
13
|
+
# monkey patch strings to symbols until we can patch upstream
|
14
|
+
module Vra
|
15
|
+
class CatalogRequest
|
16
|
+
attr_accessor :template_payload
|
17
|
+
|
18
|
+
def template_payload
|
19
|
+
@template_payload ||= dump_template(@catalog_id)
|
20
|
+
end
|
21
|
+
|
22
|
+
def template_payload=(payload)
|
23
|
+
@template_payload = payload
|
24
|
+
end
|
25
|
+
|
26
|
+
def write_template(id, filename = nil)
|
27
|
+
filename ||= "#{id}.json"
|
28
|
+
begin
|
29
|
+
puts "Writing file #{filename}"
|
30
|
+
contents = dump_template(id)
|
31
|
+
data = JSON.parse(contents)
|
32
|
+
pretty_contents = JSON.pretty_generate(data)
|
33
|
+
File.write(filename, pretty_contents)
|
34
|
+
rescue Vra::Exception::HTTPError => e
|
35
|
+
puts e.message
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def dump_template(id)
|
40
|
+
response = client.http_get("/catalog-service/api/consumer/entitledCatalogItems/#{id}/requests/template")
|
41
|
+
response.body
|
42
|
+
end
|
43
|
+
|
44
|
+
def merged_payload
|
45
|
+
merge_payload(template_payload)
|
46
|
+
end
|
47
|
+
|
48
|
+
def submit
|
49
|
+
validate_params!
|
50
|
+
|
51
|
+
begin
|
52
|
+
post_response = client.http_post("/catalog-service/api/consumer/entitledCatalogItems/#{@catalog_id}/requests", merged_payload)
|
53
|
+
rescue Vra::Exception::HTTPError => e
|
54
|
+
raise Vra::Exception::RequestError, "Unable to submit request: #{e.errors.join(', ')}"
|
55
|
+
rescue
|
56
|
+
raise
|
57
|
+
end
|
58
|
+
|
59
|
+
request_id = JSON.parse(post_response.body)["id"]
|
60
|
+
Vra::Request.new(client, request_id)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
class RequestParameters
|
65
|
+
def set_parameters(key, value_data, parent = nil)
|
66
|
+
value_type = value_data[:type] || value_data['type']
|
67
|
+
data_value = value_data[:value] || value_data['value']
|
68
|
+
if value_type
|
69
|
+
if parent.nil?
|
70
|
+
set(key, value_type, data_value)
|
71
|
+
else
|
72
|
+
parent.add_child(Vra::RequestParameter.new(key, value_type, data_value))
|
73
|
+
end
|
74
|
+
else
|
75
|
+
if parent.nil?
|
76
|
+
p = set(key, nil, nil)
|
77
|
+
else
|
78
|
+
p = Vra::RequestParameter.new(key, nil, nil)
|
79
|
+
parent.add_child(p)
|
80
|
+
end
|
81
|
+
|
82
|
+
value_data.each do |k, data|
|
83
|
+
set_parameters(k, data, p)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
module VraUtilities
|
91
|
+
def classification
|
92
|
+
ENV['VRA_CLASSIFY']
|
93
|
+
end
|
94
|
+
|
95
|
+
def branch
|
96
|
+
`git rev-parse --abbrev-ref HEAD`.chomp if ENV['USE_BRANCH']
|
97
|
+
end
|
98
|
+
|
99
|
+
def sandbox
|
100
|
+
branch || 'dev'
|
101
|
+
end
|
102
|
+
|
103
|
+
def datacenter
|
104
|
+
ENV['DATACENTER']
|
105
|
+
end
|
106
|
+
|
107
|
+
def vra_email
|
108
|
+
@vra_email ||= ENV['VRA_EMAIL'] || ask('What is your frit email for VRA notifications')
|
109
|
+
end
|
110
|
+
|
111
|
+
def subtenant_id
|
112
|
+
ENV['VRA_SUB_TENANT_ID']
|
113
|
+
end
|
114
|
+
|
115
|
+
def vra_user
|
116
|
+
@vra_user ||= ENV['VRA_USER'] || ask('Enter User: ') {|q| q.echo = true}
|
117
|
+
end
|
118
|
+
|
119
|
+
def vra_pass
|
120
|
+
@vra_pass ||= ENV['VRA_PASS'] || ask('Enter VRA Password: ') {|q| q.echo = 'x'}
|
121
|
+
end
|
122
|
+
|
123
|
+
def base_url
|
124
|
+
@server ||= ENV['VRA_URL']
|
125
|
+
end
|
126
|
+
|
127
|
+
# @return [VRA::Client] - creates a new client object and returns it
|
128
|
+
def client
|
129
|
+
@client ||= Vra::Client.new(
|
130
|
+
username: vra_user,
|
131
|
+
password: vra_pass,
|
132
|
+
tenant: 'vsphere.local',
|
133
|
+
base_url: base_url,
|
134
|
+
verify_ssl: false,
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
138
|
+
# @return Array[String] - returns an array of catalog items
|
139
|
+
def catalog_items
|
140
|
+
client.catalog.all_items.map {|i| {name: i.name, id: i.id}}
|
141
|
+
end
|
142
|
+
|
143
|
+
def template_data
|
144
|
+
@template_data ||= JSON.parse(File.read(@payload_file))
|
145
|
+
end
|
146
|
+
|
147
|
+
def request_options
|
148
|
+
{
|
149
|
+
cpus: template_data['data']['Machine']['data']['cpu'] || 2,
|
150
|
+
memory: template_data['data']['Machine']['data']['memory'] || 4096,
|
151
|
+
requested_for: ENV['VRA_USER']
|
152
|
+
lease_days: 2,
|
153
|
+
notes: 'VRA Server Pool Test',
|
154
|
+
subtenant_id: template_data['businessGroupId']
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
def catalog_request
|
159
|
+
blueprint = template_data['catalogItemId']
|
160
|
+
cr = client.catalog.request(blueprint, request_options)
|
161
|
+
cr.template_payload = File.read(@payload_file)
|
162
|
+
cr
|
163
|
+
end
|
164
|
+
|
165
|
+
# @return [Vra::Request] - returns a request item
|
166
|
+
def submit_new_request(file)
|
167
|
+
@payload_file = File.expand_path(file)
|
168
|
+
unless @payload_file and File.exist?(@payload_file)
|
169
|
+
puts "The payload file: #{@payload_file} does not exist"
|
170
|
+
exit -1
|
171
|
+
end
|
172
|
+
cr = catalog_request
|
173
|
+
cr.submit
|
174
|
+
end
|
175
|
+
|
176
|
+
require 'optparse'
|
177
|
+
|
178
|
+
def dump_templates(dir_name = 'vra7_templates')
|
179
|
+
FileUtils.mkdir(dir_name) unless File.exist?(dir_name)
|
180
|
+
catalog_items.each do |c|
|
181
|
+
cr = catalog_request
|
182
|
+
filename = File.join(dir_name, "#{c[:name]}.json".gsub(' ', '_')).downcase
|
183
|
+
cr.write_template(c[:id], filename)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
def run
|
189
|
+
cli_options = {}
|
190
|
+
o = OptionParser.new do |opts|
|
191
|
+
opts.program_name = 'vra-pool'
|
192
|
+
opts.on_head(<<-EOF
|
193
|
+
|
194
|
+
Summary: A tool used to provision systems in VRA
|
195
|
+
EOF
|
196
|
+
)
|
197
|
+
opts.on('-n', '--node-file FILE', "Load the request data from this file and create it") do |c|
|
198
|
+
cli_options[:node_file] = c
|
199
|
+
end
|
200
|
+
opts.on('-t', '--dump-templates', "Dump all catalog templates") do |c|
|
201
|
+
cli_options[:dump_templates] = true
|
202
|
+
end
|
203
|
+
end.parse!
|
204
|
+
@payload_file = cli_options[:node_file]
|
205
|
+
dump_templates if cli_options[:dump_templates]
|
206
|
+
submit_new_request(@payload_file)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
include VraUtilities
|
@@ -1,11 +1,11 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
3
|
+
require 'hashdiff'
|
4
4
|
require 'resolv'
|
5
5
|
require 'optparse'
|
6
6
|
require 'yaml'
|
7
7
|
require_relative 'vra_create'
|
8
|
-
require 'kitchen/driver/vmpool_stores/
|
8
|
+
require 'kitchen/driver/vmpool_stores/gitlab_commit_store'
|
9
9
|
require 'socket'
|
10
10
|
require 'timeout'
|
11
11
|
|
@@ -20,34 +20,62 @@ def options
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def create_pool(pool_data)
|
23
|
-
(1..pool_data['instances']).map
|
23
|
+
(1..pool_data['instances']).map do |num|
|
24
|
+
puts pool_data['payload_file']
|
25
|
+
submit_new_request(pool_data['payload_file']).id
|
26
|
+
end
|
24
27
|
end
|
25
28
|
|
26
29
|
# return hostnames or false
|
30
|
+
# @param [VRA::Request] - a request object
|
27
31
|
def resolve_vm_name(request)
|
28
|
-
r =
|
32
|
+
r = request
|
29
33
|
return r.resources.map(&:name) if r.successful? and r.completed?
|
30
34
|
return false
|
31
35
|
end
|
32
36
|
|
37
|
+
def req_obj(id)
|
38
|
+
client.requests.by_id(id)
|
39
|
+
end
|
40
|
+
|
41
|
+
# compares the before and after state of the store hashes
|
42
|
+
def store_changed?(before, after)
|
43
|
+
HashDiff.diff(before, after).count > 0
|
44
|
+
end
|
45
|
+
|
33
46
|
# @return [Hash] - a store hash that contains one or more pools
|
34
47
|
# @option project_id [Integer] - the project id in gitlab
|
35
|
-
# @option snippet_id [Integer] - the snipppet id in the gitlab project
|
36
48
|
# @option pool_file [String] - the snipppet file name
|
37
49
|
def store(store_options = options)
|
38
50
|
# create a new instance of the store with the provided options
|
39
|
-
@store ||= Kitchen::Driver::VmpoolStores::
|
51
|
+
@store ||= Kitchen::Driver::VmpoolStores::GitlabCommitStore.new(store_options)
|
40
52
|
end
|
41
53
|
|
42
54
|
# creates the number of instances defined in the pool data
|
43
55
|
def create_pools
|
56
|
+
@old_store = store.dup
|
44
57
|
pools.map do |key, value|
|
45
58
|
# convert the requests to vm names
|
46
59
|
pools[key]['requests'] = value['requests'].find_all do |req|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
60
|
+
puts "Checking request: #{req}"
|
61
|
+
r = req_obj(req)
|
62
|
+
if r.completed?
|
63
|
+
puts "The request #{req} has completed, getting hostname"
|
64
|
+
hostnames = resolve_vm_name(r)
|
65
|
+
# remove request from pool file by not returning anything
|
66
|
+
# if hostname does not exist but request completed don't update pool
|
67
|
+
if ! hostnames
|
68
|
+
puts "Provisioning seemed to have failed for #{req}"
|
69
|
+
puts "Removing request #{req} from pool #{key}"
|
70
|
+
false
|
71
|
+
else
|
72
|
+
pools[key]['pool_instances'] = value['pool_instances'] + hostnames
|
73
|
+
false
|
74
|
+
end
|
75
|
+
else
|
76
|
+
# has not completed
|
77
|
+
# keep the request, since it is not finished
|
78
|
+
puts "The request #{req} is still running"
|
51
79
|
req
|
52
80
|
end
|
53
81
|
end
|
@@ -63,7 +91,8 @@ def create_pools
|
|
63
91
|
pools[key]['requests'] = reqs
|
64
92
|
end
|
65
93
|
end
|
66
|
-
|
94
|
+
# prevents updates from occuring when they are not required
|
95
|
+
store.save if store_changed?(@old_store, store)
|
67
96
|
end
|
68
97
|
|
69
98
|
# @return [Boolean] - true if the host is alive and listening
|
@@ -107,22 +136,18 @@ end
|
|
107
136
|
|
108
137
|
# @return [Boolean] - true if options are valid
|
109
138
|
def valid_options?
|
110
|
-
options['
|
111
|
-
File.exist?(options['pool_file']) &&
|
112
|
-
options['project_id'].to_i > 0 &&
|
113
|
-
options['snippet_id'].to_i > 0
|
139
|
+
options['project_id'].to_i > 0
|
114
140
|
end
|
115
141
|
|
116
142
|
## main entry point
|
117
143
|
|
118
144
|
OptionParser.new do |opts|
|
119
|
-
opts.program_name = 'create-pool'
|
120
145
|
opts.on_head(<<-EOF
|
121
146
|
|
122
147
|
Summary: A tool used to create a pool of vra systems
|
123
148
|
|
124
149
|
Example:
|
125
|
-
#{__FILE__} -f pool_file.yaml -p 33 -
|
150
|
+
#{__FILE__} -f pool_file.yaml -p 33 -f vmpool
|
126
151
|
|
127
152
|
If you wish to store the config in a file, this script will read vmpool_config.yaml
|
128
153
|
for the same configs.
|
@@ -135,9 +160,6 @@ Summary: A tool used to create a pool of vra systems
|
|
135
160
|
opts.on('-p', '--project-id ID', "The gitlab project id") do |c|
|
136
161
|
options['project_id'] = c.to_i
|
137
162
|
end
|
138
|
-
opts.on('-s', '--snippet-id ID', "The gitlab project snippet id") do |c|
|
139
|
-
options['snippet_id'] = c.to_i
|
140
|
-
end
|
141
163
|
|
142
164
|
end.parse!
|
143
165
|
|
data/kitchen-vmpool.gemspec
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
lib = File.expand_path("../lib", __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'kitchen-vmpool/version'
|
4
5
|
|
5
6
|
Gem::Specification.new do |spec|
|
6
7
|
spec.name = "kitchen-vmpool"
|
7
|
-
spec.version =
|
8
|
+
spec.version = KitchenVmpool::VERSION
|
8
9
|
spec.authors = ["Corey Osman"]
|
9
10
|
spec.email = ["corey@nwops.io"]
|
10
11
|
|
@@ -19,17 +19,21 @@
|
|
19
19
|
#
|
20
20
|
require 'kitchen'
|
21
21
|
require "kitchen/version"
|
22
|
+
require 'kitchen/logging'
|
22
23
|
require 'kitchen/driver/base'
|
24
|
+
require 'kitchen-vmpool/version'
|
25
|
+
|
23
26
|
module Kitchen
|
24
27
|
module Driver
|
25
28
|
|
26
29
|
class PoolMemberNotFound < Exception; end
|
27
30
|
|
28
31
|
class Vmpool < Kitchen::Driver::Base
|
29
|
-
|
32
|
+
include Kitchen::Logging
|
33
|
+
|
34
|
+
plugin_version KitchenVmpool::VERSION
|
30
35
|
|
31
36
|
default_config :pool_name, 'pool1'
|
32
|
-
default_config :pool_file, 'vmpool.yaml'
|
33
37
|
default_config :state_store, 'file'
|
34
38
|
default_config :store_options, {}
|
35
39
|
default_config :reuse_instances, false
|
@@ -40,7 +44,7 @@ module Kitchen
|
|
40
44
|
|
41
45
|
# (see Base#create)
|
42
46
|
def create(state)
|
43
|
-
state[:hostname] =
|
47
|
+
state[:hostname] = take_pool_member
|
44
48
|
end
|
45
49
|
|
46
50
|
# (see Base#destroy)
|
@@ -53,11 +57,12 @@ module Kitchen
|
|
53
57
|
private
|
54
58
|
|
55
59
|
# @return [String] - a random host from the list of systems
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
+
# mark them used so nobody else can use it
|
61
|
+
def take_pool_member
|
62
|
+
member = pool_hosts.sample
|
63
|
+
raise PoolMemberNotFound.new("No pool members exist for #{config[:pool_name]}, please create some pool members") unless member
|
60
64
|
mark_used(member)
|
65
|
+
info("Pool member #{member} was selected")
|
61
66
|
return member
|
62
67
|
end
|
63
68
|
|
@@ -80,37 +85,46 @@ module Kitchen
|
|
80
85
|
|
81
86
|
# @return Array[String] - a list of host names in the pool
|
82
87
|
def pool_hosts
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
+
pool['pool_instances'] ||= []
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return Array[String] - a list of used host names in the pool
|
92
|
+
def used_hosts
|
93
|
+
pool['used_instances'] ||= []
|
88
94
|
end
|
89
95
|
|
90
96
|
# @param name [String] - the hostname to mark not used
|
91
97
|
# @return Array[String] - list of unused instances
|
92
98
|
def mark_unused(name)
|
93
|
-
|
94
|
-
|
99
|
+
if config[:reuse_instances]
|
100
|
+
info("Marking pool member #{name} as unused")
|
101
|
+
used_hosts.delete(name)
|
102
|
+
pool_hosts << name unless pool_hosts.include?(name)
|
103
|
+
end
|
95
104
|
store.save
|
96
|
-
|
105
|
+
pool_hosts
|
97
106
|
end
|
98
107
|
|
99
108
|
# @param name [String] - the hostname to mark used
|
100
109
|
# @return Array[String] - list of used instances
|
101
110
|
def mark_used(name)
|
102
|
-
pool
|
103
|
-
|
111
|
+
debug("Marking pool member #{name} as used")
|
112
|
+
# ideally the member should not already be in this array
|
113
|
+
# but just in case we will protect against that
|
114
|
+
pool_hosts.delete(name)
|
115
|
+
used_hosts << name unless used_hosts.include?(name)
|
104
116
|
store.save
|
105
|
-
|
117
|
+
used_hosts
|
106
118
|
end
|
107
119
|
|
108
120
|
# @return [Hash] - a store hash that contains one or more pools
|
109
121
|
def store
|
110
122
|
@store ||= begin
|
111
123
|
# load the store adapter and create a new instance of the store
|
112
|
-
|
113
|
-
|
124
|
+
name = config[:state_store].split('_').map(&:capitalize).join('')
|
125
|
+
store = sprintf("%s%s", name, 'Store')
|
126
|
+
store_file = "#{config[:state_store]}_store"
|
127
|
+
require "kitchen/driver/vmpool_stores/#{store_file}"
|
114
128
|
klass = Object.const_get("Kitchen::Driver::VmpoolStores::#{store}")
|
115
129
|
# create a new instance of the store with the provided options
|
116
130
|
store_opts = config[:store_options]
|
@@ -125,3 +139,24 @@ module Kitchen
|
|
125
139
|
end
|
126
140
|
end
|
127
141
|
end
|
142
|
+
|
143
|
+
require 'gitlab'
|
144
|
+
# monkey patch error in error code until it is fixed upstream
|
145
|
+
module Gitlab
|
146
|
+
module Error
|
147
|
+
class ResponseError
|
148
|
+
# Human friendly message.
|
149
|
+
#
|
150
|
+
# @return [String]
|
151
|
+
private
|
152
|
+
def build_error_message
|
153
|
+
parsed_response = @response.parsed_response
|
154
|
+
message = parsed_response.respond_to?(:message) ? parsed_response.message : parsed_response['message']
|
155
|
+
message = parsed_response.error unless message
|
156
|
+
"Server responded with code #{@response.code}, message: " \
|
157
|
+
"#{handle_message(message)}. " \
|
158
|
+
"Request URI: #{@response.request.base_uri}#{@response.request.path}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'kitchen/logger'
|
3
|
+
require 'kitchen'
|
4
|
+
require 'kitchen/logging'
|
5
|
+
|
6
|
+
module Kitchen
|
7
|
+
module Driver
|
8
|
+
module VmpoolStores
|
9
|
+
class BaseStore
|
10
|
+
attr_reader :pool_file, :pool_data
|
11
|
+
include Kitchen::Logging
|
12
|
+
|
13
|
+
def update(content = nil)
|
14
|
+
#info("Updating vmpool data")
|
15
|
+
write_content(content)
|
16
|
+
read
|
17
|
+
end
|
18
|
+
|
19
|
+
def create
|
20
|
+
#info("Creating new vmpool data")
|
21
|
+
write_content(base_content)
|
22
|
+
read
|
23
|
+
end
|
24
|
+
|
25
|
+
def read
|
26
|
+
#info("Reading vmpool data")
|
27
|
+
read_content
|
28
|
+
end
|
29
|
+
|
30
|
+
def reread
|
31
|
+
pool_data(true)
|
32
|
+
end
|
33
|
+
|
34
|
+
def save
|
35
|
+
#info("Saving vmpool data")
|
36
|
+
write_content
|
37
|
+
read
|
38
|
+
end
|
39
|
+
|
40
|
+
def pool_data(refresh = false)
|
41
|
+
@pool_data = nil if refresh
|
42
|
+
@pool_data ||= YAML.load(read_content)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def read_content
|
48
|
+
raise NotImplementedError
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,11 +1,9 @@
|
|
1
1
|
require 'yaml'
|
2
|
-
|
2
|
+
require "kitchen/driver/vmpool_stores/base_store"
|
3
3
|
module Kitchen
|
4
4
|
module Driver
|
5
5
|
module VmpoolStores
|
6
|
-
class FileStore
|
7
|
-
|
8
|
-
attr_reader :pool_file
|
6
|
+
class FileStore < BaseStore
|
9
7
|
|
10
8
|
# @option pool_file [String] - the file path that holds the pool information
|
11
9
|
def initialize(options = nil)
|
@@ -14,30 +12,6 @@ module Kitchen
|
|
14
12
|
@pool_file = options['pool_file']
|
15
13
|
end
|
16
14
|
|
17
|
-
def update(content)
|
18
|
-
write_content(content)
|
19
|
-
read
|
20
|
-
end
|
21
|
-
|
22
|
-
def create
|
23
|
-
write_content(base_content)
|
24
|
-
read
|
25
|
-
end
|
26
|
-
|
27
|
-
def read
|
28
|
-
puts "Reading snippet"
|
29
|
-
read_content
|
30
|
-
end
|
31
|
-
|
32
|
-
def save
|
33
|
-
write_content
|
34
|
-
read
|
35
|
-
end
|
36
|
-
|
37
|
-
def pool_data
|
38
|
-
@pool_data ||= YAML.load(read_content)
|
39
|
-
end
|
40
|
-
|
41
15
|
private
|
42
16
|
|
43
17
|
def base_content
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'gitlab'
|
2
|
+
require "kitchen/driver/vmpool_stores/base_store"
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module Kitchen
|
6
|
+
module Driver
|
7
|
+
module VmpoolStores
|
8
|
+
class GitlabCommitStore < BaseStore
|
9
|
+
|
10
|
+
attr_accessor :project_id
|
11
|
+
attr_reader :pool_file, :branch
|
12
|
+
|
13
|
+
# @option project_id [Integer] - the project id in gitlab
|
14
|
+
# @option commit_id [Integer] - the snipppet id in the gitlab project
|
15
|
+
# @option pool_file [String] - the snipppet file name
|
16
|
+
def initialize(options = nil)
|
17
|
+
options ||= { project_id: nil, pool_file: 'vmpool.yaml'}
|
18
|
+
raise ArgumentError.new("You must pass the project_id option") unless options['project_id'].to_i > 0
|
19
|
+
@project_id = options['project_id'] #ie. 89
|
20
|
+
@pool_file = options['pool_file'] || 'vmpool.yaml'
|
21
|
+
@branch = 'master'
|
22
|
+
end
|
23
|
+
|
24
|
+
def update(content = nil)
|
25
|
+
#info("Updating vmpool data")
|
26
|
+
update_file
|
27
|
+
read
|
28
|
+
end
|
29
|
+
|
30
|
+
def create
|
31
|
+
#info("Creating new vmpool data commit")
|
32
|
+
create_file unless file_exists?
|
33
|
+
read
|
34
|
+
end
|
35
|
+
|
36
|
+
def save
|
37
|
+
# info("Saving vmpool data")
|
38
|
+
update_file
|
39
|
+
read
|
40
|
+
end
|
41
|
+
|
42
|
+
def file_exists?(project = project_id, file = pool_file)
|
43
|
+
read_content(project, file)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def client
|
49
|
+
@client ||= Gitlab.client
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_file(project = project_id)
|
53
|
+
actions = [{
|
54
|
+
"action" => "create",
|
55
|
+
"file_path" => pool_file,
|
56
|
+
"content" => {}.to_yaml
|
57
|
+
}]
|
58
|
+
client.create_commit(project, branch, "update vmpool data", actions)
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def update_file(project = project_id)
|
63
|
+
actions = [{
|
64
|
+
"action" => "update",
|
65
|
+
"file_path" => pool_file,
|
66
|
+
"content" => pool_data.to_yaml
|
67
|
+
}]
|
68
|
+
client.create_commit(project, branch, "update vmpool data", actions)
|
69
|
+
end
|
70
|
+
|
71
|
+
def read_content(project = project_id, file = pool_file)
|
72
|
+
begin
|
73
|
+
client.file_contents(project, file, branch)
|
74
|
+
rescue Gitlab::Error::NotFound
|
75
|
+
false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -1,8 +1,10 @@
|
|
1
1
|
require 'gitlab'
|
2
|
+
require "kitchen/driver/vmpool_stores/base_store"
|
3
|
+
require 'yaml'
|
2
4
|
module Kitchen
|
3
5
|
module Driver
|
4
6
|
module VmpoolStores
|
5
|
-
class
|
7
|
+
class GitlabSnippetStore < BaseStore
|
6
8
|
|
7
9
|
attr_accessor :project_id, :snippet_id
|
8
10
|
attr_reader :pool_file
|
@@ -11,42 +13,32 @@ module Kitchen
|
|
11
13
|
# @option snippet_id [Integer] - the snipppet id in the gitlab project
|
12
14
|
# @option pool_file [String] - the snipppet file name
|
13
15
|
def initialize(options = nil)
|
14
|
-
options ||= { project_id: nil, snippet_id: nil, pool_file: 'vmpool
|
16
|
+
options ||= { project_id: nil, snippet_id: nil, pool_file: 'vmpool'}
|
15
17
|
raise ArgumentError.new("You must pass the project_id option") unless options['project_id'].to_i > 0
|
16
|
-
raise ArgumentError.new("You must pass the snippet_id option") unless options['snippet_id'].to_i > 0
|
17
18
|
@snippet_id = options['snippet_id'] #ie. 630
|
18
19
|
@project_id = options['project_id'] #ie. 89
|
19
20
|
@pool_file = options['pool_file']
|
20
21
|
end
|
21
22
|
|
22
|
-
def update
|
23
|
+
def update(content = nil)
|
24
|
+
#info("Updating vmpool data")
|
23
25
|
update_snippet
|
24
26
|
read
|
25
27
|
end
|
26
28
|
|
27
29
|
def create
|
28
|
-
|
30
|
+
#info("Creating new vmpool data snippet")
|
31
|
+
snippet = create_snippet
|
32
|
+
@snippet_id = snippet.id
|
29
33
|
read
|
30
34
|
end
|
31
35
|
|
32
|
-
def read
|
33
|
-
puts "Reading snippet"
|
34
|
-
pool_content
|
35
|
-
end
|
36
|
-
|
37
|
-
def pool_data
|
38
|
-
@pool_data ||= YAML.load(pool_content)
|
39
|
-
end
|
40
|
-
|
41
36
|
def save
|
37
|
+
#info("Saving vmpool data")
|
42
38
|
update_snippet
|
43
39
|
read
|
44
40
|
end
|
45
41
|
|
46
|
-
def pool_content
|
47
|
-
read_snippet
|
48
|
-
end
|
49
|
-
|
50
42
|
private
|
51
43
|
|
52
44
|
def client
|
@@ -54,19 +46,20 @@ module Kitchen
|
|
54
46
|
end
|
55
47
|
|
56
48
|
def snippet_exists?(project = project_id)
|
49
|
+
return false unless snippet_id
|
57
50
|
client.snippets(project, {
|
58
51
|
title: 'Virtual Machine Pools',
|
59
52
|
visibility: 'public',
|
60
53
|
file_name: pool_file,
|
61
|
-
code:
|
54
|
+
code: {}.to_yaml})
|
62
55
|
end
|
63
56
|
|
64
57
|
def create_snippet(project = project_id)
|
65
|
-
client.
|
58
|
+
client.create_commit(project, {
|
66
59
|
title: 'Virtual Machine Pools',
|
67
60
|
visibility: 'public',
|
68
61
|
file_name: pool_file,
|
69
|
-
code:
|
62
|
+
code: {}.to_yaml
|
70
63
|
})
|
71
64
|
end
|
72
65
|
|
@@ -83,7 +76,7 @@ module Kitchen
|
|
83
76
|
client.snippets(project).map {|s| s.id }
|
84
77
|
end
|
85
78
|
|
86
|
-
def
|
79
|
+
def read_content(project = project_id, id = snippet_id)
|
87
80
|
client.snippet_content(project, id)
|
88
81
|
end
|
89
82
|
|
data/vmpool.yaml
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kitchen-vmpool
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Corey Osman
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gitlab
|
@@ -69,7 +69,9 @@ dependencies:
|
|
69
69
|
description: When you need to create pools of vms and manage them with test kitchen
|
70
70
|
email:
|
71
71
|
- corey@nwops.io
|
72
|
-
executables:
|
72
|
+
executables:
|
73
|
+
- vra_create.rb
|
74
|
+
- vra_create_pool
|
73
75
|
extensions: []
|
74
76
|
extra_rdoc_files: []
|
75
77
|
files:
|
@@ -83,13 +85,16 @@ files:
|
|
83
85
|
- Rakefile
|
84
86
|
- bin/console
|
85
87
|
- bin/setup
|
88
|
+
- exe/vra_create.rb
|
89
|
+
- exe/vra_create_pool
|
86
90
|
- kitchen-vmpool.gemspec
|
91
|
+
- lib/kitchen-vmpool/version.rb
|
87
92
|
- lib/kitchen/driver/vmpool.rb
|
93
|
+
- lib/kitchen/driver/vmpool_stores/base_store.rb
|
88
94
|
- lib/kitchen/driver/vmpool_stores/file_store.rb
|
89
|
-
- lib/kitchen/driver/vmpool_stores/
|
95
|
+
- lib/kitchen/driver/vmpool_stores/gitlab_commit_store.rb
|
96
|
+
- lib/kitchen/driver/vmpool_stores/gitlab_snippet_store.rb
|
90
97
|
- vmpool.yaml
|
91
|
-
- vra_create.rb
|
92
|
-
- vra_create_pool
|
93
98
|
homepage: https://gitlab.com/nwops/kitchen-vmpool
|
94
99
|
licenses:
|
95
100
|
- MIT
|
data/vra_create.rb
DELETED
@@ -1,156 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
require 'vra'
|
3
|
-
require 'erb'
|
4
|
-
require 'highline/import'
|
5
|
-
require 'openssl'
|
6
|
-
require 'json'
|
7
|
-
require 'yaml'
|
8
|
-
|
9
|
-
# Purpose: Submits a single request to VRA for vm creation
|
10
|
-
module Vra
|
11
|
-
class Client
|
12
|
-
# monkey patch the init method to accept token
|
13
|
-
def initialize(opts)
|
14
|
-
@base_url = opts[:base_url]
|
15
|
-
@username = opts[:username]
|
16
|
-
@password = PasswordMasker.new(opts[:password])
|
17
|
-
@tenant = opts[:tenant]
|
18
|
-
@verify_ssl = opts.fetch(:verify_ssl, true)
|
19
|
-
@bearer_token = PasswordMasker.new(nil)
|
20
|
-
@page_size = opts.fetch(:page_size, 20)
|
21
|
-
|
22
|
-
validate_client_options!
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# monkey patch the init method to accept additional params
|
27
|
-
class CatalogRequest
|
28
|
-
def initialize(client, catalog_id, opts = {})
|
29
|
-
@client = client
|
30
|
-
@catalog_id = catalog_id
|
31
|
-
@cpus = opts[:cpus]
|
32
|
-
@memory = opts[:memory]
|
33
|
-
@requested_for = opts[:requested_for]
|
34
|
-
@lease_days = opts[:lease_days]
|
35
|
-
@notes = opts[:notes]
|
36
|
-
@subtenant_id = opts[:subtenant_id]
|
37
|
-
@additional_params = opts[:additional_params] || Vra::RequestParameters.new
|
38
|
-
@catalog_item = Vra::CatalogItem.new(client, id: catalog_id)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
module VraUtilities
|
44
|
-
def classification
|
45
|
-
ENV['VRA_CLASSIFY']
|
46
|
-
end
|
47
|
-
|
48
|
-
def branch
|
49
|
-
`git rev-parse --abbrev-ref HEAD`.chomp if ENV['USE_BRANCH']
|
50
|
-
end
|
51
|
-
|
52
|
-
def sandbox
|
53
|
-
branch || 'dev'
|
54
|
-
end
|
55
|
-
|
56
|
-
def datacenter
|
57
|
-
ENV['DATACENTER'] || 'Eroc'
|
58
|
-
end
|
59
|
-
|
60
|
-
def vra_email
|
61
|
-
@vra_email ||= ENV['VRA_EMAIL'] || ask('What is your frit email for VRA notifications')
|
62
|
-
end
|
63
|
-
|
64
|
-
def subtenant_id
|
65
|
-
ENV['VRA_SUB_TENANT_ID']
|
66
|
-
end
|
67
|
-
|
68
|
-
def vra_user
|
69
|
-
@vra_user ||= ENV['VRA_USER'] || ask('Enter User: ') {|q| q.echo = true}
|
70
|
-
end
|
71
|
-
|
72
|
-
def vra_pass
|
73
|
-
@vra_pass ||= ENV['VRA_PASS'] || ask('Enter VRA Password: ') {|q| q.echo = 'x'}
|
74
|
-
end
|
75
|
-
|
76
|
-
def base_url
|
77
|
-
@server ||= ENV['VRA_URL']
|
78
|
-
end
|
79
|
-
|
80
|
-
|
81
|
-
# @return [VRA::Client] - creates a new client object and returns it
|
82
|
-
def client
|
83
|
-
@client ||= Vra::Client.new(
|
84
|
-
username: vra_user,
|
85
|
-
password: vra_pass,
|
86
|
-
tenant: 'vsphere.local',
|
87
|
-
base_url: base_url,
|
88
|
-
verify_ssl: false,
|
89
|
-
)
|
90
|
-
end
|
91
|
-
|
92
|
-
# @return Array[String] - returns an array of catalog items
|
93
|
-
def catalog_items
|
94
|
-
client.catalog.all_items.map {|i| {name: i.name, id: i.id}}
|
95
|
-
end
|
96
|
-
|
97
|
-
def request_options
|
98
|
-
{
|
99
|
-
cpus: 1,
|
100
|
-
memory: 4096,
|
101
|
-
requested_for: 'someone@localhost',
|
102
|
-
lease_days: 2,
|
103
|
-
additional_params: request_params,
|
104
|
-
notes: 'Corey Test',
|
105
|
-
subtenant_id: request_data['organization']['subtenantRef']
|
106
|
-
}
|
107
|
-
end
|
108
|
-
|
109
|
-
def request_data
|
110
|
-
@request_data ||= YAML.load_file(@payload_file)
|
111
|
-
end
|
112
|
-
|
113
|
-
def parameters
|
114
|
-
request_data['requestData']['entries'].map {|item| [item['key'], item['value'].values].flatten }
|
115
|
-
end
|
116
|
-
|
117
|
-
def request_params
|
118
|
-
unless @request_params
|
119
|
-
@request_params = Vra::RequestParameters.new
|
120
|
-
parameters.each { |p| @request_params.set(*p)}
|
121
|
-
end
|
122
|
-
@request_params
|
123
|
-
end
|
124
|
-
|
125
|
-
def request_item
|
126
|
-
blueprint = request_data['catalogItemRef']['id']
|
127
|
-
client.catalog.request(blueprint, request_options)
|
128
|
-
end
|
129
|
-
|
130
|
-
# @return [Vra::Request] - returns a request item
|
131
|
-
def submit_new_request(payload_file)
|
132
|
-
@payload_file = payload_file
|
133
|
-
request_item.submit
|
134
|
-
end
|
135
|
-
|
136
|
-
require 'optparse'
|
137
|
-
|
138
|
-
def run
|
139
|
-
options = {}
|
140
|
-
OptionParser.new do |opts|
|
141
|
-
opts.program_name = 'vra-pool'
|
142
|
-
opts.on_head(<<-EOF
|
143
|
-
|
144
|
-
Summary: A tool used to provision systems in VRA
|
145
|
-
EOF
|
146
|
-
)
|
147
|
-
opts.on('-n', '--node-file FILE', "Load the request data from this file and create it") do |c|
|
148
|
-
options[:node_file] = c
|
149
|
-
@payload_file = c
|
150
|
-
submit_new_request if File.exist?(@payload_file) # create the request
|
151
|
-
end
|
152
|
-
end.parse!
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
include VraUtilities
|