elevage 0.1.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/.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
|