vcloud-launcher 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.
Files changed (38) hide show
  1. data/.gitignore +16 -0
  2. data/Gemfile +9 -0
  3. data/LICENSE.txt +20 -0
  4. data/README.md +127 -0
  5. data/Rakefile +47 -0
  6. data/bin/vcloud-launch +47 -0
  7. data/examples/.fog-example.fog +15 -0
  8. data/examples/vcloud-launch/basic_preamble.erb +8 -0
  9. data/examples/vcloud-launch/complete_vapp_config.yaml +58 -0
  10. data/examples/vcloud-launch/minimal_vapp_config.yaml +11 -0
  11. data/examples/vcloud-launch/multiple_vapps_simple.yaml +45 -0
  12. data/examples/vcloud-launch/yaml_anchors_example.yaml +87 -0
  13. data/features/step_definitions/vcloud-launch_steps.rb +3 -0
  14. data/features/support/env.rb +16 -0
  15. data/features/vcloud-launch.feature +16 -0
  16. data/jenkins.sh +11 -0
  17. data/jenkins_integration_tests.sh +7 -0
  18. data/lib/vcloud/launcher.rb +22 -0
  19. data/lib/vcloud/launcher/launch.rb +46 -0
  20. data/lib/vcloud/launcher/vapp_orchestrator.rb +44 -0
  21. data/lib/vcloud/launcher/version.rb +5 -0
  22. data/lib/vcloud/launcher/vm_orchestrator.rb +85 -0
  23. data/scripts/basic.erb +13 -0
  24. data/scripts/generate_fog_conf_file.sh +6 -0
  25. data/spec/erb_helper.rb +11 -0
  26. data/spec/integration/launcher/data/basic_preamble_test.erb +8 -0
  27. data/spec/integration/launcher/data/happy_path.yaml.erb +32 -0
  28. data/spec/integration/launcher/data/minimum_data_setup.yaml.erb +6 -0
  29. data/spec/integration/launcher/data/storage_profile.yaml.erb +24 -0
  30. data/spec/integration/launcher/storage_profile_integration_spec.rb +90 -0
  31. data/spec/integration/launcher/vcloud_launcher_spec.rb +157 -0
  32. data/spec/spec_helper.rb +3 -0
  33. data/spec/support/stub_fog_interface.rb +59 -0
  34. data/spec/vcloud/launcher/launch_spec.rb +48 -0
  35. data/spec/vcloud/launcher/vapp_orchestrator_spec.rb +57 -0
  36. data/spec/vcloud/launcher/vm_orchestrator_spec.rb +80 -0
  37. data/vcloud-launcher.gemspec +30 -0
  38. metadata +214 -0
