knife-clc 0.0.1 → 0.0.2.pre

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +62 -40
  3. data/lib/chef/knife/clc_ip_create.rb +1 -0
  4. data/lib/chef/knife/clc_server_create.rb +25 -528
  5. data/lib/clc/client.rb +1 -1
  6. data/lib/knife-clc/async/config_options.rb +18 -0
  7. data/lib/knife-clc/async.rb +11 -0
  8. data/lib/knife-clc/base/config_options.rb +26 -0
  9. data/lib/knife-clc/base.rb +56 -0
  10. data/lib/knife-clc/bootstrap/bootstrapper.rb +92 -0
  11. data/lib/knife-clc/bootstrap/config_options.rb +66 -0
  12. data/lib/knife-clc/bootstrap/connectivity_helper.rb +39 -0
  13. data/lib/knife-clc/bootstrap/methods/async_linux_package.rb +41 -0
  14. data/lib/knife-clc/bootstrap/methods/async_windows_package.rb +69 -0
  15. data/lib/knife-clc/bootstrap/methods/sync_linux_ssh.rb +67 -0
  16. data/lib/knife-clc/bootstrap/methods/sync_windows_winrm.rb +61 -0
  17. data/lib/knife-clc/bootstrap/subcommand_loader.rb +18 -0
  18. data/lib/knife-clc/bootstrap/validator.rb +149 -0
  19. data/lib/knife-clc/bootstrap.rb +20 -0
  20. data/lib/knife-clc/cloud_extensions/cloud_adapter.rb +35 -0
  21. data/lib/knife-clc/cloud_extensions.rb +11 -0
  22. data/lib/knife-clc/ip_assignment/config_options.rb +29 -0
  23. data/lib/knife-clc/ip_assignment/ip_assigner.rb +41 -0
  24. data/lib/knife-clc/ip_assignment/mapper.rb +20 -0
  25. data/lib/knife-clc/ip_assignment/validator.rb +74 -0
  26. data/lib/knife-clc/ip_assignment.rb +20 -0
  27. data/lib/knife-clc/server_launch/config_options.rb +145 -0
  28. data/lib/knife-clc/server_launch/mapper.rb +40 -0
  29. data/lib/knife-clc/server_launch/server_launcher.rb +40 -0
  30. data/lib/knife-clc/server_launch/validator.rb +100 -0
  31. data/lib/knife-clc/server_launch.rb +21 -0
  32. data/lib/knife-clc/version.rb +1 -1
  33. metadata +44 -4
