vaquero_io 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,195 @@
1
+ require 'resolv'
2
+ require 'uri'
3
+ require 'vaquero_io/provision'
4
+
5
+ module VaqueroIo
6
+ # rubocop:disable ClassLength
7
+ class Platform
8
+ include VaqueroIo::Platform::Provision
9
+
10
+ attr_accessor :provider
11
+ attr_accessor :product
12
+ attr_accessor :product_provider, :product_provider_version
13
+ attr_accessor :environments
14
+ attr_accessor :nodename
15
+ attr_accessor :components
16
+ attr_accessor :required
17
+ attr_accessor :env_definition
18
+
19
+ # rubocop:disable MethodLength, LineLength
20
+ def initialize(provider = nil)
21
+ @provider = provider
22
+ fail unless platform_files_exist?
23
+ platform = YAML.load_file(PLATFORMFILE).fetch('platform')
24
+ @product = platform['product']
25
+ @product_provider = platform['provider']
26
+
27
+ # Lazy provider loading if we weren't given one...
28
+ if @provider.nil?
29
+ @provider = VaqueroIo::Provider.new(@product_provider)
30
+ # re-test platform health
31
+ fail unless platform_files_exist?
32
+ end
33
+
34
+ @product_provider_version = platform['plugin_version']
35
+ @environments = platform['environments']
36
+ @nodename = platform['nodename']
37
+ @components = platform['components']
38
+ @required = {}
39
+ @provider.definition['structure']['require'].each do |required_file|
40
+ @required[required_file] = YAML.load_file(@provider.definition['structure'][required_file]['path'] + required_file + '.yml').fetch(required_file)
41
+ end
42
+ end
43
+ # rubocop:enable MethodLength, LineLength
44
+
45
+ # rubocop:disable MethodLength, LineLength, CyclomaticComplexity, PerceivedComplexity
46
+ def healthy?(env = '')
47
+ health = ''
48
+ health += (FAIL_PROVIDER + "\n") if @provider.definition['name'] != @product_provider
49
+ puts WARN_PROVIDER_VERSION if @provider.definition['version'] != @product_provider_version
50
+ health += (FAIL_EMPTY_ENVIRONMENTS + "\n") unless @environments.all?
51
+ @environments.each do |e|
52
+ health += (FAIL_MISSING_ENV + e + "\n") unless File.file?(ENVIRONMENTFILE + e + '.yml')
53
+ end if @environments.all?
54
+ health += (FAIL_EMPTY_NODENAME + "\n") unless @nodename.all?
55
+
56
+ # confirm that the platform definition references all required files
57
+ @provider.definition['structure']['require'].each do |required_file|
58
+ @components.each do |_param, value|
59
+ health += (FAIL_REQUIRED_FILES + required_file + "\n") unless value.key?(required_file)
60
+ # TODO: Validate that the value for the required file key actually matches a defined key value
61
+ end
62
+ # Validate required files against template
63
+ health += validate(@provider.definition['structure'][required_file]['params'], @required[required_file])
64
+ end
65
+ # Validate platform against template
66
+ health += validate(@provider.definition['structure']['platform']['params'], @components)
67
+
68
+ # TODO: if an environment is specified then we are checking the health of the environment rather than platform
69
+ puts 'list nodes' unless env.empty?
70
+ if health.length > 0
71
+ puts health + "\n#{health.lines.count} platform offense(s) detected"
72
+ false
73
+ else
74
+ true
75
+ end
76
+ end
77
+ # rubocop:enable MethodLength, LineLength, CyclomaticComplexity, PerceivedComplexity
78
+
79
+ # rubocop:disable MethodLength, LineLength
80
+ def environment(env)
81
+ return unless healthy?
82
+ build_env = YAML.load_file(ENVIRONMENTFILE + env + '.yml').fetch(env)
83
+ @components.each do |component, _config|
84
+ begin
85
+ build_env['components'][component].merge!(@components[component]) { |_key, v1, _v2| v1 } unless build_env['components'][component].nil?
86
+ rescue => error
87
+ puts "ERROR: build_env: could not merge component \"#{component}\" for environment \"#{env}\"!"
88
+ raise error
89
+ end
90
+ end
91
+ build_env['components'].each do |component, config|
92
+ @required.each do |required_item, _values|
93
+ build_env['components'][component][required_item] = @required[required_item][build_env['components'][component][required_item]]
94
+ end
95
+ config['component_role'] = config['component_role'].gsub('#', component) if config['component_role']
96
+ end
97
+ @env_definition = {}
98
+ build_env['components'].each do |component, config|
99
+ nodes = []
100
+ (1..config['count']).each do |n|
101
+ nodes << node_name(env, component, n)
102
+ end
103
+ # build_env['components'][component].merge!(@components[component])
104
+ build_env['components'][component]['nodes'] = nodes
105
+ end
106
+ @env_definition = build_env
107
+ end
108
+ # rubocop:enable MethodLength, LineLength
109
+
110
+ private
111
+
112
+ # rubocop:disable MethodLength
113
+ def node_name(env, component, instance)
114
+ name = ''
115
+ @nodename.each do |i|
116
+ case i
117
+ when 'environment'
118
+ name += env
119
+ when 'component'
120
+ name += component
121
+ when 'instance'
122
+ name += instance.to_s.rjust(2, '0')
123
+ else
124
+ name += i
125
+ end
126
+ end
127
+ name
128
+ end
129
+ # rubocop:enable MethodLength
130
+
131
+ # rubocop:disable MethodLength, LineLength, CyclomaticComplexity, PerceivedComplexity
132
+ def validate(templatefile, definitionfile)
133
+ health = ''
134
+ templatefile.each do |param, value|
135
+ definitionfile.each do |component, keys|
136
+ case
137
+ when value['array']
138
+ if keys[param].class == Array
139
+ keys[param].each do |i|
140
+ health += "Validation error: #{component}:#{param}\n" unless valid?(i, value['array'])
141
+ end
142
+ else
143
+ health += "Validation error: #{component}:#{param}\n"
144
+ end
145
+ when value['hash']
146
+ keys[param].each do |k, v|
147
+ health += "Validation error: #{component}:#{param}\n" unless valid?(v, value['hash'][k])
148
+ end
149
+ else
150
+ health += "Validation error: #{component}:#{param}\n" unless valid?(keys[param], value)
151
+ end
152
+ end
153
+ end
154
+ health
155
+ end
156
+ # rubocop:enable MethodLength, LineLength, CyclomaticComplexity, PerceivedComplexity
157
+
158
+ # rubocop:disable MethodLength, DoubleNegation, CyclomaticComplexity, PerceivedComplexity, LineLength
159
+ def valid?(value, validate)
160
+ if value.nil?
161
+ false
162
+ else
163
+ case validate['type']
164
+ when 'integer'
165
+ rng = validate['range'].split('..').map { |d| Integer(d) }
166
+ (value.class != Fixnum) ? false : Range.new(rng[0], rng[1]).member?(value)
167
+ when 'string'
168
+ (value.class != String) ? false : value.match(Regexp.new(validate['match']))
169
+ when 'IP'
170
+ (value.class != String) ? false : Resolv::IPv4::Regex.match(value)
171
+ when 'URL'
172
+ (value.class != String) ? false : value.match(URI.regexp)
173
+ when 'boolean'
174
+ !!value == value
175
+ else
176
+ false
177
+ end
178
+ end
179
+ end
180
+ # rubocop:enable MethodLength, DoubleNegation, CyclomaticComplexity, PerceivedComplexity, LineLength
181
+
182
+ # rubocop:disable LineLength
183
+ def platform_files_exist?
184
+ fail(IOError, MISSING_PLATFORM) unless File.file?(PLATFORMFILE)
185
+ unless @provider.nil? # We can't check that everything's present yet...
186
+ @provider.definition['structure']['require'].each do |required_file|
187
+ fail(IOError, MISSING_PLATFORM + required_file) unless File.file?(@provider.definition['structure'][required_file]['path'] + required_file + '.yml')
188
+ end
189
+ end
190
+ true
191
+ end
192
+ # rubocop:enable LineLength
193
+ end
194
+ # rubocop:enable ClassLength
195
+ end
@@ -0,0 +1,118 @@
1
+ require 'pathname'
2
+ require 'yaml'
3
+ require 'git'
4
+ require 'fileutils'
5
+
6
+ module VaqueroIo
7
+ # top level comment
8
+ class Plugin < Thor
9
+ include Thor::Actions
10
+
11
+ # list of installed providers (includes PWD)
12
+ # rubocop:disable LineLength
13
+ desc 'list', DESC_PLUGIN_LIST
14
+ def list
15
+ installed_providers.each { |k, v| puts "#{k} (#{v}) #{ENV[PUTENV_PROVIDER] == k ? '<default' : ''}" }
16
+ end
17
+ # rubocop:enable LineLength
18
+
19
+ desc 'init', DESC_PLUGIN_INIT
20
+ def init
21
+ template(TEMPLATE_PROVIDER, PROVIDERFILE)
22
+ end
23
+
24
+ # rubocop:disable LineLength
25
+ desc 'install LOCATION', DESC_PLUGIN_INSTALL
26
+ def install(path)
27
+ latest = get_working_copy(path)
28
+ if installed_providers.key?(latest['name'])
29
+ puts "#{latest['name']} already installed"
30
+ else
31
+ Dir.mkdir(PROVIDERS_PATH) unless Dir.exist?(PROVIDERS_PATH)
32
+ FileUtils.cp_r("#{TMP_INSTALL_FOLDER}/.", "#{PROVIDERS_PATH}#{latest['name']}")
33
+ puts "Successfully installed #{latest['name']} (#{latest['version']})"
34
+ end if latest != NO_PROVIDER_FILE
35
+ clear_working_copy
36
+ fail(IOError, NO_PROVIDER_FILE) if latest == NO_PROVIDER_FILE
37
+ end
38
+ # rubocop:enable LineLength
39
+
40
+ # rubocop:disable MethodLength, LineLength
41
+ desc 'update [PROVIDER]', DESC_PLUGIN_UPDATE
42
+ def update(plugin = '')
43
+ update_list = {}
44
+ providers = installed_providers
45
+ if plugin.empty?
46
+ update_list = providers
47
+ else
48
+ if installed_providers.key?(plugin)
49
+ update_list[plugin] = providers[plugin]
50
+ else
51
+ update_list = NO_PROVIDER_FILE
52
+ end
53
+ end
54
+ update_list.each { |prov, ins_vers| update_plugin(prov, ins_vers) } if update_list != NO_PROVIDER_FILE
55
+ fail(IOError, NO_PROVIDER_FILE) if update_list == NO_PROVIDER_FILE
56
+ end
57
+ # rubocop:enable MethodLength, LineLength
58
+
59
+ desc 'remove PROVIDER', DESC_PLUGIN_REMOVE
60
+ def remove(plugin)
61
+ if installed_providers.key?(plugin)
62
+ FileUtils.remove_dir("#{PROVIDERS_PATH}#{plugin}")
63
+ puts "#{plugin} removed"
64
+ else
65
+ fail(IOError, NO_PROVIDER_FILE)
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def get_working_copy(path)
72
+ Git.clone(path, TMP_INSTALL_FOLDER)
73
+ NO_PROVIDER_FILE_FILE unless File.file?(TMP_INSTALL_PROVIDER)
74
+ YAML.load_file(TMP_INSTALL_PROVIDER).fetch('provider', NO_PROVIDER_FILE)
75
+ end
76
+
77
+ def clear_working_copy
78
+ FileUtils.remove_dir(TMP_INSTALL_FOLDER)
79
+ end
80
+
81
+ def installed_providers
82
+ providers = {}
83
+ Pathname.glob(LIST_PLUGINS_PATH).each do |prov|
84
+ plugin = YAML.load_file(prov).fetch('provider')
85
+ providers[plugin['name']] = plugin['version']
86
+ end
87
+ providers
88
+ end
89
+
90
+ def plugin_locations
91
+ locations = {}
92
+ Pathname.glob(LIST_PLUGINS_PATH).each do |prov|
93
+ plugin = YAML.load_file(prov).fetch('provider')
94
+ locations[plugin['name']] = plugin['location']
95
+ end
96
+ locations
97
+ end
98
+
99
+ # rubocop:disable LineLength
100
+ def update_plugin(plugin, version)
101
+ locations = plugin_locations
102
+ latest = get_working_copy(locations[plugin])
103
+ if latest['version'] != version
104
+ FileUtils.remove_dir("#{PROVIDERS_PATH}#{plugin}")
105
+ FileUtils.cp_r("#{TMP_INSTALL_FOLDER}/.", "#{PROVIDERS_PATH}#{latest['name']}")
106
+ puts "Updated #{plugin} version #{version} -> #{latest['version']}"
107
+ else
108
+ puts "#{plugin}#{PLUGINS_CURRENT}"
109
+ end
110
+ clear_working_copy
111
+ end
112
+ # rubocop:enable LineLength
113
+
114
+ def self.source_root
115
+ File.dirname(__FILE__)
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,48 @@
1
+ module VaqueroIo
2
+ # comment
3
+ class Provider
4
+ attr_accessor :provider
5
+ attr_accessor :definition
6
+
7
+ # rubocop:disable LineLength
8
+ def initialize(use_provider)
9
+ @provider = resolve_provider(use_provider)
10
+ @definition = YAML.load_file(PROVIDERS_PATH + @provider + '/' + PROVIDERFILE).fetch('provider')
11
+ require PROVIDERS_PATH + @provider + '/' + @provider.gsub('-', '_') + '.rb'
12
+ end
13
+ # rubocop:enable LineLength
14
+
15
+ # rubocop:disable MethodLength, LineLength
16
+ def new_definition
17
+ if File.exist?('platform.yml')
18
+ fail(IOError, PLATFORM_EXISTS)
19
+ else
20
+ Pathname.glob(PROVIDERS_PATH + @provider + '/templates/*.yml').each do |prov|
21
+ path = @definition['structure'][File.basename(prov, '.yml')]['path']
22
+ if @definition['structure'][File.basename(prov, '.yml')]['path'].nil?
23
+ path = ''
24
+ else
25
+ unless File.directory?(@definition['structure'][File.basename(prov, '.yml')]['path'])
26
+ FileUtils.mkdir(@definition['structure'][File.basename(prov, '.yml')]['path'])
27
+ end
28
+ end
29
+ FileUtils.cp(prov, path + File.basename(prov))
30
+ end
31
+ puts MSG_NEW_SUCCESS
32
+ end
33
+ end
34
+ # rubocop:enable MethodLength, LineLength
35
+
36
+ private
37
+
38
+ # rubocop:disable LineLength
39
+ def resolve_provider(provider)
40
+ if provider.nil?
41
+ ENV[PUTENV_PROVIDER].nil? ? fail(IOError, NO_PROVIDER) : ENV[PUTENV_PROVIDER]
42
+ else
43
+ provider
44
+ end
45
+ end
46
+ # rubocop:enable LineLength
47
+ end
48
+ end
@@ -0,0 +1,13 @@
1
+ # Basically a dummy placeholder for the provision method that
2
+ # plugins will override
3
+ module VaqueroIo
4
+ # comment
5
+ class Platform
6
+ # comment
7
+ module Provision
8
+ def provision(_env)
9
+ puts 'success'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,42 @@
1
+ require 'vaquero_io'
2
+
3
+ module VaqueroIo
4
+ # wrapper to assist aruba in single process execution
5
+ class Runner
6
+ # rubocop:disable LineLength
7
+ def initialize(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel)
8
+ @argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
9
+ end
10
+ # rubocop:enable LineLength
11
+
12
+ # rubocop:disable MethodLength
13
+ def execute!
14
+ exit_code = begin
15
+ $stderr = @stderr
16
+ $stdin = @stdin
17
+ $stdout = @stdout
18
+
19
+ VaqueroIo::CLI.start(@argv)
20
+
21
+ # Thor::Base#start does not have a return value
22
+ # assume success if no exception is raised.
23
+ 0
24
+ rescue StandardError => err
25
+ # The ruby interpreter would pipe this to STDERR and
26
+ # exit 1 in the case of an unhandled exception
27
+ b = err.backtrace
28
+ b.unshift("#{b.shift}: #{err.message} (#{err.class})")
29
+ @stderr.puts(b.map { |s| "\tfrom #{s}" }.join("\n"))
30
+ 1
31
+ ensure
32
+ # put them back.
33
+ $stderr = STDERR
34
+ $stdin = STDIN
35
+ $stdout = STDOUT
36
+ end
37
+ # Proxy exit code back to the injected kernel.
38
+ @kernel.exit(exit_code)
39
+ end
40
+ # rubocop:enable MethodLength
41
+ end
42
+ end
@@ -0,0 +1,86 @@
1
+ # vaquero: Provider template definition file
2
+ #
3
+ # Complete provider file documentation can be found on the vaquero wiki
4
+ # https://github.com/vaquero-io/vaquero/wiki/Custom-Provider-Plugins
5
+ #
6
+ provider:
7
+ # define the plugin name that can be used to select this provider from the command line or environmental variable
8
+ name:
9
+ version: 0.0.0
10
+
11
+ # Repository location for installation and updates
12
+ location:
13
+
14
+ structure:
15
+
16
+ platform:
17
+ # default to present working directory to find platform.yml
18
+ path:
19
+
20
+ # Use of additional reference files is optional (see documentation)
21
+ #
22
+ # Example
23
+ #
24
+ # references:
25
+ # - compute
26
+ # - tags
27
+ #
28
+ references:
29
+ -
30
+
31
+ params:
32
+ name:
33
+ type: string
34
+ match: 'regex'
35
+ description:
36
+ type: string
37
+ match: 'regex'
38
+ environments:
39
+ array:
40
+ type: string
41
+ match: 'regex'
42
+ nodename:
43
+ array:
44
+ type: string
45
+ match: 'regex'
46
+ pools:
47
+ count:
48
+ type: integer
49
+ range: 1..100
50
+ runlist:
51
+ array:
52
+ type: string
53
+ match: 'regex'
54
+ componentrole:
55
+ type: string
56
+ match: 'regex'
57
+ compute:
58
+ type: string
59
+ match: 'regex'
60
+
61
+ components:
62
+
63
+ environment:
64
+ path: 'environments/'
65
+
66
+ compute:
67
+ path: 'infrastructure/'
68
+ references:
69
+ params:
70
+ ram:
71
+ type: integer
72
+ range: 128..262144
73
+ cpu:
74
+ type: integer
75
+ range: 1..16
76
+ drive:
77
+ hash:
78
+ mount:
79
+ type: string
80
+ match: 'regex'
81
+ capacity:
82
+ type: integer
83
+ range: 8..2048
84
+ image:
85
+ type: string
86
+ match: 'regex'
@@ -0,0 +1,4 @@
1
+ # simple gem version number tracking
2
+ module VaqueroIo
3
+ VERSION = '0.1.1'
4
+ end
data/lib/vaquero_io.rb ADDED
@@ -0,0 +1,43 @@
1
+ require 'thor'
2
+ require 'vaquero_io/messages'
3
+ require 'vaquero_io/config'
4
+ require 'vaquero_io/plugin'
5
+ require 'vaquero_io/provider'
6
+ require 'vaquero_io/platform'
7
+ require 'vaquero_io/build'
8
+ require 'vaquero_io/provision'
9
+
10
+ # Refer to README.md for use instructions
11
+ module VaqueroIo
12
+ # Start of main CLI
13
+ class CLI < Thor
14
+ package_name 'vaquero_io'
15
+ map '--version' => :version
16
+ map '-v' => :version
17
+
18
+ desc 'version, -v', DESC_VERSION
19
+ def version
20
+ puts VERSION
21
+ end
22
+
23
+ desc 'new', DESC_NEW
24
+ method_options %w( provider -p ) => :string, :required => true
25
+ def new
26
+ VaqueroIo::Provider.new(options[:provider]).new_definition
27
+ end
28
+
29
+ # rubocop:disable LineLength
30
+ desc 'health [ENVIRONMENT]', DESC_HEALTH
31
+ method_options %w( provider -p ) => :string
32
+ def health(env = '')
33
+ provider = options[:provider] ? VaqueroIo::Provider.new(options[:provider]) : nil
34
+ # puts HEALTHY if VaqueroIo::Platform.new(VaqueroIo::Provider.new(options[:provider])).healthy?(env)
35
+ puts HEALTHY if VaqueroIo::Platform.new(provider).healthy?(env)
36
+ end
37
+ # rubocop:enable LineLength
38
+
39
+ # subcommand in Thor called as registered class
40
+ register(VaqueroIo::Plugin, 'plugin', 'plugin COMMAND', DESC_PLUGIN)
41
+ register(VaqueroIo::Build, 'build', 'build TARGET', DESC_BUILD)
42
+ end
43
+ end
@@ -0,0 +1,4 @@
1
+ # $LOAD_PATH << '../../lib'
2
+ #
3
+ require 'coveralls'
4
+ Coveralls.wear!
@@ -0,0 +1,38 @@
1
+ # rubocop:disable all
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
4
+ require 'vaquero_io/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'vaquero_io'
8
+ spec.version = VaqueroIo::VERSION
9
+ spec.authors = %w('Nic Cheneweth','Gregory Ruiz-ade')
10
+ spec.email = %w('Nic.Cheneweth@thoughtworks.com','gregory.ruiz-ade@activenetwork.com')
11
+ spec.summary = 'Automated provisioning of application environments'
12
+ spec.description = 'Command line tool to automate the provision and bootstrap of virtual machine application environments'
13
+ spec.homepage = 'http://vaquero.io/'
14
+ spec.license = 'Apache 2.0'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '>= 1.7'
22
+ spec.add_development_dependency 'rake', '>= 10.0'
23
+ spec.add_development_dependency 'rubocop', '>= 0.27.0'
24
+ spec.add_development_dependency 'aruba', '>= 0.6'
25
+ spec.add_development_dependency 'rspec', '>= 3.0'
26
+ spec.add_development_dependency 'coveralls', '>= 0.7.9'
27
+ spec.add_development_dependency 'guard', '>= 2.10.0'
28
+ spec.add_development_dependency 'guard-rubocop', '>= 1.1.0'
29
+ spec.add_development_dependency 'guard-rspec', '>= 4.5.0'
30
+ spec.add_development_dependency 'guard-cucumber', '>= 1.6.0'
31
+ spec.add_development_dependency 'guard-yard', '>= 2.1.4'
32
+ # growl functionality in Guardfile depends on growlnotify binary
33
+ spec.add_development_dependency 'growl'
34
+ spec.add_development_dependency 'yard', '>= 0.8'
35
+ spec.add_dependency 'thor', '>= 0.18.0'
36
+ spec.add_dependency 'git', '>= 1.2.0'
37
+ end
38
+ # rubocop:enable all