@@ -0,0 +1,3 @@
1
+ Then(/^the banner should document that this app takes (\d+) argument$/) do |arg1|
2
+ pending # express the regexp above with the code you wish you had
3
+ end
@@ -0,0 +1,16 @@
1
+ require 'aruba/cucumber'
2
+ require 'methadone/cucumber'
3
+
4
+ ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
5
+ LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
6
+
7
+ Before do
8
+ # Using "announce" causes massive warnings on 1.9.2
9
+ @puts = true
10
+ @original_rubylib = ENV['RUBYLIB']
11
+ ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
12
+ end
13
+
14
+ After do
15
+ ENV['RUBYLIB'] = @original_rubylib
16
+ end
@@ -0,0 +1,16 @@
1
+ Feature: "vcloud-launch" works as a useful command-line tool
2
+ In order to use "vcloud-launch" from the CLI
3
+ I want to have it behave like a typical Unix tool
4
+ So I don't get surpised
5
+
6
+ Scenario: Common arguments work
7
+ When I get help for "vcloud-launch"
8
+ Then the exit status should be 0
9
+ And the banner should be present
10
+ And the banner should document that this app takes options
11
+ And the following options should be documented:
12
+ |--version|
13
+ |--dont-power-on|
14
+ |--continue-on-error|
15
+ And the banner should document that this app's arguments are:
16
+ |org_config_file|
data/jenkins.sh ADDED
@@ -0,0 +1,11 @@
1
+ #!/bin/bash -x
2
+ set -e
3
+ bundle install --path "${HOME}/bundles/${JOB_NAME}"
4
+ bundle exec rake
5
+
6
+ ./scripts/generate_fog_conf_file.sh
7
+ export FOG_RC=fog_integration_test.config
8
+ bundle exec rake integration:all
9
+ rm fog_integration_test.config
10
+
11
+ bundle exec rake publish_gem
@@ -0,0 +1,7 @@
1
+ #!/bin/bash -x
2
+ set -e
3
+ bundle install --path "${HOME}/bundles/${JOB_NAME}"
4
+ ./scripts/generate_fog_conf_file.sh
5
+ export FOG_RC=fog_integration_test.config
6
+ bundle exec rake integration:all
7
+ rm fog_integration_test.config
@@ -0,0 +1,22 @@
1
+ require 'vcloud/fog'
2
+ require 'vcloud/core'
3
+
4
+ require 'vcloud/launcher/launch'
5
+ require 'vcloud/launcher/vm_orchestrator'
6
+ require 'vcloud/launcher/vapp_orchestrator'
7
+
8
+ require 'vcloud/launcher/version'
9
+
10
+ module Vcloud
11
+ module Launcher
12
+
13
+ def self.logger
14
+ @logger ||= Logger.new(STDOUT)
15
+ end
16
+
17
+ def self.clone_object object
18
+ Marshal.load(Marshal.dump(object))
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,46 @@
1
+ module Vcloud
2
+ module Launcher
3
+ class Launch
4
+
5
+ def initialize
6
+ @config_loader = Vcloud::Core::ConfigLoader.new
7
+ end
8
+
9
+ def run(config_file = nil, cli_options = {})
10
+ config = @config_loader.load_config(config_file, config_schema)
11
+ config[:vapps].each do |vapp_config|
12
+ Vcloud::Launcher.logger.info("\n")
13
+ Vcloud::Launcher.logger.info("Provisioning vApp #{vapp_config[:name]}.")
14
+ begin
15
+ vapp = ::Vcloud::Launcher::VappOrchestrator.provision(vapp_config)
16
+ #methadone sends option starting with 'no' as false.
17
+ vapp.power_on unless cli_options["dont-power-on"]
18
+ Vcloud::Launcher.logger.info("Done! Provisioned vApp #{vapp_config[:name]} successfully.")
19
+ Vcloud::Launcher.logger.info("=" * 70)
20
+ rescue RuntimeError => e
21
+ Vcloud::Launcher.logger.error("Failure : Could not provision vApp: #{e.message}")
22
+ Vcloud::Launcher.logger.info("=" * 70)
23
+ break unless cli_options["continue-on-error"]
24
+ end
25
+
26
+ end
27
+ end
28
+
29
+ def config_schema
30
+ {
31
+ type: 'hash',
32
+ allowed_empty: false,
33
+ permit_unknown_parameters: true,
34
+ internals: {
35
+ vapps: {
36
+ type: 'array',
37
+ required: false,
38
+ allowed_empty: true,
39
+ each_element_is: ::Vcloud::Launcher::VappOrchestrator.provision_schema
40
+ },
41
+ }
42
+ }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,44 @@
1
+ module Vcloud
2
+ module Launcher
3
+ class VappOrchestrator
4
+
5
+ def self.provision(vapp_config)
6
+ name, vdc_name = vapp_config[:name], vapp_config[:vdc_name]
7
+
8
+ if vapp = Vcloud::Core::Vapp.get_by_name_and_vdc_name(name, vdc_name)
9
+ Vcloud::Launcher.logger.info("Found existing vApp #{name} in vDC '#{vdc_name}'. Skipping.")
10
+ else
11
+ template = Vcloud::Core::VappTemplate.get(vapp_config[:catalog], vapp_config[:catalog_item])
12
+ template_id = template.id
13
+
14
+ network_names = extract_vm_networks(vapp_config)
15
+ vapp = Vcloud::Core::Vapp.instantiate(name, network_names, template_id, vdc_name)
16
+ Vcloud::Launcher::VmOrchestrator.new(vapp.fog_vms.first, vapp).customize(vapp_config[:vm]) if vapp_config[:vm]
17
+ end
18
+ vapp
19
+ end
20
+
21
+ def self.provision_schema
22
+ {
23
+ type: 'hash',
24
+ required: true,
25
+ allowed_empty: false,
26
+ internals: {
27
+ name: { type: 'string', required: true, allowed_empty: false },
28
+ vdc_name: { type: 'string', required: true, allowed_empty: false },
29
+ catalog: { type: 'string', required: true, allowed_empty: false },
30
+ catalog_item: { type: 'string', required: true, allowed_empty: false },
31
+ vm: Vcloud::Launcher::VmOrchestrator.customize_schema,
32
+ }
33
+ }
34
+ end
35
+
36
+ def self.extract_vm_networks(config)
37
+ if (config[:vm] && config[:vm][:network_connections])
38
+ config[:vm][:network_connections].collect { |h| h[:name] }
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ module Vcloud
2
+ module Launcher
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
@@ -0,0 +1,85 @@
1
+ module Vcloud
2
+ module Launcher
3
+ class VmOrchestrator
4
+ def initialize fog_vm, vapp
5
+ vm_id = fog_vm[:href].split('/').last
6
+ @vm = Core::Vm.new(vm_id, vapp)
7
+ end
8
+
9
+ def customize(vm_config)
10
+ @vm.update_name(@vm.vapp_name)
11
+ @vm.configure_network_interfaces vm_config[:network_connections]
12
+ @vm.update_storage_profile(vm_config[:storage_profile]) if vm_config[:storage_profile]
13
+ if hardware_config = vm_config[:hardware_config]
14
+ @vm.update_cpu_count(hardware_config[:cpu])
15
+ @vm.update_memory_size_in_mb(hardware_config[:memory])
16
+ end
17
+ @vm.add_extra_disks(vm_config[:extra_disks])
18
+ @vm.update_metadata(vm_config[:metadata])
19
+ @vm.configure_guest_customization_section(
20
+ @vm.vapp_name,
21
+ vm_config[:bootstrap],
22
+ vm_config[:extra_disks]
23
+ )
24
+ end
25
+
26
+ def self.customize_schema
27
+ {
28
+ type: 'hash',
29
+ required: false,
30
+ allowed_empty: false,
31
+ internals: {
32
+ network_connections: {
33
+ type: 'array',
34
+ required: false,
35
+ each_element_is: {
36
+ type: 'hash',
37
+ internals: {
38
+ name: { type: 'string', required: true },
39
+ ip_address: { type: 'ip_address', required: false },
40
+ },
41
+ },
42
+ },
43
+ storage_profile: { type: 'string', required: false },
44
+ hardware_config: {
45
+ type: 'hash',
46
+ required: false,
47
+ internals: {
48
+ cpu: { type: 'string_or_number', required: false },
49
+ memory: { type: 'string_or_number', required: false },
50
+ },
51
+ },
52
+ extra_disks: {
53
+ type: 'array',
54
+ required: false,
55
+ allowed_empty: false,
56
+ each_element_is: {
57
+ type: 'hash',
58
+ internals: {
59
+ name: { type: 'string', required: false },
60
+ size: { type: 'string_or_number', required: false },
61
+ },
62
+ },
63
+ },
64
+ bootstrap: {
65
+ type: 'hash',
66
+ required: false,
67
+ allowed_empty: false,
68
+ internals: {
69
+ script_path: { type: 'string', required: false },
70
+ script_post_processor: { type: 'string', required: false },
71
+ vars: { type: 'hash', required: false, allowed_empty: true },
72
+ },
73
+ },
74
+ metadata: {
75
+ type: 'hash',
76
+ required: false,
77
+ allowed_empty: true,
78
+ },
79
+ },
80
+ }
81
+ end
82
+
83
+ end
84
+ end
85
+ end
data/scripts/basic.erb ADDED
@@ -0,0 +1,13 @@
1
+ #!/bin/sh
2
+
3
+ (
4
+ echo "============================================================"
5
+ echo "in ${1}:"
6
+ echo "vapp_name: <%= vapp_name -%>"
7
+ echo "role: <%= vars[:role] -%>"
8
+ echo "environment: <%= vars[:environment] -%>"
9
+ echo
10
+ env
11
+ ) >> /PREAMBLE_OUT
12
+
13
+
@@ -0,0 +1,6 @@
1
+ cat <<EOF >fog_integration_test.config
2
+ default:
3
+ vcloud_director_username: '$API_USERNAME'
4
+ vcloud_director_password: '$API_PASSWORD'
5
+ vcloud_director_host: '$API_HOST'
6
+ EOF
@@ -0,0 +1,11 @@
1
+ class ErbHelper
2
+ def self.convert_erb_template_to_yaml test_namespace, input_erb_config
3
+ input_erb_config = input_erb_config
4
+ e = ERB.new(File.open(input_erb_config).read)
5
+ output_yaml_config = File.join(File.dirname(input_erb_config), "output_#{Time.now.strftime('%s')}.yaml")
6
+ File.open(output_yaml_config, 'w') { |f|
7
+ f.write e.result(OpenStruct.new(test_namespace).instance_eval { binding })
8
+ }
9
+ output_yaml_config
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+
3
+ # Default preamble
4
+
5
+ (
6
+ echo "in $*"
7
+ echo "message: <%= vars[:message] -%>"
8
+ ) >> /PREAMBLE_OUTPUT
@@ -0,0 +1,32 @@
1
+ ---
2
+ vapps:
3
+ - name: <%= vapp_name %>
4
+ vdc_name: <%= vdc_name %>
5
+ catalog: <%= catalog %>
6
+ catalog_item: <%= vapp_template %>
7
+ vm:
8
+ hardware_config:
9
+ memory: 8192
10
+ cpu: 4
11
+ metadata:
12
+ is_integer: -999
13
+ is_string: Hello World
14
+ is_datetime: <%= date_metadata %>
15
+ is_true: true
16
+ is_false: false
17
+ integration_test_vm: true
18
+ extra_disks:
19
+ - size: '1024'
20
+ name: Hard disk 2
21
+ - size: '2048'
22
+ name: Hard disk 3
23
+ network_connections:
24
+ - name: <%= network1 %>
25
+ ip_address: <%= network1_ip %>
26
+ - name: <%= network2 %>
27
+ ip_address: <%= network2_ip %>
28
+ bootstrap:
29
+ script_path: <%= bootstrap_script %>
30
+ vars:
31
+ message: hello world
32
+ storage_profile: <%= storage_profile %>
@@ -0,0 +1,6 @@
1
+ ---
2
+ vapps:
3
+ - name: <%= vapp_name %>
4
+ vdc_name: <%= vdc_name %>
5
+ catalog: <%= catalog %>
6
+ catalog_item: <%= vapp_template %>
@@ -0,0 +1,24 @@
1
+ ---
2
+ vapps:
3
+ - name: <%= vapp_name_1 %>
4
+ vdc_name: <%= vdc_name_1 %>
5
+ catalog: <%= catalog %>
6
+ catalog_item: <%= vapp_template %>
7
+ vm:
8
+ storage_profile: <%= storage_profile %>
9
+ - name: <%= vapp_name_2 %>
10
+ vdc_name: <%= vdc_name_2 %>
11
+ catalog: <%= catalog %>
12
+ catalog_item: <%= vapp_template %>
13
+ vm:
14
+ storage_profile: <%= storage_profile %>
15
+ - name: <%= vapp_name_3 %>
16
+ vdc_name: <%= vdc_name_1 %>
17
+ catalog: <%= catalog %>
18
+ catalog_item: <%= vapp_template %>
19
+ - name: <%= vapp_name_4 %>
20
+ vdc_name: <%= vdc_name_1 %>
21
+ catalog: <%= catalog %>
22
+ catalog_item: <%= vapp_template %>
23
+ vm:
24
+ storage_profile: <%= nonsense_storage_profile %>
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ describe Vcloud::Launcher::Launch do
4
+ context "storage profile", :take_too_long => true do
5
+ before(:all) do
6
+ @test_data = define_test_data
7
+ @config_yaml = ErbHelper.convert_erb_template_to_yaml(@test_data, File.join(File.dirname(__FILE__), 'data/storage_profile.yaml.erb'))
8
+ @fog_interface = Vcloud::Fog::ServiceInterface.new
9
+ Vcloud::Launcher::Launch.new.run(@config_yaml, {'dont-power-on' => true})
10
+
11
+ @vapp_query_result_1 = @fog_interface.get_vapp_by_name_and_vdc_name(@test_data[:vapp_name_1], @test_data[:vdc_name_1])
12
+ @vapp_id_1 = @vapp_query_result_1[:href].split('/').last
13
+ @vapp_1 = @fog_interface.get_vapp @vapp_id_1
14
+ @vm_1 = @vapp_1[:Children][:Vm].first
15
+
16
+ @vapp_query_result_2 = @fog_interface.get_vapp_by_name_and_vdc_name(@test_data[:vapp_name_2], @test_data[:vdc_name_2])
17
+ @vapp_id_2 = @vapp_query_result_2[:href].split('/').last
18
+ @vapp_2 = @fog_interface.get_vapp @vapp_id_2
19
+ @vm_2 = @vapp_2[:Children][:Vm].first
20
+
21
+ @vapp_query_result_3 = @fog_interface.get_vapp_by_name_and_vdc_name(@test_data[:vapp_name_3], @test_data[:vdc_name_1])
22
+ @vapp_id_3 = @vapp_query_result_3[:href].split('/').last
23
+ @vapp_3 = @fog_interface.get_vapp @vapp_id_3
24
+ @vm_3 = @vapp_3[:Children][:Vm].first
25
+
26
+ @vapp_query_result_4 = @fog_interface.get_vapp_by_name_and_vdc_name(@test_data[:vapp_name_4], @test_data[:vdc_name_1])
27
+ @vapp_id_4 = @vapp_query_result_4[:href].split('/').last
28
+ @vapp_4 = @fog_interface.get_vapp @vapp_id_4
29
+ @vm_4 = @vapp_4[:Children][:Vm].first
30
+ end
31
+
32
+ it "vdc 1 should have a storage profile without the href being specified" do
33
+ @vm_1[:StorageProfile][:name].should == @test_data[:storage_profile]
34
+ end
35
+
36
+ it "vdc 1's storage profile should have the expected href" do
37
+ @vm_1[:StorageProfile][:href].should == @test_data[:vdc_1_sp_href]
38
+ end
39
+
40
+ it "vdc 2 should have the same named storage profile as vdc 1" do
41
+ @vm_2[:StorageProfile][:name].should == @test_data[:storage_profile]
42
+ end
43
+
44
+ it "the storage profile in vdc 2 should have a different href to the storage profile in vdc 1" do
45
+ @vm_2[:StorageProfile][:href].should == @test_data[:vdc_2_sp_href]
46
+ end
47
+
48
+ it "when a storage profile is not specified, vm uses the default and continues" do
49
+ @vm_3[:StorageProfile][:name].should == @test_data[:default_storage_profile_name]
50
+ @vm_3[:StorageProfile][:href].should == @test_data[:default_storage_profile_href]
51
+ end
52
+
53
+ it "when a storage profile specified does not exist, vm uses the default" do
54
+ @vm_4[:StorageProfile][:name].should == @test_data[:default_storage_profile_name]
55
+ @vm_4[:StorageProfile][:href].should == @test_data[:default_storage_profile_href]
56
+ end
57
+
58
+ after(:all) do
59
+ unless ENV['VCLOUD_TOOLS_RSPEC_NO_DELETE_VAPP']
60
+ File.delete @config_yaml
61
+ @fog_interface.delete_vapp(@vapp_id_1).should == true
62
+ @fog_interface.delete_vapp(@vapp_id_2).should == true
63
+ @fog_interface.delete_vapp(@vapp_id_3).should == true
64
+ @fog_interface.delete_vapp(@vapp_id_4).should == true
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ end
71
+
72
+ def define_test_data
73
+ {
74
+ vapp_name_1: "vdc-1-sp-#{Time.now.strftime('%s')}",
75
+ vapp_name_2: "vdc-2-sp-#{Time.now.strftime('%s')}",
76
+ vapp_name_3: "vdc-3-sp-#{Time.now.strftime('%s')}",
77
+ vapp_name_4: "vdc-4-sp-#{Time.now.strftime('%s')}",
78
+ vdc_name_1: ENV['VDC_NAME_1'],
79
+ vdc_name_2: ENV['VDC_NAME_2'],
80
+ catalog: ENV['VCLOUD_CATALOG_NAME'],
81
+ vapp_template: ENV['VCLOUD_TEMPLATE_NAME'],
82
+ storage_profile: ENV['VCLOUD_STORAGE_PROFILE_NAME'],
83
+ vdc_1_sp_href: ENV['VDC_1_STORAGE_PROFILE_HREF'],
84
+ vdc_2_sp_href: ENV['VDC_2_STORAGE_PROFILE_HREF'],
85
+ default_storage_profile_name: ENV['DEFAULT_STORAGE_PROFILE_NAME'],
86
+ default_storage_profile_href: ENV['DEFAULT_STORAGE_PROFILE_HREF'],
87
+ nonsense_storage_profile: "nonsense-storage-profile-name",
88
+ bootstrap_script: File.join(File.dirname(__FILE__), "data/basic_preamble_test.erb"),
89
+ }
90
+ end