@@ -0,0 +1,56 @@
1
+ require_relative 'base/config_options'
2
+
3
+ require 'hirb'
4
+ require 'clc'
5
+ require 'knife-clc/version'
6
+
7
+ module Knife
8
+ module Clc
9
+ module Base
10
+ def self.included(command_class)
11
+ ConfigOptions.attach(command_class)
12
+ end
13
+
14
+ def connection
15
+ @connection ||= ::Clc::Client.new(
16
+ :username => config[:clc_username],
17
+ :password => config[:clc_password],
18
+ :endpoint => config[:clc_endpoint],
19
+ :verbosity => config[:verbosity]
20
+ )
21
+ end
22
+
23
+ def context
24
+ @context ||= {}
25
+ end
26
+
27
+ def run
28
+ $stdout.sync = true
29
+
30
+ parse_and_validate_parameters
31
+
32
+ if errors.any?
33
+ show_errors
34
+ show_usage
35
+ exit 1
36
+ else
37
+ execute
38
+ end
39
+ end
40
+
41
+ def parse_and_validate_parameters
42
+ end
43
+
44
+ def execute
45
+ end
46
+
47
+ def errors
48
+ @errors ||= []
49
+ end
50
+
51
+ def show_errors
52
+ errors.each { |message| ui.error message }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,92 @@
1
+ require_relative 'validator'
2
+ require_relative 'connectivity_helper'
3
+ require_relative 'subcommand_loader'
4
+
5
+ require_relative 'methods/async_linux_package'
6
+ require_relative 'methods/async_windows_package'
7
+ require_relative 'methods/sync_linux_ssh'
8
+ require_relative 'methods/sync_windows_winrm'
9
+
10
+ module Knife
11
+ module Clc
12
+ module Bootstrap
13
+ class Bootstrapper
14
+ attr_reader :cloud_adapter, :config, :errors
15
+
16
+ def initialize(params)
17
+ @cloud_adapter = params.fetch(:cloud_adapter)
18
+ @config = params.fetch(:config)
19
+ @errors = params.fetch(:errors)
20
+ end
21
+
22
+ def sync_bootstrap(server)
23
+ sync_bootstrap_method.execute(server)
24
+ end
25
+
26
+ def async_bootstrap(launch_parameters)
27
+ async_bootstrap_method.execute(launch_parameters)
28
+ end
29
+
30
+ def prepare
31
+ validator.validate
32
+ end
33
+
34
+ private
35
+
36
+ def validator
37
+ @validator ||= Validator.new(
38
+ :connection => cloud_adapter.connection,
39
+ :config => config,
40
+ :errors => errors
41
+ )
42
+ end
43
+
44
+ def connectivity_helper
45
+ @connectivity_helper ||= ConnectivityHelper.new
46
+ end
47
+
48
+ def subcommand_loader
49
+ @subcommand_loader ||= SubcommandLoader.new
50
+ end
51
+
52
+ def sync_bootstrap_method
53
+ case config[:clc_bootstrap_platform]
54
+ when 'linux'
55
+ Methods::SyncLinuxSsh.new(
56
+ :cloud_adapter => cloud_adapter,
57
+ :config => config,
58
+ :connectivity_helper => connectivity_helper,
59
+ :subcommand_loader => subcommand_loader
60
+ )
61
+ when 'windows'
62
+ Methods::SyncWindowsWinrm.new(
63
+ :cloud_adapter => cloud_adapter,
64
+ :config => config,
65
+ :connectivity_helper => connectivity_helper,
66
+ :subcommand_loader => subcommand_loader
67
+ )
68
+ else
69
+ raise 'No suitable bootstrap method found'
70
+ end
71
+ end
72
+
73
+ def async_bootstrap_method
74
+ case config[:clc_bootstrap_platform]
75
+ when 'linux'
76
+ Methods::AsyncLinuxPackage.new(
77
+ :config => config,
78
+ :subcommand_loader => subcommand_loader
79
+ )
80
+ when 'windows'
81
+ Methods::AsyncWindowsPackage.new(
82
+ :config => config,
83
+ :subcommand_loader => subcommand_loader
84
+ )
85
+ else
86
+ raise 'No suitable bootstrap method found'
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,66 @@
1
+ require 'chef/knife/bootstrap'
2
+ require 'chef/knife/bootstrap_windows_winrm'
3
+
4
+ module Knife
5
+ module Clc
6
+ module Bootstrap
7
+ class ConfigOptions
8
+ def self.attach(command_class)
9
+ command_class.class_eval do
10
+ option :clc_bootstrap,
11
+ :long => '--bootstrap',
12
+ :description => '[Bootstrap] Bootstrap launched server',
13
+ :boolean => true,
14
+ :default => false,
15
+ :on => :head
16
+
17
+ option :clc_bootstrap_private,
18
+ :long => '--bootstrap-private',
19
+ :description => '[Bootstrap] Bootstrap from private network. Requires client or SSH gateway to have an access to private network of the server',
20
+ :boolean => true,
21
+ :default => false,
22
+ :on => :head
23
+
24
+ option :clc_bootstrap_platform,
25
+ :long => '--bootstrap-platform PLATFORM',
26
+ :description => '[Bootstrap] Sets bootstrapping server platform as windows or linux. Derived automatically by default',
27
+ :on => :head
28
+ end
29
+
30
+ attach_platform_specific_options(command_class)
31
+ end
32
+
33
+ # TODO AS: Once bootstrapper has separate platform modules - rework this
34
+ def self.attach_platform_specific_options(command_class)
35
+ linux_options = Chef::Knife::Bootstrap.options.dup
36
+ windows_options = Chef::Knife::BootstrapWindowsWinrm.options.dup
37
+
38
+ windows_specific_option_keys = windows_options.keys - linux_options.keys
39
+ linux_specific_option_keys = linux_options.keys - windows_options.keys
40
+
41
+ linux_specific_option_keys.each do |linux_key|
42
+ description = linux_options[linux_key][:description]
43
+ linux_options[linux_key][:description] = '(Linux Only) ' + description
44
+ end
45
+
46
+ windows_specific_option_keys.each do |windows_key|
47
+ description = windows_options[windows_key][:description]
48
+ windows_options[windows_key][:description] = '(Windows Only) ' + description
49
+ end
50
+
51
+ windows_options.each do |name, settings|
52
+ settings[:description] = '[Bootstrap] ' + settings[:description]
53
+ end
54
+
55
+ linux_options.each do |name, settings|
56
+ settings[:description] = '[Bootstrap] ' + settings[:description]
57
+ end
58
+
59
+ command_class.options
60
+ .merge!(linux_options)
61
+ .merge!(windows_options)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,39 @@
1
+ require 'net/ssh'
2
+ require 'socket'
3
+
4
+ class ConnectivityHelper
5
+ def test_tcp(params)
6
+ host = params.fetch(:host)
7
+ port = params.fetch(:port)
8
+
9
+ socket = TCPSocket.new(host, port)
10
+ if readable = IO.select([socket], [socket], nil, 5)
11
+ yield if block_given?
12
+ true
13
+ else
14
+ false
15
+ end
16
+ rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError, Errno::EPERM, Errno::ETIMEDOUT
17
+ false
18
+ ensure
19
+ socket && socket.close
20
+ end
21
+
22
+ def test_ssh_tunnel(params)
23
+ host = params.fetch(:host)
24
+ port = params.fetch(:port)
25
+ gateway = params.fetch(:gateway)
26
+
27
+ gateway_user, gateway_host = gateway.split('@')
28
+ gateway_host, gateway_port = gateway_host.split(':')
29
+
30
+ gateway = Net::SSH::Gateway.new(gateway_host, gateway_user, :port => gateway_port || 22)
31
+ status = false
32
+ gateway.open(host, port) do |local_tunnel_port|
33
+ status = test_tcp(:host => 'localhost', :port => local_tunnel_port)
34
+ end
35
+ status
36
+ rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError, Errno::EPERM, Errno::ETIMEDOUT
37
+ false
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ module Knife
2
+ module Clc
3
+ module Bootstrap
4
+ module Methods
5
+ class AsyncLinuxPackage
6
+ attr_reader :config, :subcommand_loader
7
+
8
+ def initialize(params)
9
+ @config = params.fetch(:config)
10
+ @subcommand_loader = params.fetch(:subcommand_loader)
11
+ end
12
+
13
+ def execute(launch_parameters)
14
+ launch_parameters['packages'] ||= []
15
+ launch_parameters['packages'].concat(packages_for_async_bootstrap)
16
+ end
17
+
18
+ private
19
+
20
+ def packages_for_async_bootstrap
21
+ [{
22
+ 'packageId' => 'a5d9d04369df4276a4f98f2ca7f7872b',
23
+ 'parameters' => {
24
+ 'Mode' => 'Ssh',
25
+ 'Script' => bootstrap_script
26
+ }
27
+ }]
28
+ end
29
+
30
+ def bootstrap_script
31
+ bootstrap_command.render_template
32
+ end
33
+
34
+ def bootstrap_command
35
+ subcommand_loader.load(:class => Chef::Knife::Bootstrap, :config => config)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,69 @@
1
+ require 'chef/knife/bootstrap_windows_winrm'
2
+
3
+ module Knife
4
+ module Clc
5
+ module Bootstrap
6
+ module Methods
7
+ class AsyncWindowsPackage
8
+ COMBINED_SCRIPT_PATH = 'C:/bootstrap.bat'
9
+ LINES_PER_PARTIAL_SCRIPT = 100
10
+
11
+ attr_reader :config, :subcommand_loader
12
+
13
+ def initialize(params)
14
+ @config = params.fetch(:config)
15
+ @subcommand_loader = params.fetch(:subcommand_loader)
16
+ end
17
+
18
+ def execute(launch_parameters)
19
+ launch_parameters['packages'] ||= []
20
+ launch_parameters['packages'].concat(packages_for_async_bootstrap)
21
+ end
22
+
23
+ private
24
+
25
+ def packages_for_async_bootstrap
26
+ split_script(bootstrap_script).map do |partial_script|
27
+ {
28
+ 'packageId' => 'a5d9d04369df4276a4f98f2ca7f7872b',
29
+ 'parameters' => {
30
+ 'Mode' => 'PowerShell',
31
+ 'Script' => partial_script
32
+ }
33
+ }
34
+ end
35
+ end
36
+
37
+ def split_script(script)
38
+ partial_scripts = script.lines.each_slice(LINES_PER_PARTIAL_SCRIPT).map do |lines|
39
+ appending_script(lines.join).tap { |script| ensure_windows_newlines(script) }
40
+ end
41
+
42
+ partial_scripts.push(COMBINED_SCRIPT_PATH)
43
+ end
44
+
45
+ def appending_script(script_to_append)
46
+ "$script = @'\n" +
47
+ script_to_append +
48
+ "'@\n" +
49
+ "$script | out-file #{COMBINED_SCRIPT_PATH} -Append -Encoding ASCII\n"
50
+ end
51
+
52
+ def ensure_windows_newlines(script)
53
+ script.gsub!("\r\n", "\n")
54
+ script.gsub!("\n", "\r\n")
55
+ end
56
+
57
+ def bootstrap_command
58
+ subcommand_loader.load(:class => Chef::Knife::BootstrapWindowsWinrm, :config => config)
59
+ end
60
+
61
+ def bootstrap_script
62
+ command = bootstrap_command
63
+ command.render_template(command.load_template(config[:bootstrap_template]))
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,67 @@
1
+ module Knife
2
+ module Clc
3
+ module Bootstrap
4
+ module Methods
5
+ class SyncLinuxSsh
6
+ attr_reader :cloud_adapter, :config, :connectivity_helper, :subcommand_loader
7
+
8
+ def initialize(params)
9
+ @cloud_adapter = params.fetch(:cloud_adapter)
10
+ @config = params.fetch(:config)
11
+ @connectivity_helper = params.fetch(:connectivity_helper)
12
+ @subcommand_loader = params.fetch(:subcommand_loader)
13
+ end
14
+
15
+ def execute(server)
16
+ cloud_adapter.ensure_server_powered_on(server)
17
+
18
+ fqdn = get_server_fqdn(server)
19
+ wait_for_sshd(fqdn)
20
+
21
+ command = subcommand_loader.load(:class => Chef::Knife::Bootstrap, :config => config)
22
+
23
+ username, password = config.values_at(:ssh_user, :ssh_password)
24
+ unless username && password
25
+ creds = cloud_adapter.get_server_credentials(server)
26
+ command.config.merge!(:ssh_user => creds['userName'], :ssh_password => creds['password'])
27
+ end
28
+
29
+ command.name_args = [fqdn]
30
+ command.config[:chef_node_name] ||= server['name']
31
+
32
+ command.run
33
+ end
34
+
35
+ private
36
+
37
+ def wait_for_sshd(hostname)
38
+ expire_at = Time.now + 30
39
+ port = config[:ssh_port] || 22
40
+
41
+ if gateway = config[:ssh_gateway]
42
+ until connectivity_helper.test_ssh_tunnel(:host => hostname, :port => port, :gateway => gateway)
43
+ raise 'Could not establish tunneled SSH connection with the server' if Time.now > expire_at
44
+ end
45
+ else
46
+ until connectivity_helper.test_tcp(:host => hostname, :port => port)
47
+ raise 'Could not establish SSH connection with the server' if Time.now > expire_at
48
+ end
49
+ end
50
+ end
51
+
52
+ def get_server_fqdn(server)
53
+ if indirect_bootstrap?
54
+ cloud_adapter.get_private_ip(server)
55
+ else
56
+ cloud_adapter.get_public_ip(server)
57
+ end
58
+ end
59
+
60
+ def indirect_bootstrap?
61
+ config[:clc_bootstrap_private] || config[:ssh_gateway]
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,61 @@
1
+ module Knife
2
+ module Clc
3
+ module Bootstrap
4
+ module Methods
5
+ class SyncWindowsWinrm
6
+ attr_reader :cloud_adapter, :config, :connectivity_helper, :subcommand_loader
7
+
8
+ def initialize(params)
9
+ @cloud_adapter = params.fetch(:cloud_adapter)
10
+ @config = params.fetch(:config)
11
+ @connectivity_helper = params.fetch(:connectivity_helper)
12
+ @subcommand_loader = params.fetch(:subcommand_loader)
13
+ end
14
+
15
+ def execute(server)
16
+ cloud_adapter.ensure_server_powered_on(server)
17
+
18
+ fqdn = get_server_fqdn(server)
19
+ wait_for_winrm(fqdn)
20
+
21
+ command = subcommand_loader.load(:class => Chef::Knife::BootstrapWindowsWinrm, :config => config)
22
+
23
+ username, password = config.values_at(:winrm_user, :winrm_password)
24
+ unless username && password
25
+ creds = cloud_adapter.get_server_credentials(server)
26
+ command.config.merge!(:winrm_user => creds['userName'], :winrm_password => creds['password'])
27
+ end
28
+
29
+ command.name_args = [fqdn]
30
+ command.config[:chef_node_name] ||= server['name']
31
+
32
+ command.run
33
+ end
34
+
35
+ private
36
+
37
+ def wait_for_winrm(hostname)
38
+ expire_at = Time.now + 3600
39
+ port = config[:winrm_port] || 5985
40
+
41
+ until connectivity_helper.test_tcp(:host => hostname, :port => port)
42
+ raise 'Could not establish WinRM connection with the server' if Time.now > expire_at
43
+ end
44
+ end
45
+
46
+ def get_server_fqdn(server)
47
+ if indirect_bootstrap?
48
+ cloud_adapter.get_private_ip(server)
49
+ else
50
+ cloud_adapter.get_public_ip(server)
51
+ end
52
+ end
53
+
54
+ def indirect_bootstrap?
55
+ config[:clc_bootstrap_private]
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,18 @@
1
+ module Knife
2
+ module Clc
3
+ module Bootstrap
4
+ class SubcommandLoader
5
+ def load(params)
6
+ klass = params.fetch(:class)
7
+ config = params.fetch(:config)
8
+
9
+ klass.load_deps
10
+ command = klass.new
11
+ command.config.merge!(config)
12
+ command.configure_chef
13
+ command
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,149 @@
1
+ require 'chef/node'
2
+
3
+ module Knife
4
+ module Clc
5
+ module Bootstrap
6
+ class Validator
7
+ attr_reader :connection, :config, :errors
8
+
9
+ def initialize(params)
10
+ @connection = params.fetch(:connection)
11
+ @config = params.fetch(:config)
12
+ @errors = params.fetch(:errors)
13
+ end
14
+
15
+ def validate
16
+ return unless config[:clc_bootstrap]
17
+
18
+ check_chef_server_connectivity
19
+
20
+ if config[:clc_bootstrap_platform]
21
+ validate_bootstrap_platform
22
+ else
23
+ check_server_platform
24
+ end
25
+
26
+ check_server_platform
27
+ if config[:clc_wait]
28
+ check_bootstrap_connectivity_params
29
+ else
30
+ check_bootstrap_node_connectivity_params
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def indirect_bootstrap?
37
+ config[:clc_bootstrap_private] || config[:ssh_gateway]
38
+ end
39
+
40
+ def check_chef_server_connectivity
41
+ Chef::Node.list
42
+ rescue Exception => e
43
+ errors << 'Could not connect to Chef Server: ' + e.message
44
+ end
45
+
46
+ def check_bootstrap_node_connectivity_params
47
+ unless Chef::Config[:validation_key]
48
+ errors << "Validatorless async bootstrap is not supported. Validation key #{Chef::Config[:validation_key]} not found"
49
+ end
50
+ end
51
+
52
+ def validate_bootstrap_platform
53
+ unless %w(linux windows).include? config[:clc_bootstrap_platform]
54
+ errors << "Unsupported bootstrap platform: #{config[:clc_bootstrap_platform]}"
55
+ end
56
+ end
57
+
58
+ def check_bootstrap_connectivity_params
59
+ return if indirect_bootstrap?
60
+
61
+ if public_ip_requested?
62
+ check_connectivity_errors
63
+ else
64
+ errors << 'Bootstrapping requires public IP access to the server. Ignore this check with --bootstrap-private'
65
+ end
66
+ end
67
+
68
+ def check_connectivity_errors
69
+ if config[:clc_bootstrap_platform] == 'windows'
70
+ errors << "Bootstrapping requires WinRM access to the server" unless winrm_access_requested?
71
+ else
72
+ errors << "Bootstrapping requires SSH access to the server" unless ssh_access_requested?
73
+ end
74
+ end
75
+
76
+ def check_server_platform
77
+ return unless config[:clc_group] && config[:clc_source_server]
78
+
79
+ if template = find_source_template
80
+ windows_platform = template['osType'] =~ /windows/
81
+ elsif server = find_source_server
82
+ windows_platform = server['os'] =~ /windows/
83
+ end
84
+
85
+ if windows_platform
86
+ config[:clc_bootstrap_platform] = 'windows'
87
+ else
88
+ config[:clc_bootstrap_platform] = 'linux'
89
+ end
90
+ rescue Clc::CloudExceptions::Error => e
91
+ errors << "Could not derive server bootstrap platform: #{e.message}. Please set it manually via --bootstrap-platform"
92
+ end
93
+
94
+ def find_source_template
95
+ group = connection.show_group(config[:clc_group])
96
+ datacenter_id = group['locationId']
97
+ connection.list_templates(datacenter_id).find do |template|
98
+ template['name'] == config[:clc_source_server]
99
+ end
100
+ end
101
+
102
+ def find_source_server
103
+ connection.show_server(config[:clc_source_server])
104
+ end
105
+
106
+ def public_ip_requested?
107
+ config[:clc_allowed_protocols] && config[:clc_allowed_protocols].any?
108
+ end
109
+
110
+
111
+ def winrm_access_requested?
112
+ winrm_port = requested_winrm_port
113
+
114
+ config[:clc_allowed_protocols].find do |permission|
115
+ protocol, from, to = permission.values_at('protocol', 'port', 'portTo')
116
+ next unless protocol == 'tcp'
117
+ next unless from
118
+
119
+ to ||= from
120
+
121
+ Range.new(from, to).include? winrm_port
122
+ end
123
+ end
124
+
125
+ def requested_winrm_port
126
+ (config[:winrm_port] && Integer(config[:winrm_port])) || 5985
127
+ end
128
+
129
+ def ssh_access_requested?
130
+ ssh_port = requested_ssh_port
131
+
132
+ config[:clc_allowed_protocols].find do |permission|
133
+ protocol, from, to = permission.values_at('protocol', 'port', 'portTo')
134
+ next unless protocol == 'tcp'
135
+ next unless from
136
+
137
+ to ||= from
138
+
139
+ Range.new(from, to).include? ssh_port
140
+ end
141
+ end
142
+
143
+ def requested_ssh_port
144
+ (config[:ssh_port] && Integer(config[:ssh_port])) || 22
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,20 @@
1
+ require_relative 'bootstrap/config_options'
2
+ require_relative 'bootstrap/bootstrapper'
3
+
4
+ module Knife
5
+ module Clc
6
+ module Bootstrap
7
+ def self.included(command_class)
8
+ ConfigOptions.attach(command_class)
9
+ end
10
+
11
+ def bootstrapper
12
+ @bootstrapper = Bootstrapper.new(
13
+ :cloud_adapter => cloud_adapter,
14
+ :config => config,
15
+ :errors => errors
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end