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.
@@ -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
@@ -0,0 +1,3 @@
1
+ module VmShepherd
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -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
@@ -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