kitchen-vmpool 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|