scalemail 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/bin/scalemail +4 -0
- data/lib/configurator.rb +148 -0
- data/lib/entry.rb +4 -0
- data/lib/host_types.rb +7 -0
- data/lib/hosts.rb +48 -0
- data/lib/provisioner.rb +132 -0
- data/lib/scalemail.rb +53 -0
- data/lib/test.rb +20 -0
- data/spec/lib/cli_spec.rb +39 -0
- data/spec/lib/configuration_loading_spec.rb +408 -0
- data/spec/lib/provisioner_spec.rb +25 -0
- data/spec/lib/scalemail_spec.rb +34 -0
- data/spec/resources/host-tree-generation-test.yml +62 -0
- data/spec/spec_helper.rb +97 -0
- metadata +59 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7afb9d2cc8a19c8510d3eee98915d87ee123e78d
|
4
|
+
data.tar.gz: ca48eb9cf1a05b3e5c859ba7f0bbc61d437bd307
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 314c9eda18923f751d95619f438e6da41ef2d6f7075290197109769a992e308267aba05baf59153b889f543a1589eae799f15d8c23502a856a9a42dc264f7333
|
7
|
+
data.tar.gz: 46363add0a2fb8c4389a20a1e5e604236da497699cb847dbc759aebf5ab46141a770b40671ab6e45eb210f401fa30a9c08b065d9827e31445b8dbdb4c79b7f74
|
data/bin/scalemail
ADDED
data/lib/configurator.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'erb'
|
3
|
+
require_relative 'hosts'
|
4
|
+
|
5
|
+
module Configurator
|
6
|
+
class Config
|
7
|
+
attr_reader :loaded_configuration, :driver, :config_tree
|
8
|
+
@@known_drivers = ['amazonec2']
|
9
|
+
@@known_host_types = %w(generic swarm-master swarm-node)
|
10
|
+
|
11
|
+
def initialize(config_file_path)
|
12
|
+
abort("Docker Machine configuration file not found: #{config_file_path}") unless File.exist?(config_file_path)
|
13
|
+
abort("Can't read Docker Machine configuration file: #{config_file_path}") unless File.readable?(config_file_path)
|
14
|
+
|
15
|
+
template = ERB.new File.new(config_file_path).read
|
16
|
+
@loaded_configuration = YAML.load template.result(binding)
|
17
|
+
|
18
|
+
# Checking 'options' section from Docker Machine config file
|
19
|
+
@driver = get_option('driver')
|
20
|
+
abort(build_wrong_config_format_error('options => driver parameter not found')) unless @driver
|
21
|
+
abort(build_invalid_config_error("unknown driver 'aws'")) unless @@known_drivers.include?(@driver)
|
22
|
+
abort(build_invalid_config_error("driver set as 'amazonec2', but 'aws-credentials-profile' parameter not set")) \
|
23
|
+
unless get_option('aws-credentials-profile')
|
24
|
+
|
25
|
+
# Loading 'hosts' section from Docker Machine config file
|
26
|
+
hosts = @loaded_configuration['hosts']
|
27
|
+
abort(build_invalid_config_error('hosts are not defined')) unless hosts
|
28
|
+
|
29
|
+
hosts.each do |host|
|
30
|
+
host_name = host.first.to_s
|
31
|
+
host_config = get_host_config(host_name)
|
32
|
+
host_type = host_config['host-type']
|
33
|
+
|
34
|
+
# Checking 'host' section from Docker Machine config file
|
35
|
+
abort(build_invalid_config_error("host-type is not defined for host '#{host_name}'")) unless host_type
|
36
|
+
abort(build_invalid_config_error("unknown host-type '#{host_type}' for host '#{host_name}'")) \
|
37
|
+
unless @@known_host_types.include?(host_type)
|
38
|
+
abort(build_invalid_config_error("hosts-amount not defined for host '#{host_name}'")) \
|
39
|
+
unless host_config['hosts-amount']
|
40
|
+
|
41
|
+
is_hosts_amount_int = Integer(host_config['hosts-amount']) rescue false
|
42
|
+
abort(build_invalid_config_error("hosts-amount must be integer. Host: '#{host_name}'")) \
|
43
|
+
unless is_hosts_amount_int
|
44
|
+
|
45
|
+
engine_opts = host_config['engine-opts']
|
46
|
+
abort(build_invalid_config_error("engine-opts must be an array in yml notation. Host: '#{host_name}'")) \
|
47
|
+
unless engine_opts.nil? || engine_opts.kind_of?(Array)
|
48
|
+
|
49
|
+
commands_to_execute = host_config['commands-to-execute']
|
50
|
+
abort(build_invalid_config_error("commands-to-execute must be an array in yml notation. Host: '#{host_name}'")) \
|
51
|
+
unless commands_to_execute.nil? || commands_to_execute.kind_of?(Array)
|
52
|
+
|
53
|
+
engine_install_url = host_config['engine-install-url']
|
54
|
+
abort(build_invalid_config_error("engine-install-url must be a string. Host: '#{host_name}'")) \
|
55
|
+
unless engine_install_url.nil? || engine_install_url.kind_of?(String)
|
56
|
+
|
57
|
+
# Checking 'host' section from Docker Machine config file for 'swarm-node' host type
|
58
|
+
if (host_type == 'swarm-node')
|
59
|
+
abort(build_invalid_config_error("host-type is swarm-node, but swarm-discovery is not set. Host: '#{host_name}'")) \
|
60
|
+
unless host_config['swarm-discovery']
|
61
|
+
abort(build_invalid_config_error("swarm-discovery must be a string. Host: '#{host_name}'")) \
|
62
|
+
unless host_config['swarm-discovery'].kind_of?(String)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Checking 'host' section from Docker Machine config file for 'amazonec2' driver
|
66
|
+
validate_amazonec2_options(host_config, host_name)
|
67
|
+
|
68
|
+
@config_tree ||= {}
|
69
|
+
hosts_amount = host_config['hosts-amount']
|
70
|
+
if hosts_amount > 1
|
71
|
+
for i in 1..hosts_amount
|
72
|
+
create_host_in_config_tree(host_name+"-#{i}", host_config, host_type, @driver)
|
73
|
+
end
|
74
|
+
else
|
75
|
+
create_host_in_config_tree(host_name, host_config, host_type, @driver)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def validate_amazonec2_options(host_config, host_name)
|
81
|
+
abort(build_invalid_config_error("amazonec2-region not defined for host '#{host_name}'")) unless host_config['amazonec2-region']
|
82
|
+
|
83
|
+
amazonec2_region = host_config['amazonec2-region']
|
84
|
+
abort(build_invalid_config_error("amazonec2-region must be a string. Host: '#{host_name}'")) \
|
85
|
+
unless amazonec2_region.kind_of?(String)
|
86
|
+
|
87
|
+
abort(build_invalid_config_error("amazonec2-zone not defined for host '#{host_name}'")) unless host_config['amazonec2-zone']
|
88
|
+
|
89
|
+
amazonec2_zone = host_config['amazonec2-zone']
|
90
|
+
abort(build_invalid_config_error("amazonec2-zone must be a string. Host: '#{host_name}'")) \
|
91
|
+
unless amazonec2_zone.kind_of?(String)
|
92
|
+
|
93
|
+
abort(build_invalid_config_error("amazonec2-vpc-id not defined for host '#{host_name}'")) unless host_config['amazonec2-vpc-id']
|
94
|
+
|
95
|
+
amazonec2_vpc_id = host_config['amazonec2-vpc-id']
|
96
|
+
abort(build_invalid_config_error("amazonec2-vpc-id must be a string. Host: '#{host_name}'")) \
|
97
|
+
unless amazonec2_vpc_id.kind_of?(String)
|
98
|
+
|
99
|
+
abort(build_invalid_config_error("amazonec2-subnet-id not defined for host '#{host_name}'")) unless host_config['amazonec2-subnet-id']
|
100
|
+
|
101
|
+
amazonec2_subnet_id = host_config['amazonec2-subnet-id']
|
102
|
+
abort(build_invalid_config_error("amazonec2-subnet-id must be a string. Host: '#{host_name}'")) \
|
103
|
+
unless amazonec2_subnet_id.kind_of?(String)
|
104
|
+
|
105
|
+
abort(build_invalid_config_error("amazonec2-security-group not defined for host '#{host_name}'")) unless host_config['amazonec2-security-group']
|
106
|
+
|
107
|
+
amazonec2_security_group = host_config['amazonec2-security-group']
|
108
|
+
abort(build_invalid_config_error("amazonec2-security-group must be a string. Host: '#{host_name}'")) \
|
109
|
+
unless amazonec2_security_group.kind_of?(String)
|
110
|
+
|
111
|
+
abort(build_invalid_config_error("amazonec2-instance-type not defined for host '#{host_name}'")) unless host_config['amazonec2-instance-type']
|
112
|
+
|
113
|
+
amazonec2_instance_type = host_config['amazonec2-instance-type']
|
114
|
+
abort(build_invalid_config_error("amazonec2-instance-type must be a string. Host: '#{host_name}'")) \
|
115
|
+
unless amazonec2_instance_type.kind_of?(String)
|
116
|
+
|
117
|
+
amazonec2_ami = host_config['amazonec2-ami']
|
118
|
+
abort(build_invalid_config_error("amazonec2-ami must be a string. Host: '#{host_name}'")) \
|
119
|
+
unless amazonec2_ami.nil? || amazonec2_ami.kind_of?(String)
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
def get_option(key)
|
124
|
+
options_hash = @loaded_configuration['options']
|
125
|
+
options_hash ? options_hash[key] : nil
|
126
|
+
end
|
127
|
+
|
128
|
+
def build_wrong_config_format_error(reason)
|
129
|
+
"Wrong Docker Machine config format: #{reason}."
|
130
|
+
end
|
131
|
+
|
132
|
+
def build_invalid_config_error(reason)
|
133
|
+
"Invalid Docker Machine config: #{reason}."
|
134
|
+
end
|
135
|
+
|
136
|
+
def get_host_config(host_node_name)
|
137
|
+
hosts_hash = @loaded_configuration['hosts']
|
138
|
+
hosts_hash ? hosts_hash[host_node_name] : nil
|
139
|
+
end
|
140
|
+
|
141
|
+
def create_host_in_config_tree(host_name, host_config, host_type, driver)
|
142
|
+
case driver
|
143
|
+
when 'amazonec2'
|
144
|
+
@config_tree[host_name] = Hosts::AmazonHost.new(host_config, host_type)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
data/lib/entry.rb
ADDED
data/lib/host_types.rb
ADDED
data/lib/hosts.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative 'configurator'
|
2
|
+
module Hosts
|
3
|
+
class BasicHost
|
4
|
+
attr_reader :creation_string
|
5
|
+
# , :engine_insecure_registries, \
|
6
|
+
# :engine_registy_mirrors, :engine_labels, :engine_storage_driver, :engine_env_vars
|
7
|
+
|
8
|
+
def initialize(config, host_type)
|
9
|
+
@creation_string ||= []
|
10
|
+
engine_install_url = config['engine-install-url']
|
11
|
+
engine_opts = config['engine-opts']
|
12
|
+
|
13
|
+
@creation_string.push("--engine-install-url '#{engine_install_url}'") if engine_install_url
|
14
|
+
if engine_opts
|
15
|
+
engine_opts.each {|opt| @creation_string.push("--engine-opt='"+opt+"'")}
|
16
|
+
end
|
17
|
+
|
18
|
+
add_swarm_node_fields config, host_type if host_type=='swarm-node' || host_type=='swarm-master'
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_swarm_node_fields(config, host_type)
|
22
|
+
swarm_discovery = config['swarm-discovery']
|
23
|
+
@creation_string.push('--swarm') if host_type=='swarm-node'
|
24
|
+
@creation_string.push('--swarm-master') if host_type=='swarm-master'
|
25
|
+
@creation_string.push("--swarm-discovery='#{swarm_discovery}'")
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_creation_string
|
29
|
+
@creation_string.join(' ')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class AmazonHost < BasicHost
|
34
|
+
def initialize(config, host_type)
|
35
|
+
super(config, host_type)
|
36
|
+
@creation_string.unshift('--driver amazonec2')
|
37
|
+
@creation_string.push("--amazonec2-region #{config['amazonec2-region']}")
|
38
|
+
@creation_string.push("--amazonec2-zone #{config['amazonec2-zone']}")
|
39
|
+
@creation_string.push("--amazonec2-vpc-id #{config['amazonec2-vpc-id']}")
|
40
|
+
@creation_string.push("--amazonec2-subnet-id #{config['amazonec2-subnet-id']}")
|
41
|
+
@creation_string.push("--amazonec2-security-group #{config['amazonec2-security-group']}")
|
42
|
+
@creation_string.push("--amazonec2-instance-type #{config['amazonec2-instance-type']}")
|
43
|
+
@creation_string.push("--amazonec2-ami #{config['amazonec2-ami']}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
data/lib/provisioner.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'open4'
|
2
|
+
require 'yaml'
|
3
|
+
require_relative 'configurator'
|
4
|
+
|
5
|
+
class Provisioner
|
6
|
+
attr_reader :config_to_provision, :hosts, :config
|
7
|
+
def initialize(config)
|
8
|
+
@config = config
|
9
|
+
@config_to_provision = @config.config_tree
|
10
|
+
@hosts = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def provision_hosts
|
14
|
+
threads = []
|
15
|
+
@config_to_provision.each do |host|
|
16
|
+
host_name = host[0]
|
17
|
+
host_config = host[1]
|
18
|
+
|
19
|
+
host_node_name = host_name.sub /\-\d*$/, ''
|
20
|
+
host_config_node = @config.loaded_configuration['hosts'][host_node_name]
|
21
|
+
placeholders = host_config_node['placeholders']
|
22
|
+
if placeholders
|
23
|
+
placeholders.each do |key, value|
|
24
|
+
if key=='host_ip' && @hosts[value] && @hosts[value][:ip]
|
25
|
+
replacement = @hosts[value][:ip]
|
26
|
+
else
|
27
|
+
abort("Cant find IP for host '#{value}' to use in placeholder")
|
28
|
+
end
|
29
|
+
host_config.creation_string.each do |parameter|
|
30
|
+
if parameter.respond_to?(:each)
|
31
|
+
parameter.each do |subparam|
|
32
|
+
if subparam.respond_to?(:sub!)
|
33
|
+
subparam.sub! "$#{key}$", replacement
|
34
|
+
end
|
35
|
+
end
|
36
|
+
else
|
37
|
+
parameter.sub! "$#{key}$", replacement
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
print_provisioning_plan host_name
|
43
|
+
synchron = @config.loaded_configuration['hosts'][host_node_name]['synchronous']
|
44
|
+
if synchron == true
|
45
|
+
create_host(host_name, host_config).join()
|
46
|
+
next
|
47
|
+
end
|
48
|
+
threads.push(create_host host_name, host_config)
|
49
|
+
end
|
50
|
+
threads.each {|thread| thread.join}
|
51
|
+
puts @hosts.to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_host(host_name, host_config)
|
55
|
+
host_creation_string = host_config.get_creation_string
|
56
|
+
thread = Thread.new do
|
57
|
+
process = run_os_command(host_name, "docker-machine create #{host_creation_string} #{host_name}")
|
58
|
+
if process[:process].exitstatus != 0
|
59
|
+
abort("#{host_name} creation failed. Check log for details.")
|
60
|
+
end
|
61
|
+
@hosts[host_name] = {:ip => get_host_ip(host_name)}
|
62
|
+
commands = @config.loaded_configuration['hosts'][host_name]['commands-to-execute']
|
63
|
+
if commands
|
64
|
+
commands.each do |command|
|
65
|
+
process = run_os_command(host_name, "docker-machine ssh #{host_name} #{command}")
|
66
|
+
if process[:process].exitstatus != 0
|
67
|
+
abort("#{host_name} provisioning failed. Check log for details.")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
thread
|
73
|
+
end
|
74
|
+
|
75
|
+
def formatted_log_to_stdout(prefix, string, timestamp=true)
|
76
|
+
time = Time.new
|
77
|
+
time_string = time.strftime("%H:%M:%S")
|
78
|
+
puts "#{time_string if timestamp} #{prefix} #{string}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def formatted_log_to_stderr(prefix, string, timestamp=true)
|
82
|
+
time = Time.new
|
83
|
+
time_string = time.strftime("%H:%M:%S")
|
84
|
+
STDERR.puts "#{time_string if timestamp} #{prefix} #{string}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def get_host_ip(host_name)
|
88
|
+
while true
|
89
|
+
process = run_os_command host_name, "docker-machine ip #{host_name}"
|
90
|
+
if process[:process].exitstatus == 0
|
91
|
+
ip = process[:stdout][/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/,0]
|
92
|
+
if !ip.nil?
|
93
|
+
formatted_log_to_stdout("OUT [#{host_name}]", "Got IP: #{ip}")
|
94
|
+
return ip
|
95
|
+
end
|
96
|
+
else
|
97
|
+
formatted_log_to_stdout("OUT [#{host_name}]", 'Waiting 10 seconds for IP.')
|
98
|
+
sleep 10
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def print_provisioning_plan(host_name)
|
104
|
+
host_config = @config_to_provision[host_name]
|
105
|
+
formatted_log_to_stdout "PLAN [#{host_name}]", "docker machine create #{host_config.get_creation_string} #{host_name}"
|
106
|
+
end
|
107
|
+
|
108
|
+
def run_os_command(host_name, command, stream_stdout=true, stream_stderr=true)
|
109
|
+
process_hash = {}
|
110
|
+
process = Open4::popen4('sh') do |pid, stdin, stdout, stderr|
|
111
|
+
stdin.puts command
|
112
|
+
stdin.close
|
113
|
+
if stream_stdout
|
114
|
+
stdout.each do |line|
|
115
|
+
formatted_log_to_stdout("OUT [#{host_name}]", line)
|
116
|
+
process_hash[:stdout] ||= ''
|
117
|
+
process_hash[:stdout] += line
|
118
|
+
end
|
119
|
+
end
|
120
|
+
if stream_stderr
|
121
|
+
stderr.each do |line|
|
122
|
+
formatted_log_to_stderr("ERROR [#{host_name}]", line)
|
123
|
+
process_hash[:stderr] ||= ''
|
124
|
+
process_hash[:stderr] += line
|
125
|
+
end
|
126
|
+
end
|
127
|
+
process_hash[:pid] = pid
|
128
|
+
end
|
129
|
+
process_hash[:process] = process
|
130
|
+
process_hash
|
131
|
+
end
|
132
|
+
end
|
data/lib/scalemail.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
3
|
+
require_relative 'configurator'
|
4
|
+
require_relative 'provisioner'
|
5
|
+
|
6
|
+
class Scalemail
|
7
|
+
attr_reader :options, :configuration
|
8
|
+
|
9
|
+
def initialize(args)
|
10
|
+
@options = OpenStruct.new
|
11
|
+
opt_parser = OptionParser.new do |opts|
|
12
|
+
opts.banner = 'Usage: scalemail -m DOCKER_MACHINE_CONFIG_PATH [options]'
|
13
|
+
opts.separator ''
|
14
|
+
opts.separator 'Specific options:'
|
15
|
+
|
16
|
+
if args.empty?
|
17
|
+
args << '-h'
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on('-m', '--docker-machine-config FILE', 'Docker Machine config file to load') { |cfile| @options.docker_machine_config_file = cfile }
|
21
|
+
# opts.on('-c FILE', 'Docker Compose config file to load') { |cfile| @options.docker_compose_config_file = cfile }
|
22
|
+
|
23
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
24
|
+
puts opts
|
25
|
+
exit
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
opt_parser.parse!(args)
|
30
|
+
config = get_config
|
31
|
+
provision config
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_config
|
35
|
+
if @configuration.nil?
|
36
|
+
config = configure @options.docker_machine_config_file
|
37
|
+
return config
|
38
|
+
else
|
39
|
+
return @configuration
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def configure(config_file)
|
44
|
+
configuration = Configurator::Config.new(config_file)
|
45
|
+
configuration
|
46
|
+
end
|
47
|
+
|
48
|
+
def provision(config_tree)
|
49
|
+
provisioner = Provisioner.new(config_tree)
|
50
|
+
provisioner.provision_hosts
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/lib/test.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'open4'
|
3
|
+
include Open4
|
4
|
+
|
5
|
+
stdin = '42'
|
6
|
+
stdout = ''
|
7
|
+
stderr = ''
|
8
|
+
|
9
|
+
t = bg 'ruby -e"sleep 4; puts ARGF.read"', 0=>stdin, 1=>stdout, 2=>stderr
|
10
|
+
|
11
|
+
waiter = Thread.new{ y t.pid => t.exitstatus } # t.exitstatus is a blocking call!
|
12
|
+
|
13
|
+
while((status = t.status))
|
14
|
+
y "status" => status
|
15
|
+
sleep 1
|
16
|
+
end
|
17
|
+
|
18
|
+
waiter.join
|
19
|
+
|
20
|
+
y "stdout" => stdout
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
describe 'CLI Interaction' do
|
4
|
+
before do
|
5
|
+
$stdout = StringIO.new
|
6
|
+
$stderr = StringIO.new
|
7
|
+
@banner_message = "Usage: scalemail -m DOCKER_MACHINE_CONFIG_PATH [options]\n"\
|
8
|
+
"\n"\
|
9
|
+
"Specific options:\n"\
|
10
|
+
" -m, --docker-machine-config FILE Docker Machine config file to load\n"\
|
11
|
+
" -h, --help Show this message\n"
|
12
|
+
end
|
13
|
+
after(:all) do
|
14
|
+
$stdout = STDOUT
|
15
|
+
$stderr = STDERR
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should show usage banner if -h option used' do
|
19
|
+
expect {Scalemail.new(%w(-h))}.to raise_error(SystemExit)
|
20
|
+
out = $stdout.string
|
21
|
+
expect(out).to eq(@banner_message)
|
22
|
+
end
|
23
|
+
it 'should show usage banner if --help option used' do
|
24
|
+
expect {Scalemail.new(%w(--help))}.to raise_error(SystemExit)
|
25
|
+
out = $stdout.string
|
26
|
+
expect(out).to eq(@banner_message)
|
27
|
+
end
|
28
|
+
it 'should show usage banner if no options provided' do
|
29
|
+
expect {Scalemail.new([])}.to raise_error(SystemExit)
|
30
|
+
out = $stdout.string
|
31
|
+
expect(out).to eq(@banner_message)
|
32
|
+
end
|
33
|
+
it 'should fail with error if DockerMachine config file not specified' do
|
34
|
+
#TODO
|
35
|
+
end
|
36
|
+
it 'should fail with error if DockerCompose file not specified' do
|
37
|
+
#TODO
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,408 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
def write_to_tmp_file(file_content)
|
5
|
+
tmpfile = Tempfile.new('rspec_tmp')
|
6
|
+
tmpfile.write(file_content)
|
7
|
+
tmpfile.close
|
8
|
+
tmpfile
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'Configuration Loading' do
|
12
|
+
|
13
|
+
before do
|
14
|
+
$stdout = StringIO.new
|
15
|
+
$stderr = StringIO.new
|
16
|
+
@config_tree_test_file = File.dirname(__FILE__)+ '/../resources/host-tree-generation-test.yml'
|
17
|
+
end
|
18
|
+
after(:all) do
|
19
|
+
$stdout = STDOUT
|
20
|
+
$stderr = STDERR
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should fail with error if DockerMachine config file not found' do
|
24
|
+
expect { Configurator::Config.new('fakefile') }.to raise_error(SystemExit, /Docker Machine configuration file not found: fakefile/)
|
25
|
+
end
|
26
|
+
it 'should fail with error if DockerMachine config file cannot be read' do
|
27
|
+
tmpfile = Tempfile.new('fakefile')
|
28
|
+
tmpfile.chmod(000)
|
29
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, /Can't read Docker Machine configuration file: #{tmpfile.path}/)
|
30
|
+
end
|
31
|
+
it 'should fail with error if DockerCompose file not found' do
|
32
|
+
#TODO
|
33
|
+
end
|
34
|
+
it 'should fail with error if DockerCompose file cannot be read' do
|
35
|
+
#TODO
|
36
|
+
end
|
37
|
+
it 'should fail with error if driver is unknown' do
|
38
|
+
file_content = "options:\n driver: aws"
|
39
|
+
tmpfile = write_to_tmp_file(file_content)
|
40
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
41
|
+
/Invalid Docker Machine config: unknown driver 'aws'./)
|
42
|
+
end
|
43
|
+
it 'should fail with error if driver is amazonec2 and aws-credentials-profile is nil' do
|
44
|
+
file_content = "options:\n driver: amazonec2"
|
45
|
+
tmpfile = write_to_tmp_file(file_content)
|
46
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
47
|
+
/Invalid Docker Machine config: driver set as 'amazonec2', but 'aws-credentials-profile' parameter not set./)
|
48
|
+
end
|
49
|
+
it 'should fail with error if hosts section is not defined' do
|
50
|
+
file_content = "options:\n"\
|
51
|
+
" driver: amazonec2\n" \
|
52
|
+
" aws-credentials-profile: default\n"
|
53
|
+
tmpfile = write_to_tmp_file(file_content)
|
54
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
55
|
+
/Invalid Docker Machine config: hosts are not defined./)
|
56
|
+
end
|
57
|
+
it 'should fail with error if host-type is not defined in host node' do
|
58
|
+
file_content = "options:\n"\
|
59
|
+
" driver: amazonec2\n" \
|
60
|
+
" aws-credentials-profile: default\n"\
|
61
|
+
"hosts:\n"\
|
62
|
+
" fail-host:\n"\
|
63
|
+
" aparam: something"
|
64
|
+
tmpfile = write_to_tmp_file(file_content)
|
65
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
66
|
+
/Invalid Docker Machine config: host-type is not defined for host 'fail-host'./)
|
67
|
+
end
|
68
|
+
it 'should fail with error if host type is unknown' do
|
69
|
+
file_content = "options:\n"\
|
70
|
+
" driver: amazonec2\n" \
|
71
|
+
" aws-credentials-profile: default\n"\
|
72
|
+
"hosts:\n"\
|
73
|
+
" fail-host:\n"\
|
74
|
+
" host-type: something"
|
75
|
+
tmpfile = write_to_tmp_file(file_content)
|
76
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
77
|
+
/Invalid Docker Machine config: unknown host-type 'something' for host 'fail-host'./)
|
78
|
+
end
|
79
|
+
it 'should fail with error if hosts-amount is not defined in host node' do
|
80
|
+
file_content = "options:\n"\
|
81
|
+
" driver: amazonec2\n" \
|
82
|
+
" aws-credentials-profile: default\n"\
|
83
|
+
"hosts:\n"\
|
84
|
+
" fail-host:\n"\
|
85
|
+
" host-type: generic"
|
86
|
+
tmpfile = write_to_tmp_file(file_content)
|
87
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
88
|
+
/Invalid Docker Machine config: hosts-amount not defined for host 'fail-host'./)
|
89
|
+
end
|
90
|
+
it 'should fail with error if hosts-amount is not integer' do
|
91
|
+
file_content = "options:\n"\
|
92
|
+
" driver: amazonec2\n" \
|
93
|
+
" aws-credentials-profile: default\n"\
|
94
|
+
"hosts:\n"\
|
95
|
+
" fail-host:\n"\
|
96
|
+
" host-type: generic\n"\
|
97
|
+
" hosts-amount: fail"
|
98
|
+
tmpfile = write_to_tmp_file(file_content)
|
99
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
100
|
+
/Invalid Docker Machine config: hosts-amount must be integer. Host: 'fail-host'./)
|
101
|
+
end
|
102
|
+
it 'should fail with error if AWS region not defined' do
|
103
|
+
file_content = "options:\n"\
|
104
|
+
" driver: amazonec2\n" \
|
105
|
+
" aws-credentials-profile: default\n"\
|
106
|
+
"hosts:\n"\
|
107
|
+
" fail-host:\n"\
|
108
|
+
" host-type: generic\n"\
|
109
|
+
" hosts-amount: 1\n"
|
110
|
+
tmpfile = write_to_tmp_file(file_content)
|
111
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
112
|
+
/Invalid Docker Machine config: amazonec2-region not defined for host 'fail-host'./)
|
113
|
+
end
|
114
|
+
it 'should fail with error if AWS region is not a string' do
|
115
|
+
file_content = "options:\n"\
|
116
|
+
" driver: amazonec2\n" \
|
117
|
+
" aws-credentials-profile: default\n"\
|
118
|
+
"hosts:\n"\
|
119
|
+
" fail-host:\n"\
|
120
|
+
" host-type: generic\n"\
|
121
|
+
" hosts-amount: 1\n"\
|
122
|
+
" amazonec2-region: \n"\
|
123
|
+
" - test"
|
124
|
+
tmpfile = write_to_tmp_file(file_content)
|
125
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
126
|
+
/Invalid Docker Machine config: amazonec2-region must be a string. Host: 'fail-host'./)
|
127
|
+
end
|
128
|
+
it 'should fail with error if AWS availability zone not defined' do
|
129
|
+
file_content = "options:\n"\
|
130
|
+
" driver: amazonec2\n" \
|
131
|
+
" aws-credentials-profile: default\n"\
|
132
|
+
"hosts:\n"\
|
133
|
+
" fail-host:\n"\
|
134
|
+
" host-type: generic\n"\
|
135
|
+
" hosts-amount: 1\n"\
|
136
|
+
" amazonec2-region: region"
|
137
|
+
tmpfile = write_to_tmp_file(file_content)
|
138
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
139
|
+
/Invalid Docker Machine config: amazonec2-zone not defined for host 'fail-host'./)
|
140
|
+
end
|
141
|
+
it 'should fail with error if AWS availability zone is not a string' do
|
142
|
+
file_content = "options:\n"\
|
143
|
+
" driver: amazonec2\n" \
|
144
|
+
" aws-credentials-profile: default\n"\
|
145
|
+
"hosts:\n"\
|
146
|
+
" fail-host:\n"\
|
147
|
+
" host-type: generic\n"\
|
148
|
+
" hosts-amount: 1\n"\
|
149
|
+
" amazonec2-region: region\n"\
|
150
|
+
" amazonec2-zone: \n"\
|
151
|
+
" - test"
|
152
|
+
tmpfile = write_to_tmp_file(file_content)
|
153
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
154
|
+
/Invalid Docker Machine config: amazonec2-zone must be a string. Host: 'fail-host'./)
|
155
|
+
end
|
156
|
+
it 'should fail with error if AWS VPC ID not defined' do
|
157
|
+
file_content = "options:\n"\
|
158
|
+
" driver: amazonec2\n" \
|
159
|
+
" aws-credentials-profile: default\n"\
|
160
|
+
"hosts:\n"\
|
161
|
+
" fail-host:\n"\
|
162
|
+
" host-type: generic\n"\
|
163
|
+
" hosts-amount: 1\n"\
|
164
|
+
" amazonec2-region: region\n"\
|
165
|
+
" amazonec2-zone: zone"
|
166
|
+
tmpfile = write_to_tmp_file(file_content)
|
167
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
168
|
+
/Invalid Docker Machine config: amazonec2-vpc-id not defined for host 'fail-host'./)
|
169
|
+
end
|
170
|
+
it 'should fail with error if AWS VPC ID is not a string' do
|
171
|
+
file_content = "options:\n"\
|
172
|
+
" driver: amazonec2\n" \
|
173
|
+
" aws-credentials-profile: default\n"\
|
174
|
+
"hosts:\n"\
|
175
|
+
" fail-host:\n"\
|
176
|
+
" host-type: generic\n"\
|
177
|
+
" hosts-amount: 1\n"\
|
178
|
+
" amazonec2-region: region\n"\
|
179
|
+
" amazonec2-zone: zone\n"\
|
180
|
+
" amazonec2-vpc-id:\n"\
|
181
|
+
" - test"
|
182
|
+
tmpfile = write_to_tmp_file(file_content)
|
183
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
184
|
+
/Invalid Docker Machine config: amazonec2-vpc-id must be a string. Host: 'fail-host'./)
|
185
|
+
end
|
186
|
+
it 'should fail with error if AWS Subnet ID not defined' do
|
187
|
+
file_content = "options:\n"\
|
188
|
+
" driver: amazonec2\n" \
|
189
|
+
" aws-credentials-profile: default\n"\
|
190
|
+
"hosts:\n"\
|
191
|
+
" fail-host:\n"\
|
192
|
+
" host-type: generic\n"\
|
193
|
+
" hosts-amount: 1\n"\
|
194
|
+
" amazonec2-region: region\n"\
|
195
|
+
" amazonec2-zone: zone\n"\
|
196
|
+
" amazonec2-vpc-id: vpc"
|
197
|
+
tmpfile = write_to_tmp_file(file_content)
|
198
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
199
|
+
/Invalid Docker Machine config: amazonec2-subnet-id not defined for host 'fail-host'./)
|
200
|
+
end
|
201
|
+
it 'should fail with error if AWS Subnet ID is not a string' do
|
202
|
+
file_content = "options:\n"\
|
203
|
+
" driver: amazonec2\n" \
|
204
|
+
" aws-credentials-profile: default\n"\
|
205
|
+
"hosts:\n"\
|
206
|
+
" fail-host:\n"\
|
207
|
+
" host-type: generic\n"\
|
208
|
+
" hosts-amount: 1\n"\
|
209
|
+
" amazonec2-region: region\n"\
|
210
|
+
" amazonec2-zone: zone\n"\
|
211
|
+
" amazonec2-vpc-id: vpc\n"\
|
212
|
+
" amazonec2-subnet-id:\n"\
|
213
|
+
" - test"
|
214
|
+
tmpfile = write_to_tmp_file(file_content)
|
215
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
216
|
+
/Invalid Docker Machine config: amazonec2-subnet-id must be a string. Host: 'fail-host'./)
|
217
|
+
end
|
218
|
+
it 'should fail with error if AWS Security Group not defined' do
|
219
|
+
file_content = "options:\n"\
|
220
|
+
" driver: amazonec2\n" \
|
221
|
+
" aws-credentials-profile: default\n"\
|
222
|
+
"hosts:\n"\
|
223
|
+
" fail-host:\n"\
|
224
|
+
" host-type: generic\n"\
|
225
|
+
" hosts-amount: 1\n"\
|
226
|
+
" amazonec2-region: region\n"\
|
227
|
+
" amazonec2-zone: zone\n"\
|
228
|
+
" amazonec2-vpc-id: vpc\n"\
|
229
|
+
" amazonec2-subnet-id: subnet"
|
230
|
+
tmpfile = write_to_tmp_file(file_content)
|
231
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
232
|
+
/Invalid Docker Machine config: amazonec2-security-group not defined for host 'fail-host'./)
|
233
|
+
end
|
234
|
+
it 'should fail with error if AWS Security Group is not a string' do
|
235
|
+
file_content = "options:\n"\
|
236
|
+
" driver: amazonec2\n" \
|
237
|
+
" aws-credentials-profile: default\n"\
|
238
|
+
"hosts:\n"\
|
239
|
+
" fail-host:\n"\
|
240
|
+
" host-type: generic\n"\
|
241
|
+
" hosts-amount: 1\n"\
|
242
|
+
" amazonec2-region: region\n"\
|
243
|
+
" amazonec2-zone: zone\n"\
|
244
|
+
" amazonec2-vpc-id: vpc\n"\
|
245
|
+
" amazonec2-subnet-id: subnet\n"\
|
246
|
+
" amazonec2-security-group:\n"\
|
247
|
+
" - test"
|
248
|
+
tmpfile = write_to_tmp_file(file_content)
|
249
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
250
|
+
/Invalid Docker Machine config: amazonec2-security-group must be a string. Host: 'fail-host'./)
|
251
|
+
end
|
252
|
+
it 'should fail with error if AWS Instance Type not defined' do
|
253
|
+
file_content = "options:\n"\
|
254
|
+
" driver: amazonec2\n" \
|
255
|
+
" aws-credentials-profile: default\n"\
|
256
|
+
"hosts:\n"\
|
257
|
+
" fail-host:\n"\
|
258
|
+
" host-type: generic\n"\
|
259
|
+
" hosts-amount: 1\n"\
|
260
|
+
" amazonec2-region: region\n"\
|
261
|
+
" amazonec2-zone: zone\n"\
|
262
|
+
" amazonec2-vpc-id: vpc\n"\
|
263
|
+
" amazonec2-subnet-id: subnet\n"\
|
264
|
+
" amazonec2-security-group: sg"
|
265
|
+
tmpfile = write_to_tmp_file(file_content)
|
266
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
267
|
+
/Invalid Docker Machine config: amazonec2-instance-type not defined for host 'fail-host'./)
|
268
|
+
end
|
269
|
+
it 'should fail with error if AWS Instance Type is not a string' do
|
270
|
+
file_content = "options:\n"\
|
271
|
+
" driver: amazonec2\n" \
|
272
|
+
" aws-credentials-profile: default\n"\
|
273
|
+
"hosts:\n"\
|
274
|
+
" fail-host:\n"\
|
275
|
+
" host-type: generic\n"\
|
276
|
+
" hosts-amount: 1\n"\
|
277
|
+
" amazonec2-region: region\n"\
|
278
|
+
" amazonec2-zone: zone\n"\
|
279
|
+
" amazonec2-vpc-id: vpc\n"\
|
280
|
+
" amazonec2-subnet-id: subnet\n"\
|
281
|
+
" amazonec2-security-group: sg\n"\
|
282
|
+
" amazonec2-instance-type:\n"\
|
283
|
+
" - test"
|
284
|
+
tmpfile = write_to_tmp_file(file_content)
|
285
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
286
|
+
/Invalid Docker Machine config: amazonec2-instance-type must be a string. Host: 'fail-host'./)
|
287
|
+
end
|
288
|
+
it 'should fail with error if AWS AMI is not a string' do
|
289
|
+
file_content = "options:\n"\
|
290
|
+
" driver: amazonec2\n" \
|
291
|
+
" aws-credentials-profile: default\n"\
|
292
|
+
"hosts:\n"\
|
293
|
+
" fail-host:\n"\
|
294
|
+
" host-type: generic\n"\
|
295
|
+
" hosts-amount: 1\n"\
|
296
|
+
" amazonec2-region: region\n"\
|
297
|
+
" amazonec2-zone: zone\n"\
|
298
|
+
" amazonec2-vpc-id: vpc\n"\
|
299
|
+
" amazonec2-subnet-id: subnet\n"\
|
300
|
+
" amazonec2-security-group: sg\n"\
|
301
|
+
" amazonec2-instance-type: t2.micro\n"\
|
302
|
+
" amazonec2-ami:\n"\
|
303
|
+
" - test"
|
304
|
+
tmpfile = write_to_tmp_file(file_content)
|
305
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
306
|
+
/Invalid Docker Machine config: amazonec2-ami must be a string. Host: 'fail-host'./)
|
307
|
+
end
|
308
|
+
it 'should fail with error if driver is not defined in options section' do
|
309
|
+
file_content = "driver: aws"
|
310
|
+
tmpfile = write_to_tmp_file(file_content)
|
311
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
312
|
+
/Wrong Docker Machine config format: options => driver parameter not found./)
|
313
|
+
end
|
314
|
+
it 'should fail with error if engine-opts is not an array' do
|
315
|
+
file_content = "options:\n"\
|
316
|
+
" driver: amazonec2\n" \
|
317
|
+
" aws-credentials-profile: default\n"\
|
318
|
+
"hosts:\n"\
|
319
|
+
" fail-host:\n"\
|
320
|
+
" host-type: generic\n"\
|
321
|
+
" hosts-amount: 2\n"\
|
322
|
+
" engine-opts:\n" \
|
323
|
+
" op1: sdf"
|
324
|
+
tmpfile = write_to_tmp_file(file_content)
|
325
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
326
|
+
/Invalid Docker Machine config: engine-opts must be an array in yml notation. Host: 'fail-host'./)
|
327
|
+
end
|
328
|
+
it 'should fail with error if commands-to-execute is not an array' do
|
329
|
+
file_content = "options:\n"\
|
330
|
+
" driver: amazonec2\n" \
|
331
|
+
" aws-credentials-profile: default\n"\
|
332
|
+
"hosts:\n"\
|
333
|
+
" fail-host:\n"\
|
334
|
+
" host-type: generic\n"\
|
335
|
+
" hosts-amount: 2\n"\
|
336
|
+
" commands-to-execute:\n" \
|
337
|
+
" op1: sdf"
|
338
|
+
tmpfile = write_to_tmp_file(file_content)
|
339
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
340
|
+
/Invalid Docker Machine config: commands-to-execute must be an array in yml notation. Host: 'fail-host'./)
|
341
|
+
end
|
342
|
+
it 'should fail with error if engine-install-url is not a string' do
|
343
|
+
file_content = "options:\n"\
|
344
|
+
" driver: amazonec2\n" \
|
345
|
+
" aws-credentials-profile: default\n"\
|
346
|
+
"hosts:\n"\
|
347
|
+
" fail-host:\n"\
|
348
|
+
" host-type: generic\n"\
|
349
|
+
" hosts-amount: 2\n"\
|
350
|
+
" engine-install-url:\n" \
|
351
|
+
" op1: sdf"
|
352
|
+
tmpfile = write_to_tmp_file(file_content)
|
353
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
354
|
+
/Invalid Docker Machine config: engine-install-url must be a string. Host: 'fail-host'./)
|
355
|
+
end
|
356
|
+
it 'should fail with error if host-type is "swarm-node" and swarm-discovery is not defined' do
|
357
|
+
file_content = "options:\n"\
|
358
|
+
" driver: amazonec2\n" \
|
359
|
+
" aws-credentials-profile: default\n"\
|
360
|
+
"hosts:\n"\
|
361
|
+
" fail-host:\n"\
|
362
|
+
" host-type: swarm-node\n"\
|
363
|
+
" hosts-amount: 2"
|
364
|
+
tmpfile = write_to_tmp_file(file_content)
|
365
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
366
|
+
/Invalid Docker Machine config: host-type is swarm-node, but swarm-discovery is not set. Host: 'fail-host'./)
|
367
|
+
end
|
368
|
+
it 'should fail with error if host-type is "swarm-node" and swarm-discovery is not a string' do
|
369
|
+
file_content = "options:\n"\
|
370
|
+
" driver: amazonec2\n" \
|
371
|
+
" aws-credentials-profile: default\n"\
|
372
|
+
"hosts:\n"\
|
373
|
+
" fail-host:\n"\
|
374
|
+
" host-type: swarm-node\n"\
|
375
|
+
" hosts-amount: 2\n"\
|
376
|
+
" swarm-discovery:\n" \
|
377
|
+
" op1: sdf"
|
378
|
+
tmpfile = write_to_tmp_file(file_content)
|
379
|
+
expect { Configurator::Config.new(tmpfile.path) }.to raise_error(SystemExit, \
|
380
|
+
/Invalid Docker Machine config: swarm-discovery must be a string. Host: 'fail-host'./)
|
381
|
+
end
|
382
|
+
it 'should return proper creation string for amazonec2 generic host type' do
|
383
|
+
test_creation_string = "--driver amazonec2 "\
|
384
|
+
"--engine-install-url 'http://test.com' --engine-opt='opt1' --engine-opt='opt2' "\
|
385
|
+
"--amazonec2-region region --amazonec2-zone zone --amazonec2-vpc-id vpc "\
|
386
|
+
"--amazonec2-subnet-id subnet --amazonec2-security-group sg --amazonec2-instance-type it"
|
387
|
+
config = Configurator::Config.new(@config_tree_test_file)
|
388
|
+
expect(config.config_tree['generic-host'].get_creation_string).to eq(test_creation_string)
|
389
|
+
end
|
390
|
+
it 'should return proper creation string for swarm-node host type' do
|
391
|
+
test_creation_string = "--driver amazonec2 "\
|
392
|
+
"--engine-install-url 'http://test.com' --engine-opt='opt1' --engine-opt='opt2' "\
|
393
|
+
"--swarm --swarm-discovery='something_somewhere' "\
|
394
|
+
"--amazonec2-region region --amazonec2-zone zone --amazonec2-vpc-id vpc "\
|
395
|
+
"--amazonec2-subnet-id subnet --amazonec2-security-group sg --amazonec2-instance-type it"
|
396
|
+
config = Configurator::Config.new(@config_tree_test_file)
|
397
|
+
expect(config.config_tree['swarm-node'].get_creation_string).to eq(test_creation_string)
|
398
|
+
end
|
399
|
+
it 'should return proper creation string for swarm-master host type' do
|
400
|
+
test_creation_string = "--driver amazonec2 "\
|
401
|
+
"--engine-install-url 'http://test.com' --engine-opt='opt1' --engine-opt='opt2' "\
|
402
|
+
"--swarm-master --swarm-discovery='something_somewhere' "\
|
403
|
+
"--amazonec2-region region --amazonec2-zone zone --amazonec2-vpc-id vpc "\
|
404
|
+
"--amazonec2-subnet-id subnet --amazonec2-security-group sg --amazonec2-instance-type it"
|
405
|
+
config = Configurator::Config.new(@config_tree_test_file)
|
406
|
+
expect(config.config_tree['swarm-master'].get_creation_string).to eq(test_creation_string)
|
407
|
+
end
|
408
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require_relative '../../lib/provisioner'
|
3
|
+
|
4
|
+
describe 'CLI Interaction' do
|
5
|
+
before do
|
6
|
+
$stdout = StringIO.new
|
7
|
+
$stderr = StringIO.new
|
8
|
+
end
|
9
|
+
after(:all) do
|
10
|
+
$stdout = STDOUT
|
11
|
+
$stderr = STDERR
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should print timestamped message in STDOUT' do
|
15
|
+
# TODO: fix
|
16
|
+
# Provisioner.new(nil).formatted_log_to_stdout('prefix', 'string')
|
17
|
+
# expect($stdout.string).to match(/\d{2}:\d{2} prefix string/)
|
18
|
+
end
|
19
|
+
it 'should print message without timestamp in STDOUT' do
|
20
|
+
#TODO: fix
|
21
|
+
#Provisioner.new(nil).formatted_log_to_stdout('prefix', 'string', false)
|
22
|
+
#expect($stdout.string).to match(/prefix string/)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require_relative '../../lib/scalemail'
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
describe Scalemail do
|
7
|
+
|
8
|
+
#
|
9
|
+
# it 'raises error if configuration file cannot be readed' do
|
10
|
+
# begin
|
11
|
+
# Scalemail.new(%w(-m fakefile))
|
12
|
+
# rescue SystemExit
|
13
|
+
# expect($stderr.string).to match("Can't read Docker Machine configuration file: fakefile")
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
# it 'shows usage banner if no options provided' do
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# end
|
20
|
+
# it 'shows usage banner if -h key provided' do
|
21
|
+
# begin
|
22
|
+
# Scalemail.new(%w(-h))
|
23
|
+
# rescue SystemExit
|
24
|
+
# expect($stdout.string).to match(@banner_message)
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
# it 'shows usage banner if --help key provided' do
|
28
|
+
# begin
|
29
|
+
# Scalemail.new(%w(--help))
|
30
|
+
# rescue SystemExit
|
31
|
+
# expect($stdout.string).to match(@banner_message)
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
options:
|
2
|
+
driver: amazonec2
|
3
|
+
aws-credentials-profile: default
|
4
|
+
hosts:
|
5
|
+
generic-host:
|
6
|
+
host-type: generic
|
7
|
+
hosts-amount: 1
|
8
|
+
engine-install-url: http://test.com
|
9
|
+
engine-opts:
|
10
|
+
- opt1
|
11
|
+
- opt2
|
12
|
+
amazonec2-region: region
|
13
|
+
amazonec2-zone: zone
|
14
|
+
amazonec2-vpc-id: vpc
|
15
|
+
amazonec2-subnet-id: subnet
|
16
|
+
amazonec2-security-group: sg
|
17
|
+
amazonec2-instance-type: it
|
18
|
+
|
19
|
+
swarm-node:
|
20
|
+
host-type: swarm-node
|
21
|
+
hosts-amount: 1
|
22
|
+
engine-install-url: http://test.com
|
23
|
+
engine-opts:
|
24
|
+
- opt1
|
25
|
+
- opt2
|
26
|
+
swarm-discovery: something_somewhere
|
27
|
+
amazonec2-region: region
|
28
|
+
amazonec2-zone: zone
|
29
|
+
amazonec2-vpc-id: vpc
|
30
|
+
amazonec2-subnet-id: subnet
|
31
|
+
amazonec2-security-group: sg
|
32
|
+
amazonec2-instance-type: it
|
33
|
+
|
34
|
+
swarm-node-multiple:
|
35
|
+
host-type: swarm-node
|
36
|
+
hosts-amount: 3
|
37
|
+
engine-install-url: http://test.com
|
38
|
+
engine-opts:
|
39
|
+
- opt1
|
40
|
+
- opt2
|
41
|
+
swarm-discovery: something_somewhere
|
42
|
+
amazonec2-region: region
|
43
|
+
amazonec2-zone: zone
|
44
|
+
amazonec2-vpc-id: vpc
|
45
|
+
amazonec2-subnet-id: subnet
|
46
|
+
amazonec2-security-group: sg
|
47
|
+
amazonec2-instance-type: it
|
48
|
+
|
49
|
+
swarm-master:
|
50
|
+
host-type: swarm-master
|
51
|
+
hosts-amount: 1
|
52
|
+
engine-install-url: http://test.com
|
53
|
+
engine-opts:
|
54
|
+
- opt1
|
55
|
+
- opt2
|
56
|
+
swarm-discovery: something_somewhere
|
57
|
+
amazonec2-region: region
|
58
|
+
amazonec2-zone: zone
|
59
|
+
amazonec2-vpc-id: vpc
|
60
|
+
amazonec2-subnet-id: subnet
|
61
|
+
amazonec2-security-group: sg
|
62
|
+
amazonec2-instance-type: it
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
4
|
+
# this file to always be loaded, without a need to explicitly require it in any
|
5
|
+
# files.
|
6
|
+
#
|
7
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
8
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
9
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
10
|
+
# individual file that may not need all of that loaded. Instead, consider making
|
11
|
+
# a separate helper file that requires the additional dependencies and performs
|
12
|
+
# the additional setup, and require it from the spec files that actually need
|
13
|
+
# it.
|
14
|
+
#
|
15
|
+
# The `.rspec` file also contains a few flags that are not defaults but that
|
16
|
+
# users commonly want.
|
17
|
+
#
|
18
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
19
|
+
RSpec.configure do |config|
|
20
|
+
# rspec-expectations config goes here. You can use an alternate
|
21
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
22
|
+
# assertions if you prefer.
|
23
|
+
config.expect_with :rspec do |expectations|
|
24
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
25
|
+
# and `failure_message` of custom matchers include text for helper methods
|
26
|
+
# defined using `chain`, e.g.:
|
27
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
28
|
+
# # => "be bigger than 2 and smaller than 4"
|
29
|
+
# ...rather than:
|
30
|
+
# # => "be bigger than 2"
|
31
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
32
|
+
end
|
33
|
+
|
34
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
35
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
36
|
+
config.mock_with :rspec do |mocks|
|
37
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
38
|
+
# a real object. This is generally recommended, and will default to
|
39
|
+
# `true` in RSpec 4.
|
40
|
+
mocks.verify_partial_doubles = true
|
41
|
+
end
|
42
|
+
|
43
|
+
# The settings below are suggested to provide a good initial experience
|
44
|
+
# with RSpec, but feel free to customize to your heart's content.
|
45
|
+
=begin
|
46
|
+
# These two settings work together to allow you to limit a spec run
|
47
|
+
# to individual examples or groups you care about by tagging them with
|
48
|
+
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
49
|
+
# get run.
|
50
|
+
config.filter_run :focus
|
51
|
+
config.run_all_when_everything_filtered = true
|
52
|
+
|
53
|
+
# Allows RSpec to persist some state between runs in order to support
|
54
|
+
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
55
|
+
# you configure your source control system to ignore this file.
|
56
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
57
|
+
|
58
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
59
|
+
# recommended. For more details, see:
|
60
|
+
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
61
|
+
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
62
|
+
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
63
|
+
config.disable_monkey_patching!
|
64
|
+
|
65
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
66
|
+
# be too noisy due to issues in dependencies.
|
67
|
+
config.warnings = true
|
68
|
+
=end
|
69
|
+
|
70
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
71
|
+
# file, and it's useful to allow more verbose output when running an
|
72
|
+
# individual spec file.
|
73
|
+
if config.files_to_run.one?
|
74
|
+
# Use the documentation formatter for detailed output,
|
75
|
+
# unless a formatter has already been configured
|
76
|
+
# (e.g. via a command-line flag).
|
77
|
+
config.default_formatter = 'doc'
|
78
|
+
end
|
79
|
+
=begin
|
80
|
+
# Print the 10 slowest examples and example groups at the
|
81
|
+
# end of the spec run, to help surface which specs are running
|
82
|
+
# particularly slow.
|
83
|
+
config.profile_examples = 10
|
84
|
+
|
85
|
+
# Run specs in random order to surface order dependencies. If you find an
|
86
|
+
# order dependency and want to debug it, you can fix the order by providing
|
87
|
+
# the seed, which is printed after each run.
|
88
|
+
# --seed 1234
|
89
|
+
config.order = :random
|
90
|
+
|
91
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
92
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
93
|
+
# test failures related to randomization by passing the same `--seed` value
|
94
|
+
# as the one that triggered the failure.
|
95
|
+
Kernel.srand config.seed
|
96
|
+
=end
|
97
|
+
end
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scalemail
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ievgen Iezhachenko
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-05-13 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Framework for microservises scaling using Docker and cloud platforms.
|
14
|
+
email: ievgen.iezhachenko@gmail.com
|
15
|
+
executables:
|
16
|
+
- scalemail
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- bin/scalemail
|
21
|
+
- lib/configurator.rb
|
22
|
+
- lib/entry.rb
|
23
|
+
- lib/host_types.rb
|
24
|
+
- lib/hosts.rb
|
25
|
+
- lib/provisioner.rb
|
26
|
+
- lib/scalemail.rb
|
27
|
+
- lib/test.rb
|
28
|
+
- spec/lib/cli_spec.rb
|
29
|
+
- spec/lib/configuration_loading_spec.rb
|
30
|
+
- spec/lib/provisioner_spec.rb
|
31
|
+
- spec/lib/scalemail_spec.rb
|
32
|
+
- spec/resources/host-tree-generation-test.yml
|
33
|
+
- spec/spec_helper.rb
|
34
|
+
homepage: https://github.com/iiezhachenko/scalemail
|
35
|
+
licenses:
|
36
|
+
- MIT
|
37
|
+
metadata: {}
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 2.5.1
|
55
|
+
signing_key:
|
56
|
+
specification_version: 4
|
57
|
+
summary: Application scaling framework
|
58
|
+
test_files: []
|
59
|
+
has_rdoc:
|