vm_shepherd 0.0.1
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/.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
|