elevage 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +21 -0
- data/.rubocop.yml +8 -0
- data/.ruby-version +1 -0
- data/.simplecov +8 -0
- data/.travis.yml +3 -0
- data/.yardoc/checksums +12 -0
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/.yardoc/proxy_types +0 -0
- data/Gemfile +4 -0
- data/Guardfile +10 -0
- data/LICENSE.txt +203 -0
- data/README.md +112 -0
- data/Rakefile +20 -0
- data/bin/elevage +4 -0
- data/coverage/.resultset.json.lock +0 -0
- data/doc/Elevage/Build.html +435 -0
- data/doc/Elevage/CLI.html +282 -0
- data/doc/Elevage/Environment.html +950 -0
- data/doc/Elevage/Generate.html +346 -0
- data/doc/Elevage/Health.html +359 -0
- data/doc/Elevage/New.html +411 -0
- data/doc/Elevage/Platform.html +1119 -0
- data/doc/Elevage/Provisioner.html +804 -0
- data/doc/Elevage/ProvisionerRunQueue.html +765 -0
- data/doc/Elevage/Runner.html +319 -0
- data/doc/Elevage.html +501 -0
- data/doc/_index.html +239 -0
- data/doc/class_list.html +58 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +339 -0
- data/doc/file.README.html +187 -0
- data/doc/file_list.html +60 -0
- data/doc/frames.html +26 -0
- data/doc/index.html +187 -0
- data/doc/js/app.js +219 -0
- data/doc/js/full_list.js +181 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +369 -0
- data/doc/top-level-namespace.html +112 -0
- data/elevage.gemspec +39 -0
- data/features/archive +314 -0
- data/features/build.feature +237 -0
- data/features/elevage.feature +24 -0
- data/features/generate.feature +235 -0
- data/features/health_env_failure.feature +292 -0
- data/features/health_failure.feature +291 -0
- data/features/health_success.feature +279 -0
- data/features/list.feature +315 -0
- data/features/new.feature +68 -0
- data/features/step_definitions/elevage_steps.rb +27 -0
- data/features/support/env.rb +9 -0
- data/lib/elevage/build.rb +109 -0
- data/lib/elevage/constants.rb +113 -0
- data/lib/elevage/environment.rb +223 -0
- data/lib/elevage/generate.rb +48 -0
- data/lib/elevage/health.rb +27 -0
- data/lib/elevage/new.rb +30 -0
- data/lib/elevage/platform.rb +105 -0
- data/lib/elevage/provisioner.rb +169 -0
- data/lib/elevage/provisionerrunqueue.rb +114 -0
- data/lib/elevage/runner.rb +39 -0
- data/lib/elevage/templates/compute.yml.tt +18 -0
- data/lib/elevage/templates/environment.yml.tt +20 -0
- data/lib/elevage/templates/network.yml.tt +16 -0
- data/lib/elevage/templates/platform.yml.tt +110 -0
- data/lib/elevage/templates/vcenter.yml.tt +77 -0
- data/lib/elevage/version.rb +4 -0
- data/lib/elevage.rb +45 -0
- data/spec/spec_helper.rb +4 -0
- metadata +357 -0
@@ -0,0 +1,223 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'resolv'
|
3
|
+
|
4
|
+
require_relative 'constants'
|
5
|
+
require_relative 'platform'
|
6
|
+
require_relative 'provisioner'
|
7
|
+
|
8
|
+
# rubocop:disable ClassLength
|
9
|
+
module Elevage
|
10
|
+
# Environment class
|
11
|
+
class Environment
|
12
|
+
attr_accessor :name
|
13
|
+
attr_accessor :vcenter
|
14
|
+
attr_accessor :components
|
15
|
+
attr_accessor :nodenameconvention
|
16
|
+
|
17
|
+
# rubocop:disable LineLength
|
18
|
+
def initialize(env)
|
19
|
+
# Confirm environment has been defined in the platform
|
20
|
+
platform = Elevage::Platform.new
|
21
|
+
fail(IOError, ERR[:env_not_defined]) unless platform.environments.include?(env)
|
22
|
+
# Confirm environment file exists
|
23
|
+
envfile = ENV_FOLDER + env.to_s + '.yml'
|
24
|
+
fail(IOError, ERR[:no_env_file]) unless env_file_exists?(envfile)
|
25
|
+
# Build environment hash from environment and platform defintion files
|
26
|
+
environment = build_env(env, YAML.load_file(envfile).fetch('environment'), platform)
|
27
|
+
# Populate class variables
|
28
|
+
@name = env
|
29
|
+
@vcenter = environment['vcenter']
|
30
|
+
@components = environment['components']
|
31
|
+
@nodenameconvention = platform.nodenameconvention
|
32
|
+
end
|
33
|
+
# rubocop:enable LineLength
|
34
|
+
|
35
|
+
# Public: Environment class method
|
36
|
+
# Returns multiline string = IP, fqdn, runlist
|
37
|
+
def list_nodes
|
38
|
+
nodes = @vcenter['destfolder'].to_s + "\n"
|
39
|
+
@components.each do |component, _config|
|
40
|
+
(1..@components[component]['count']).each do |i|
|
41
|
+
nodes += @components[component]['addresses'][i - 1].ljust(18, ' ') +
|
42
|
+
node_name(component, i) + @vcenter['domain'] + ' ' +
|
43
|
+
@components[component]['runlist'].to_s + "\n"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
nodes
|
47
|
+
end
|
48
|
+
|
49
|
+
# rubocop:disable MethodLength, LineLength, CyclomaticComplexity, PerceivedComplexity
|
50
|
+
def healthy?
|
51
|
+
platform = Elevage::Platform.new
|
52
|
+
health = ''
|
53
|
+
health += MSG[:invalid_env_vcenter] if @vcenter.nil?
|
54
|
+
@components.each do |component, v|
|
55
|
+
health += MSG[:invalid_env_network] if v['network'].nil?
|
56
|
+
health += MSG[:invalid_env_count] unless (0..POOL_LIMIT).member?(v['count'])
|
57
|
+
health += MSG[:invalid_env_compute] if v['compute'].nil?
|
58
|
+
health += MSG[:invalid_env_ip] if v['count'] != v['addresses'].size
|
59
|
+
if v['addresses'].nil?
|
60
|
+
health += MSG[:invalid_env_ip]
|
61
|
+
else
|
62
|
+
v['addresses'].each { |ip| health += MSG[:invalid_env_ip] unless Resolv::IPv4::Regex.match(ip) }
|
63
|
+
end
|
64
|
+
health += MSG[:invalid_env_tier] unless platform.tiers.include?(v['tier'])
|
65
|
+
health += MSG[:invalid_env_image] if v['image'].nil?
|
66
|
+
health += MSG[:invalid_env_port] unless v['port'].is_a?(Integer) || v['port'].nil?
|
67
|
+
health += MSG[:invalid_env_runlist] if v['runlist'].nil? || v['runlist'].empty?
|
68
|
+
health += MSG[:invalid_env_componentrole] unless v['componentrole'].include?('#') if v['componentrole']
|
69
|
+
health += MSG[:env_component_mismatch] unless platform.components.include?(component)
|
70
|
+
end
|
71
|
+
health += MSG[:env_component_mismatch] unless platform.components.size == @components.size
|
72
|
+
if health.length > 0
|
73
|
+
puts health + "\n#{health.lines.count} environment offense(s) detected"
|
74
|
+
false
|
75
|
+
else
|
76
|
+
true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
# rubocop:enable MethodLength, LineLength, CyclomaticComplexity, PerceivedComplexity
|
80
|
+
|
81
|
+
# Public: basic class puts string output
|
82
|
+
def to_s
|
83
|
+
puts @name
|
84
|
+
puts @vcenter.to_yaml
|
85
|
+
puts @components.to_yaml
|
86
|
+
puts @nodenameconvention.to_yaml
|
87
|
+
end
|
88
|
+
|
89
|
+
# Public: method to request provisioning of all or a portion
|
90
|
+
# of the environment
|
91
|
+
# rubocop:disable MethodLength, LineLength, CyclomaticComplexity, PerceivedComplexity
|
92
|
+
def provision(type: all, tier: nil, component: nil, instance: nil, options: nil)
|
93
|
+
# Create the ProvisionerRunQueue to batch up our tasks
|
94
|
+
runner = ProvisionerRunQueue.new
|
95
|
+
|
96
|
+
# Modify behavior for dry-run (no concurrency)
|
97
|
+
if !options['dry-run']
|
98
|
+
runner.max_concurrent = options[:concurrency]
|
99
|
+
else
|
100
|
+
puts "Dry run requested, forcing concurrency to '1'."
|
101
|
+
runner.max_concurrent = 1
|
102
|
+
end
|
103
|
+
|
104
|
+
@components.each do |component_name, component_data|
|
105
|
+
next unless type.eql?(:all) || component_data['tier'].match(/#{tier}/i) && component_name.match(/#{component}/i)
|
106
|
+
|
107
|
+
1.upto(component_data['addresses'].count) do |component_instance|
|
108
|
+
next unless instance == component_instance || instance.nil?
|
109
|
+
|
110
|
+
instance_name = node_name(component_name, component_instance)
|
111
|
+
|
112
|
+
# Create the Provisioner
|
113
|
+
provisioner = Elevage::Provisioner.new(instance_name, component_data, component_instance, self, options)
|
114
|
+
|
115
|
+
# Add it to the queue
|
116
|
+
runner.provisioners << provisioner
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
runner.to_s if options['dry-run']
|
122
|
+
|
123
|
+
# Process the queue
|
124
|
+
runner.run
|
125
|
+
end
|
126
|
+
# rubocop:enable MethodLength, LineLength, CyclomaticComplexity, PerceivedComplexity
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
# Private: updates env hash with necessary info from Platform files.
|
131
|
+
# This is a blend of env and Platform info needed to construct
|
132
|
+
# Environment class object
|
133
|
+
#
|
134
|
+
# Params
|
135
|
+
# env: string passed from commend line, simple environment name
|
136
|
+
# env_yaml: hash from requested environment.yml
|
137
|
+
# platform: Platform class object built from standard
|
138
|
+
# platform definition files
|
139
|
+
#
|
140
|
+
# Returns Hash: updated env_yaml hash
|
141
|
+
# rubocop:disable MethodLength, LineLength
|
142
|
+
def build_env(env, env_yaml, platform)
|
143
|
+
# substitute vcenter resources from vcenter.yml for location defined in environment file
|
144
|
+
env_yaml['vcenter'] = platform.vcenter[env_yaml['vcenter']]
|
145
|
+
# merge component resources from environment file and platform definition
|
146
|
+
# platform info will be inserted where not found in env files, env overrides will be unchanged
|
147
|
+
#
|
148
|
+
# Note: this function does not do error checking for components that exist in env file but
|
149
|
+
# not in Platform definition. Such files will be retained but not have any Platform
|
150
|
+
# component info. The Build command will run error checking before building, but to support
|
151
|
+
# the debugging value of the list command only hash.merge! is performed at this point.
|
152
|
+
platform.components.each do |component, _config|
|
153
|
+
env_yaml['components'][component].merge!(platform.components[component]) { |_key, v1, _v2| v1 } unless env_yaml['components'][component].nil?
|
154
|
+
end
|
155
|
+
# substitute network and components for specified values from platform definition files
|
156
|
+
env_yaml['components'].each do |component, _config|
|
157
|
+
env_yaml['components'][component]['network'] = platform.network[env_yaml['components'][component]['network']]
|
158
|
+
env_yaml['components'][component]['compute'] = platform.compute[env_yaml['components'][component]['compute']]
|
159
|
+
unless env_yaml['components'][component]['runlist'].nil?
|
160
|
+
env_yaml['components'][component]['runlist'] = run_list(env_yaml['components'][component]['runlist'], env_yaml['components'][component]['componentrole'], component)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
unless env_yaml['vcenter'].nil?
|
164
|
+
# append env name to destination folder if appendenv == true
|
165
|
+
env_yaml['vcenter']['destfolder'] += (env_yaml['vcenter']['appendenv'] ? '/' + env.to_s : '')
|
166
|
+
# prepend app name to domain if appenddomain == true
|
167
|
+
env_yaml['vcenter']['appenddomain'] ? env_yaml['vcenter']['domain'] = '.' + platform.name + '.' + env_yaml['vcenter']['domain'] : ''
|
168
|
+
end
|
169
|
+
env_yaml
|
170
|
+
end
|
171
|
+
# rubocop:enable MethodLength, LineLength
|
172
|
+
|
173
|
+
# Private: construct a node hostname from parameters
|
174
|
+
#
|
175
|
+
# Params
|
176
|
+
# component: Hash, environment components
|
177
|
+
# instance: integer, passed from loop iterator
|
178
|
+
#
|
179
|
+
# Returns hostname as String
|
180
|
+
# rubocop:disable MethodLength
|
181
|
+
def node_name(component, instance)
|
182
|
+
name = ''
|
183
|
+
@nodenameconvention.each do |i|
|
184
|
+
case i
|
185
|
+
when 'environment'
|
186
|
+
name += @name
|
187
|
+
when 'component'
|
188
|
+
name += component
|
189
|
+
when 'instance'
|
190
|
+
name += instance.to_s.rjust(2, '0')
|
191
|
+
when 'geo'
|
192
|
+
name += @vcenter['geo'].to_s[0]
|
193
|
+
else
|
194
|
+
name += i
|
195
|
+
end
|
196
|
+
end
|
197
|
+
name
|
198
|
+
end
|
199
|
+
# rubocop:enable MethodLength
|
200
|
+
|
201
|
+
# Private: Constructs the node runlist from parameters
|
202
|
+
#
|
203
|
+
# Params
|
204
|
+
# list: Array of strings from component runlist hash key value
|
205
|
+
# componentrole: String value from component, performs simple
|
206
|
+
# string substitution of for component string
|
207
|
+
# in component role string
|
208
|
+
# component: String, component name
|
209
|
+
#
|
210
|
+
# Returns runlist as String
|
211
|
+
# rubocop:disable LineLength
|
212
|
+
def run_list(list, componentrole, component)
|
213
|
+
list.join(',') + (componentrole ? ',' + componentrole.gsub('#', component) : '')
|
214
|
+
end
|
215
|
+
# rubocop:enable LineLength
|
216
|
+
|
217
|
+
def env_file_exists?(env_file)
|
218
|
+
fail(IOError, ERR[:no_environment_file]) unless File.file?(env_file)
|
219
|
+
true
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
# rubocop:enable ClassLength
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'thor/group'
|
2
|
+
|
3
|
+
module Elevage
|
4
|
+
# Create new environment desired state files from platform template
|
5
|
+
class Generate < Thor::Group
|
6
|
+
include Thor::Actions
|
7
|
+
argument :env
|
8
|
+
|
9
|
+
def self.source_root
|
10
|
+
File.dirname(__FILE__)
|
11
|
+
end
|
12
|
+
|
13
|
+
# rubocop:disable MethodLength, CyclomaticComplexity, PerceivedComplexity
|
14
|
+
def create_environment
|
15
|
+
fail IOError, ERR[:env_exists] if File.file?(ENV_FOLDER + env + '.yml')
|
16
|
+
platform = Elevage::Platform.new
|
17
|
+
platformfile = File.open(YML_PLATFORM, 'r')
|
18
|
+
#
|
19
|
+
# The things from here forward I would rather have in the template file
|
20
|
+
# but that is even uglier, trying to get formatting correct
|
21
|
+
# will need to investigate some POWER erb skills to clean this up
|
22
|
+
@env_pools = ''
|
23
|
+
@env_components = ''
|
24
|
+
line = ''
|
25
|
+
line = platformfile.gets until line =~ /pools/
|
26
|
+
platform.pools.each do |k, _v|
|
27
|
+
line = platformfile.gets until line.include?(k)
|
28
|
+
@env_pools += line
|
29
|
+
next_line = platformfile.gets
|
30
|
+
@env_pools += "#{next_line}" if next_line.include?('<<')
|
31
|
+
@env_pools += " network:\n\n"
|
32
|
+
end
|
33
|
+
line = platformfile.gets until line =~ /components/
|
34
|
+
platform.components.each do |k, v|
|
35
|
+
line = platformfile.gets until line.include?(k)
|
36
|
+
@env_components += line
|
37
|
+
next_line = platformfile.gets
|
38
|
+
@env_components += "#{next_line}" if next_line.include?('<<')
|
39
|
+
@env_components += " addresses:\n"
|
40
|
+
(1..v['count']).each { @env_components += " -\n" }
|
41
|
+
@env_components += "\n"
|
42
|
+
end
|
43
|
+
template(TEMPLATE_ENV, ENV_FOLDER + env + '.yml')
|
44
|
+
puts "#{env}.yml added in environments folder"
|
45
|
+
end
|
46
|
+
# rubocop:enable MethodLength, CyclomaticComplexity, PerceivedComplexity
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'thor/group'
|
2
|
+
|
3
|
+
module Elevage
|
4
|
+
# Evaluate health of platform definition files
|
5
|
+
class Health < Thor::Group
|
6
|
+
include Thor::Actions
|
7
|
+
|
8
|
+
def self.source_root
|
9
|
+
File.dirname(__FILE__)
|
10
|
+
end
|
11
|
+
|
12
|
+
# rubocop:disable LineLength
|
13
|
+
def check_platform
|
14
|
+
@platform = Elevage::Platform.new
|
15
|
+
puts @platform.healthy? ? MSG_HEALTHY : fail(IOError, ERR[:fail_health_check])
|
16
|
+
end
|
17
|
+
# rubocop:enable LineLength
|
18
|
+
|
19
|
+
# rubocop:disable LineLength
|
20
|
+
def check_environments
|
21
|
+
@platform.environments.each do |env|
|
22
|
+
puts Elevage::Environment.new(env).healthy? ? (env + MSG_ENV_HEALTHY) : fail(IOError, ERR[:fail_health_check])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
# rubocop:enable LineLength
|
26
|
+
end
|
27
|
+
end
|
data/lib/elevage/new.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'thor/group'
|
2
|
+
|
3
|
+
module Elevage
|
4
|
+
# Create new platform definition files and environments folder structure
|
5
|
+
class New < Thor::Group
|
6
|
+
include Thor::Actions
|
7
|
+
|
8
|
+
argument :platform
|
9
|
+
|
10
|
+
def self.source_root
|
11
|
+
File.dirname(__FILE__)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Confirm command is not being run in folder with
|
15
|
+
# existing platform definition
|
16
|
+
def already_exists?
|
17
|
+
File.file?(YML_PLATFORM) && fail(IOError, ERR[:platform_exists])
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_platform_file
|
21
|
+
template(TEMPLATE_PLATFORM, YML_PLATFORM)
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_infrastructure_files
|
25
|
+
template(TEMPLATE_VCENTER, YML_VCENTER)
|
26
|
+
template(TEMPLATE_NETWORK, YML_NETWORK)
|
27
|
+
template(TEMPLATE_COMPUTE, YML_COMPUTE)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# rubocop:disable LineLength
|
2
|
+
require 'yaml'
|
3
|
+
require 'resolv'
|
4
|
+
require 'English'
|
5
|
+
|
6
|
+
module Elevage
|
7
|
+
# Platform class
|
8
|
+
class Platform
|
9
|
+
attr_accessor :name, :description
|
10
|
+
attr_accessor :environments
|
11
|
+
attr_accessor :tiers
|
12
|
+
attr_accessor :nodenameconvention
|
13
|
+
attr_accessor :pools
|
14
|
+
attr_accessor :components
|
15
|
+
attr_accessor :vcenter
|
16
|
+
attr_accessor :network
|
17
|
+
attr_accessor :compute
|
18
|
+
|
19
|
+
# rubocop:disable MethodLength
|
20
|
+
def initialize
|
21
|
+
fail unless platform_files_exists?
|
22
|
+
platform = YAML.load_file(YML_PLATFORM).fetch('platform')
|
23
|
+
@name = platform['name']
|
24
|
+
@description = platform['description']
|
25
|
+
@environments = platform['environments']
|
26
|
+
@tiers = platform['tiers']
|
27
|
+
@nodenameconvention = platform['nodenameconvention']
|
28
|
+
@pools = platform['pools']
|
29
|
+
@components = platform['components']
|
30
|
+
@vcenter = YAML.load_file(YML_VCENTER).fetch('vcenter')
|
31
|
+
@network = YAML.load_file(YML_NETWORK).fetch('network')
|
32
|
+
@compute = YAML.load_file(YML_COMPUTE).fetch('compute')
|
33
|
+
end
|
34
|
+
# rubocop:enable MethodLength
|
35
|
+
|
36
|
+
# rubocop:disable MethodLength, LineLength, CyclomaticComplexity, PerceivedComplexity, AmbiguousOperator
|
37
|
+
def healthy?
|
38
|
+
health = ''
|
39
|
+
# Array of string checked for empty values
|
40
|
+
health += MSG[:empty_environments] unless @environments.all?
|
41
|
+
health += MSG[:empty_tiers] unless @tiers.all?
|
42
|
+
health += MSG[:empty_nodenameconvention] unless @nodenameconvention.all?
|
43
|
+
# Loop through all pool definitions, check for valid settings
|
44
|
+
@pools.each do |_pool, v|
|
45
|
+
health += MSG[:pool_count_size] unless (0..POOL_LIMIT).member?(v['count'])
|
46
|
+
health += MSG[:invalid_tiers] unless @tiers.include?(v['tier'])
|
47
|
+
health += MSG[:no_image_ref] if v['image'].nil?
|
48
|
+
health += MSG[:invalid_compute] unless @compute.key?(v['compute'])
|
49
|
+
health += MSG[:invalid_port] if v['port'].nil?
|
50
|
+
health += MSG[:invalid_runlist] unless v['runlist'].all?
|
51
|
+
health += MSG[:invalid_componentrole] unless v['componentrole'].include?('#') if v['componentrole']
|
52
|
+
end
|
53
|
+
# Loop through all vcenter definitions, check for valid settings
|
54
|
+
@vcenter.each do |_vcenter, v|
|
55
|
+
health += MSG[:invalid_geo] if v['geo'].nil?
|
56
|
+
health += MSG[:invalid_timezone] unless (0..TIMEZONE_LIMIT).member?(v['timezone'].to_i)
|
57
|
+
health += MSG[:invalid_host] if v['host'].nil?
|
58
|
+
health += MSG[:invalid_datacenter] if v['datacenter'].nil?
|
59
|
+
health += MSG[:invalid_imagefolder] if v['imagefolder'].nil?
|
60
|
+
health += MSG[:invalid_destfolder] if v['destfolder'].nil?
|
61
|
+
health += MSG[:invalid_appendenv] unless v['appendenv'] == true || v['appendenv'] == false
|
62
|
+
health += MSG[:invalid_appenddomain] unless v['appenddomain'] == true || v['appenddomain'] == false
|
63
|
+
health += MSG[:empty_datastores] unless v['datastores'].all?
|
64
|
+
health += MSG[:invalid_domain] if v['domain'].nil?
|
65
|
+
v['dnsips'].each { |ip| health += MSG[:invalid_ip] unless Resolv::IPv4::Regex.match(ip) }
|
66
|
+
end
|
67
|
+
# Loop through all network definitions, check for valid settings
|
68
|
+
@network.each do |_network, v|
|
69
|
+
health += MSG[:empty_network] if v.values.any? &:nil?
|
70
|
+
health += MSG[:invalid_gateway] unless Resolv::IPv4::Regex.match(v['gateway'])
|
71
|
+
end
|
72
|
+
# Loop through all compute definitions, check for valid settings
|
73
|
+
@compute.each do |_compute, v|
|
74
|
+
health += MSG[:invalid_cpu] unless (0..CPU_LIMIT).member?(v['cpu'])
|
75
|
+
health += MSG[:invalid_ram] unless (0..RAM_LIMIT).member?(v['ram'])
|
76
|
+
end
|
77
|
+
if health.length > 0
|
78
|
+
puts health + "\n#{health.lines.count} platform offense(s) detected"
|
79
|
+
false
|
80
|
+
else
|
81
|
+
true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
# rubocop:enable MethodLength, LineLength, CyclomaticComplexity, PerceivedComplexity, AmbiguousOperator
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Private: confirms existence of the standard platform definition files
|
89
|
+
# Returns true if all standard files present
|
90
|
+
def platform_files_exists?
|
91
|
+
fail(IOError, ERR[:no_platform_file]) unless File.file?(YML_PLATFORM)
|
92
|
+
fail(IOError, ERR[:no_vcenter_file]) unless File.file?(YML_VCENTER)
|
93
|
+
fail(IOError, ERR[:no_network_file]) unless File.file?(YML_NETWORK)
|
94
|
+
fail(IOError, ERR[:no_compute_file]) unless File.file?(YML_COMPUTE)
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
# Unimplemented - part of future Communication health check option
|
99
|
+
# def valid_vcenter_host?(address)
|
100
|
+
# _result = `ping -q -c 3 #{address}`
|
101
|
+
# $CHILD_STATUS.exitstatus == 0 ? true : false
|
102
|
+
# true
|
103
|
+
# end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'open4'
|
3
|
+
require_relative 'constants'
|
4
|
+
require_relative 'platform'
|
5
|
+
require_relative 'provisionerrunqueue'
|
6
|
+
|
7
|
+
module Elevage
|
8
|
+
# Provisioner class
|
9
|
+
class Provisioner
|
10
|
+
attr_accessor :name
|
11
|
+
attr_accessor :component
|
12
|
+
attr_accessor :instance
|
13
|
+
attr_accessor :environment
|
14
|
+
attr_accessor :vcenter
|
15
|
+
|
16
|
+
# Set us up to build the specified instance of component
|
17
|
+
def initialize(name, component, instance, environment, options)
|
18
|
+
@name = name
|
19
|
+
@component = component
|
20
|
+
@instance = instance
|
21
|
+
@environment = environment
|
22
|
+
@options = options
|
23
|
+
@vcenter = @environment.vcenter
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
puts "Name: #{@name}"
|
28
|
+
puts "Instance: #{@instance}"
|
29
|
+
puts "Component: #{@component}"
|
30
|
+
puts @component.to_yaml
|
31
|
+
puts 'Environment:'
|
32
|
+
puts @environment.to_yaml
|
33
|
+
end
|
34
|
+
|
35
|
+
# Public: Build the node
|
36
|
+
# rubocop:disable MethodLength, LineLength, GlobalVars, CyclomaticComplexity
|
37
|
+
def build
|
38
|
+
knife_cmd = generate_knife_cmd
|
39
|
+
|
40
|
+
# Modify behavior for dry-run
|
41
|
+
# Echo command to stdout and logfile instead of executing command.
|
42
|
+
if @options['dry-run']
|
43
|
+
puts knife_cmd
|
44
|
+
knife_cmd = "echo #{knife_cmd}"
|
45
|
+
end
|
46
|
+
|
47
|
+
# Open the logfile for writing
|
48
|
+
logfile = File.new("#{@options[:logfiles]}/#{@name}.log", 'w')
|
49
|
+
|
50
|
+
stamp = @options['dry-run'] ? '' : "#{Time.now} [#{$$}]: "
|
51
|
+
puts "#{stamp}#{@name}: logging to #{logfile.path}"
|
52
|
+
logfile.puts "#{stamp}#{@name}: Provisioning."
|
53
|
+
|
54
|
+
# Execute the knife command, capturing stderr and stdout as they
|
55
|
+
# produce anything, and push it all into a Queue object, which we then
|
56
|
+
# write to the log file as things come available.
|
57
|
+
status = Open4.popen4(knife_cmd) do |_pid, _stdin, stdout, stderr|
|
58
|
+
sem = Mutex.new
|
59
|
+
# Set and forget the thread for stderr...
|
60
|
+
# err_thread = Thread.new do
|
61
|
+
Thread.new do
|
62
|
+
while (line = stderr.gets)
|
63
|
+
sem.synchronize { logfile.puts line }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
out_thread = Thread.new do
|
67
|
+
while (line = stdout.gets)
|
68
|
+
sem.synchronize { logfile.puts line }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
out_thread.join
|
72
|
+
# err_thread.exit
|
73
|
+
end
|
74
|
+
|
75
|
+
stamp = @options['dry-run'] ? '' : "#{Time.now} [#{$$}]: "
|
76
|
+
logfile.puts "#{stamp}#{@name}: exit status: #{status.exitstatus}"
|
77
|
+
logfile.close
|
78
|
+
|
79
|
+
# Inform our master whether we succeeded or failed. Any non-zero
|
80
|
+
# exit status is a failure, and the details will be in the logfile
|
81
|
+
status.exitstatus == 0 ? true : false
|
82
|
+
end
|
83
|
+
# rubocop:enable MethodLength, LineLength, GlobalVars, CyclomaticComplexity
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Private: Determine which datastore to use for this specific
|
88
|
+
# provisioning.
|
89
|
+
def select_datastore
|
90
|
+
if @options['dry-run']
|
91
|
+
@vcenter['datastores'][0]
|
92
|
+
else
|
93
|
+
@vcenter['datastores'][rand(@vcenter['datastores'].count)]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Private: Build the knife command that will do the provisioning.
|
98
|
+
# rubocop:disable MethodLength, LineLength
|
99
|
+
def generate_knife_cmd
|
100
|
+
knife_cmd = 'knife vsphere vm clone --vsinsecure --start'
|
101
|
+
|
102
|
+
# Authentication and host
|
103
|
+
knife_cmd << " --vsuser #{@options[:vsuser]}"
|
104
|
+
knife_cmd << " --vspass #{@options[:vspass]}"
|
105
|
+
knife_cmd << " --vshost #{@vcenter['host']}"
|
106
|
+
|
107
|
+
# VM Template (what we're cloning)
|
108
|
+
knife_cmd << " --folder '#{@vcenter['imagefolder']}'"
|
109
|
+
knife_cmd << " --template '#{@component['image']}'"
|
110
|
+
|
111
|
+
# vSphere destination information (where the clone will end up)
|
112
|
+
knife_cmd << " --vsdc '#{@vcenter['datacenter']}'"
|
113
|
+
knife_cmd << " --dest-folder '#{@vcenter['destfolder']}'"
|
114
|
+
knife_cmd << " --resource-pool '#{@vcenter['resourcepool']}'"
|
115
|
+
knife_cmd << " --datastore '#{select_datastore}'"
|
116
|
+
|
117
|
+
# VM Hardware
|
118
|
+
knife_cmd << " --ccpu #{@component['compute']['cpu']}"
|
119
|
+
knife_cmd << " --cram #{@component['compute']['ram']}"
|
120
|
+
|
121
|
+
# VM Networking
|
122
|
+
knife_cmd << " --cvlan '#{@component['network']['vlanid']}'"
|
123
|
+
knife_cmd << " --cips #{@component['addresses'][@instance - 1]}/#{@component['network']['netmask']}"
|
124
|
+
knife_cmd << " --cdnsips #{@vcenter['dnsips'].join(',')}"
|
125
|
+
knife_cmd << " --cgw #{@component['network']['gateway']}"
|
126
|
+
knife_cmd << " --chostname #{@name}"
|
127
|
+
knife_cmd << " --ctz #{@vcenter['timezone']}"
|
128
|
+
|
129
|
+
# AD Domain and DNS Suffix
|
130
|
+
domain = @vcenter['domain']
|
131
|
+
domain = domain[1, domain.length] if domain.start_with?('.')
|
132
|
+
knife_cmd << " --cdomain #{domain}"
|
133
|
+
knife_cmd << " --cdnssuffix #{domain}"
|
134
|
+
|
135
|
+
# Knife Bootstrap options
|
136
|
+
knife_cmd << ' --bootstrap'
|
137
|
+
knife_cmd << " --template-file '#{@options['template-file']}'"
|
138
|
+
|
139
|
+
# knife fqdn specifies how knife will connect to the target (in this case by IP)
|
140
|
+
knife_cmd << " --fqdn #{@component['addresses'][@instance - 1]}"
|
141
|
+
knife_cmd << " --ssh-user #{@options['ssh-user']}"
|
142
|
+
knife_cmd << " --identity-file '#{@options['ssh-key']}'"
|
143
|
+
|
144
|
+
# What the node should be identified as in Chef
|
145
|
+
nodename = String.new(@name)
|
146
|
+
nodename << '.' << @environment.name if @vcenter['appendenv']
|
147
|
+
nodename << @vcenter['domain'] if @vcenter['appenddomain']
|
148
|
+
knife_cmd << " --node-name '#{nodename}'"
|
149
|
+
|
150
|
+
# Assign the run_list
|
151
|
+
knife_cmd << " --run-list '#{@component['runlist']}'"
|
152
|
+
|
153
|
+
# Assign the Chef environment
|
154
|
+
knife_cmd << " --environment '#{@environment.name}'"
|
155
|
+
|
156
|
+
# What version of chef-client are we bootstrapping (not sure this is necessary)
|
157
|
+
knife_cmd << " --bootstrap-version #{@options['bootstrap-version']}"
|
158
|
+
|
159
|
+
# Finally, the name of the VM as seen by vSphere.
|
160
|
+
# Whereas nodename will optionally append the domain name, VM names should *always* have the domain name
|
161
|
+
# appended. The only optional bit is including the chef environment in the name.
|
162
|
+
vmname = String.new(@name)
|
163
|
+
vmname << '.' << @environment.name if @vcenter['appendenv']
|
164
|
+
vmname << @vcenter['domain']
|
165
|
+
knife_cmd << " #{vmname}"
|
166
|
+
end
|
167
|
+
# rubocop:enable MethodLength, LineLength
|
168
|
+
end
|
169
|
+
end
|