vm_shepherd 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +5 -0
- data/README.md +31 -0
- data/Rakefile +11 -0
- data/ci/run_specs.sh +11 -0
- data/lib/vm_shepherd/ami_manager.rb +81 -0
- data/lib/vm_shepherd/ova_manager/base.rb +31 -0
- data/lib/vm_shepherd/ova_manager/deployer.rb +202 -0
- data/lib/vm_shepherd/ova_manager/destroyer.rb +29 -0
- data/lib/vm_shepherd/ova_manager/open_monkey_patch.rb +14 -0
- data/lib/vm_shepherd/shepherd.rb +166 -0
- data/lib/vm_shepherd/vapp_manager/deployer.rb +151 -0
- data/lib/vm_shepherd/vapp_manager/destroyer.rb +46 -0
- data/lib/vm_shepherd/version.rb +3 -0
- data/spec/fixtures/ova_manager/foo.ova +0 -0
- data/spec/fixtures/shepherd/aws.yml +11 -0
- data/spec/fixtures/shepherd/unknown.yml +1 -0
- data/spec/fixtures/shepherd/vcloud.yml +19 -0
- data/spec/fixtures/shepherd/vsphere.yml +20 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/vm_shepherd/ami_manager_spec.rb +146 -0
- data/spec/vm_shepherd/ova_manager/base_spec.rb +56 -0
- data/spec/vm_shepherd/ova_manager/deployer_spec.rb +134 -0
- data/spec/vm_shepherd/ova_manager/destroyer_spec.rb +42 -0
- data/spec/vm_shepherd/shepherd_spec.rb +213 -0
- data/spec/vm_shepherd/vapp_manager/deployer_spec.rb +287 -0
- data/spec/vm_shepherd/vapp_manager/destroyer_spec.rb +104 -0
- data/vm_shepherd.gemspec +31 -0
- metadata +198 -0
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'vm_shepherd/vapp_manager/deployer'
|
2
|
+
require 'vm_shepherd/vapp_manager/destroyer'
|
3
|
+
require 'vm_shepherd/ova_manager/deployer'
|
4
|
+
require 'vm_shepherd/ova_manager/destroyer'
|
5
|
+
require 'vm_shepherd/ami_manager'
|
6
|
+
|
7
|
+
module VmShepherd
|
8
|
+
class Shepherd
|
9
|
+
VCLOUD_IAAS_TYPE = 'vcloud'.freeze
|
10
|
+
VSPHERE_IAAS_TYPE = 'vsphere'.freeze
|
11
|
+
AWS_IAAS_TYPE = 'aws'.freeze
|
12
|
+
VSPHERE_TEMPLATE_PREFIX = 'tpl'.freeze
|
13
|
+
|
14
|
+
class InvalidIaas < StandardError
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(settings:)
|
19
|
+
@settings = settings
|
20
|
+
end
|
21
|
+
|
22
|
+
def deploy(path:)
|
23
|
+
case settings.iaas_type
|
24
|
+
when VCLOUD_IAAS_TYPE then
|
25
|
+
vcloud_deployer.deploy(
|
26
|
+
path,
|
27
|
+
vcloud_deploy_options,
|
28
|
+
)
|
29
|
+
when VSPHERE_IAAS_TYPE then
|
30
|
+
vsphere_deployer.deploy(
|
31
|
+
VSPHERE_TEMPLATE_PREFIX,
|
32
|
+
path,
|
33
|
+
vsphere_deploy_options,
|
34
|
+
)
|
35
|
+
when AWS_IAAS_TYPE then
|
36
|
+
ami_manager.deploy(path)
|
37
|
+
else
|
38
|
+
fail(InvalidIaas, "Unknown IaaS type: #{settings.iaas_type.inspect}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def destroy
|
43
|
+
case settings.iaas_type
|
44
|
+
when VCLOUD_IAAS_TYPE then
|
45
|
+
VmShepherd::VappManager::Destroyer.new(
|
46
|
+
{
|
47
|
+
url: settings.vapp_deployer.creds.url,
|
48
|
+
organization: settings.vapp_deployer.creds.organization,
|
49
|
+
user: settings.vapp_deployer.creds.user,
|
50
|
+
password: settings.vapp_deployer.creds.password,
|
51
|
+
},
|
52
|
+
{
|
53
|
+
vdc: settings.vapp_deployer.vdc.name,
|
54
|
+
catalog: settings.vapp_deployer.vdc.catalog,
|
55
|
+
},
|
56
|
+
Logger.new(STDOUT).tap { |l| l.level = Logger::Severity::ERROR }
|
57
|
+
).destroy(settings.vapp_deployer.vapp.name)
|
58
|
+
when VSPHERE_IAAS_TYPE then
|
59
|
+
VmShepherd::OvaManager::Destroyer.new(
|
60
|
+
settings.vm_deployer.vsphere.datacenter,
|
61
|
+
{
|
62
|
+
host: settings.vm_deployer.vcenter_creds.ip,
|
63
|
+
user: settings.vm_deployer.vcenter_creds.username,
|
64
|
+
password: settings.vm_deployer.vcenter_creds.password,
|
65
|
+
}
|
66
|
+
).clean_folder(settings.vm_deployer.vsphere.folder)
|
67
|
+
when AWS_IAAS_TYPE then
|
68
|
+
ami_manager.destroy
|
69
|
+
else
|
70
|
+
fail(InvalidIaas, "Unknown IaaS type: #{settings.iaas_type.inspect}")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
attr_reader :settings
|
77
|
+
|
78
|
+
def logger
|
79
|
+
Logger.new(STDOUT).tap do |lggr|
|
80
|
+
lggr.level = Logger::Severity::ERROR
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def debug_logger
|
85
|
+
Logger.new(STDOUT).tap do |lggr|
|
86
|
+
lggr.level = Logger::Severity::DEBUG
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def vcloud_deployer
|
91
|
+
creds = settings.vapp_deployer.creds
|
92
|
+
vdc = settings.vapp_deployer.vdc
|
93
|
+
VmShepherd::VappManager::Deployer.new(
|
94
|
+
{
|
95
|
+
url: creds.url,
|
96
|
+
organization: creds.organization,
|
97
|
+
user: creds.user,
|
98
|
+
password: creds.password,
|
99
|
+
},
|
100
|
+
{
|
101
|
+
vdc: vdc.name,
|
102
|
+
catalog: vdc.catalog,
|
103
|
+
network: vdc.network,
|
104
|
+
},
|
105
|
+
debug_logger
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
def vcloud_deploy_options
|
110
|
+
vm = settings.vapp_deployer.vapp
|
111
|
+
{
|
112
|
+
name: vm.name,
|
113
|
+
ip: vm.ip,
|
114
|
+
gateway: vm.gateway,
|
115
|
+
netmask: vm.netmask,
|
116
|
+
dns: vm.dns,
|
117
|
+
ntp: vm.ntp,
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
def vsphere_deployer
|
122
|
+
vcenter_creds = settings.vm_deployer.vcenter_creds
|
123
|
+
vsphere = settings.vm_deployer.vsphere
|
124
|
+
VmShepherd::OvaManager::Deployer.new(
|
125
|
+
{
|
126
|
+
host: vcenter_creds.ip,
|
127
|
+
user: vcenter_creds.username,
|
128
|
+
password: vcenter_creds.password,
|
129
|
+
},
|
130
|
+
{
|
131
|
+
datacenter: vsphere.datacenter,
|
132
|
+
cluster: vsphere.cluster,
|
133
|
+
resource_pool: vsphere.resource_pool,
|
134
|
+
datastore: vsphere.datastore,
|
135
|
+
network: vsphere.network,
|
136
|
+
folder: vsphere.folder,
|
137
|
+
}
|
138
|
+
)
|
139
|
+
end
|
140
|
+
|
141
|
+
def vsphere_deploy_options
|
142
|
+
vm = settings.vm_deployer.vm
|
143
|
+
{
|
144
|
+
ip: vm.ip,
|
145
|
+
gateway: vm.gateway,
|
146
|
+
netmask: vm.netmask,
|
147
|
+
dns: vm.dns,
|
148
|
+
ntp_servers: vm.ntp_servers,
|
149
|
+
}
|
150
|
+
end
|
151
|
+
|
152
|
+
def ami_manager
|
153
|
+
vm_deployer = settings.vm_deployer
|
154
|
+
VmShepherd::AmiManager.new(
|
155
|
+
aws_access_key: vm_deployer.aws_access_key,
|
156
|
+
aws_secret_key: vm_deployer.aws_secret_key,
|
157
|
+
ssh_key_name: vm_deployer.ssh_key_name,
|
158
|
+
security_group_id: vm_deployer.security_group,
|
159
|
+
public_subnet_id: vm_deployer.public_subnet_id,
|
160
|
+
private_subnet_id: vm_deployer.private_subnet_id,
|
161
|
+
elastic_ip_id: vm_deployer.elastic_ip_id,
|
162
|
+
vm_name: vm_deployer.vm_name,
|
163
|
+
)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'ruby_vcloud_sdk'
|
4
|
+
|
5
|
+
module VmShepherd
|
6
|
+
module VappManager
|
7
|
+
class Deployer
|
8
|
+
def initialize(login_info, location, logger)
|
9
|
+
@login_info = login_info
|
10
|
+
@location = location
|
11
|
+
@logger = logger
|
12
|
+
raise 'VDC must be set' unless @location[:vdc]
|
13
|
+
raise 'Catalog must be set' unless @location[:catalog]
|
14
|
+
raise 'Network must be set' unless @location[:network]
|
15
|
+
end
|
16
|
+
|
17
|
+
def deploy(vapp_template_tar_path, vapp_config)
|
18
|
+
tmpdir = Dir.mktmpdir
|
19
|
+
|
20
|
+
check_vapp_status(vapp_config)
|
21
|
+
|
22
|
+
untar_vapp_template_tar(File.expand_path(vapp_template_tar_path), tmpdir)
|
23
|
+
|
24
|
+
vapp = deploy_vapp(tmpdir, vapp_config)
|
25
|
+
reconfigure_vm(vapp, vapp_config)
|
26
|
+
vapp.power_on
|
27
|
+
ensure
|
28
|
+
FileUtils.remove_entry_secure(tmpdir, force: true)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def check_vapp_status(vapp_config)
|
34
|
+
log('Checking for existing VM') do
|
35
|
+
ip = vapp_config[:ip]
|
36
|
+
raise "VM exists at #{ip}" if system("ping -c 5 #{ip}")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def untar_vapp_template_tar(vapp_template_tar_path, dir)
|
41
|
+
log("Untarring #{vapp_template_tar_path}") do
|
42
|
+
system_or_exit("cd #{dir} && tar xfv '#{vapp_template_tar_path}'")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def client
|
47
|
+
@client ||= VCloudSdk::Client.new(
|
48
|
+
@login_info[:url],
|
49
|
+
"#{@login_info[:user]}@#{@login_info[:organization]}",
|
50
|
+
@login_info[:password],
|
51
|
+
{},
|
52
|
+
@logger,
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def deploy_vapp(ovf_dir, vapp_config)
|
57
|
+
# setup the catalog
|
58
|
+
client.delete_catalog_by_name(@location[:catalog]) if client.catalog_exists?(@location[:catalog])
|
59
|
+
catalog = client.create_catalog(@location[:catalog])
|
60
|
+
|
61
|
+
# upload template and instantiate vapp
|
62
|
+
catalog.upload_vapp_template(@location[:vdc], vapp_config[:name], ovf_dir)
|
63
|
+
|
64
|
+
# instantiate template
|
65
|
+
network_config = VCloudSdk::NetworkConfig.new(@location[:network], 'Network 1')
|
66
|
+
catalog.instantiate_vapp_template(
|
67
|
+
vapp_config[:name], @location[:vdc], vapp_config[:name], nil, nil, network_config)
|
68
|
+
rescue => e
|
69
|
+
@logger.error(e.http_body) if e.respond_to?(:http_body)
|
70
|
+
raise e
|
71
|
+
end
|
72
|
+
|
73
|
+
def reconfigure_vm(vapp, vapp_config)
|
74
|
+
vm = vapp.find_vm_by_name(vapp_config[:name])
|
75
|
+
vm.product_section_properties = build_properties(vapp_config)
|
76
|
+
vm
|
77
|
+
end
|
78
|
+
|
79
|
+
def build_properties(vapp_config)
|
80
|
+
[
|
81
|
+
{
|
82
|
+
'type' => 'string',
|
83
|
+
'key' => 'gateway',
|
84
|
+
'value' => vapp_config[:gateway],
|
85
|
+
'password' => 'false',
|
86
|
+
'userConfigurable' => 'true',
|
87
|
+
'Label' => 'Default Gateway',
|
88
|
+
'Description' => 'The default gateway address for the VM network. Leave blank if DHCP is desired.'
|
89
|
+
},
|
90
|
+
{
|
91
|
+
'type' => 'string',
|
92
|
+
'key' => 'DNS',
|
93
|
+
'value' => vapp_config[:dns],
|
94
|
+
'password' => 'false',
|
95
|
+
'userConfigurable' => 'true',
|
96
|
+
'Label' => 'DNS',
|
97
|
+
'Description' => 'The domain name servers for the VM (comma separated). Leave blank if DHCP is desired.',
|
98
|
+
},
|
99
|
+
{
|
100
|
+
'type' => 'string',
|
101
|
+
'key' => 'ntp_servers',
|
102
|
+
'value' => vapp_config[:ntp],
|
103
|
+
'password' => 'false',
|
104
|
+
'userConfigurable' => 'true',
|
105
|
+
'Label' => 'NTP Servers',
|
106
|
+
'Description' => 'Comma-delimited list of NTP servers'
|
107
|
+
},
|
108
|
+
{
|
109
|
+
'type' => 'string',
|
110
|
+
'key' => 'admin_password',
|
111
|
+
'value' => 'tempest',
|
112
|
+
'password' => 'true',
|
113
|
+
'userConfigurable' => 'true',
|
114
|
+
'Label' => 'Admin Password',
|
115
|
+
'Description' => 'This password is used to SSH into the VM. The username is "tempest".',
|
116
|
+
},
|
117
|
+
{
|
118
|
+
'type' => 'string',
|
119
|
+
'key' => 'ip0',
|
120
|
+
'value' => vapp_config[:ip],
|
121
|
+
'password' => 'false',
|
122
|
+
'userConfigurable' => 'true',
|
123
|
+
'Label' => 'IP Address',
|
124
|
+
'Description' => 'The IP address for the VM. Leave blank if DHCP is desired.',
|
125
|
+
},
|
126
|
+
{
|
127
|
+
'type' => 'string',
|
128
|
+
'key' => 'netmask0',
|
129
|
+
'value' => vapp_config[:netmask],
|
130
|
+
'password' => 'false',
|
131
|
+
'userConfigurable' => 'true',
|
132
|
+
'Label' => 'Netmask',
|
133
|
+
'Description' => 'The netmask for the VM network. Leave blank if DHCP is desired.'
|
134
|
+
}
|
135
|
+
]
|
136
|
+
end
|
137
|
+
|
138
|
+
def log(title, &blk)
|
139
|
+
@logger.debug "--- Begin: #{title.inspect} @ #{DateTime.now}"
|
140
|
+
blk.call
|
141
|
+
@logger.debug "--- End: #{title.inspect} @ #{DateTime.now}"
|
142
|
+
end
|
143
|
+
|
144
|
+
def system_or_exit(command)
|
145
|
+
log(command) do
|
146
|
+
system(command) || raise("Error executing: #{command.inspect}")
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'ruby_vcloud_sdk'
|
2
|
+
|
3
|
+
module VmShepherd
|
4
|
+
module VappManager
|
5
|
+
class Destroyer
|
6
|
+
def initialize(login_info, location, logger)
|
7
|
+
@login_info = login_info
|
8
|
+
@location = location
|
9
|
+
@logger = logger
|
10
|
+
end
|
11
|
+
|
12
|
+
def destroy(vapp_name)
|
13
|
+
delete_vapp(vapp_name)
|
14
|
+
delete_catalog
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def client
|
20
|
+
@client ||= VCloudSdk::Client.new(
|
21
|
+
@login_info[:url],
|
22
|
+
"#{@login_info[:user]}@#{@login_info[:organization]}",
|
23
|
+
@login_info[:password],
|
24
|
+
{},
|
25
|
+
@logger,
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def vdc
|
30
|
+
@vdc ||= client.find_vdc_by_name(@location[:vdc])
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete_vapp(vapp_name)
|
34
|
+
vapp = vdc.find_vapp_by_name(vapp_name)
|
35
|
+
vapp.power_off
|
36
|
+
vapp.delete
|
37
|
+
rescue VCloudSdk::ObjectNotFoundError => e
|
38
|
+
@logger.debug "Could not delete vapp '#{vapp_name}': #{e.inspect}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete_catalog
|
42
|
+
client.delete_catalog_by_name(@location[:catalog]) if client.catalog_exists?(@location[:catalog])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
Binary file
|
@@ -0,0 +1,11 @@
|
|
1
|
+
iaas_type: aws
|
2
|
+
|
3
|
+
vm_deployer:
|
4
|
+
aws_access_key: aws-access-key
|
5
|
+
aws_secret_key: aws-secret-key
|
6
|
+
ssh_key_name: ssh-key-name
|
7
|
+
security_group: security-group-id
|
8
|
+
public_subnet_id: public-subnet-id
|
9
|
+
private_subnet_id: private-subnet-id
|
10
|
+
elastic_ip_id: elastic-ip-id
|
11
|
+
vm_name: vm-name
|
@@ -0,0 +1 @@
|
|
1
|
+
iaas_type: unknown
|
@@ -0,0 +1,19 @@
|
|
1
|
+
iaas_type: vcloud
|
2
|
+
vapp_deployer:
|
3
|
+
creds:
|
4
|
+
url: VAPP_URL
|
5
|
+
organization: VAPP_ORGANIZATION
|
6
|
+
user: VAPP_USERNAME
|
7
|
+
password: VAPP_PASSWORD
|
8
|
+
vdc:
|
9
|
+
name: VDC_NAME
|
10
|
+
catalog: VDC_CATALOG
|
11
|
+
network: VDC_NETWORK
|
12
|
+
vapp:
|
13
|
+
name: VAPP_NAME
|
14
|
+
ip: VAPP_IP
|
15
|
+
public_ip:
|
16
|
+
gateway: VAPP_GATEWAY
|
17
|
+
netmask: VAPP_NETMASK
|
18
|
+
dns: VAPP_DNS
|
19
|
+
ntp: VAPP_NTP
|
@@ -0,0 +1,20 @@
|
|
1
|
+
iaas_type: vsphere
|
2
|
+
vm_deployer:
|
3
|
+
vcenter_creds:
|
4
|
+
ip: OVA_URL
|
5
|
+
username: OVA_ORGANIZATION
|
6
|
+
password: OVA_PASSWORD
|
7
|
+
vsphere:
|
8
|
+
host:
|
9
|
+
datacenter: VSPHERE_DATACENTER
|
10
|
+
cluster: VSPHERE_CLUSTER
|
11
|
+
network: VSPHERE_NETWORK
|
12
|
+
resource_pool: VSPHERE_RESOURCE_POOL
|
13
|
+
datastore: VSPHERE_DATASTORE
|
14
|
+
folder: VSPHERE_FOLDER
|
15
|
+
vm:
|
16
|
+
ip: OVA_IP
|
17
|
+
gateway: OVA_GATEWAY
|
18
|
+
netmask: OVA_NETMASK
|
19
|
+
dns: OVA_DNS
|
20
|
+
ntp_servers: OVA_NTP
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
SPEC_ROOT = File.expand_path(__dir__)
|
2
|
+
|
3
|
+
$LOAD_PATH << SPEC_ROOT
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.expect_with :rspec do |expectations|
|
7
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
8
|
+
end
|
9
|
+
|
10
|
+
config.mock_with :rspec do |mocks|
|
11
|
+
mocks.verify_partial_doubles = true
|
12
|
+
end
|
13
|
+
|
14
|
+
config.filter_run :focus
|
15
|
+
config.run_all_when_everything_filtered = true
|
16
|
+
config.disable_monkey_patching!
|
17
|
+
# config.warnings = true
|
18
|
+
|
19
|
+
if config.files_to_run.one?
|
20
|
+
config.default_formatter = 'doc'
|
21
|
+
end
|
22
|
+
|
23
|
+
config.profile_examples = 3
|
24
|
+
|
25
|
+
config.order = :random
|
26
|
+
|
27
|
+
Kernel.srand config.seed #this allows you to use `--seed` to deterministically reproduce failures
|
28
|
+
end
|