vagrant-skytap 0.1.1a

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.hgignore +22 -0
  4. data/.project +11 -0
  5. data/.rspec +1 -0
  6. data/CHANGELOG.md +85 -0
  7. data/Gemfile +29 -0
  8. data/LICENSE +8 -0
  9. data/README.md +292 -0
  10. data/Rakefile +31 -0
  11. data/Vagrantfile.orig +34 -0
  12. data/bar/checksums.yaml +7 -0
  13. data/bar/data.tar +0 -0
  14. data/bar/metadata +242 -0
  15. data/dummy.box +0 -0
  16. data/example_box/README.md +13 -0
  17. data/example_box/metadata.json +3 -0
  18. data/lib/vagrant-skytap/action/add_vm_to_environment.rb +35 -0
  19. data/lib/vagrant-skytap/action/create_environment.rb +44 -0
  20. data/lib/vagrant-skytap/action/delete_environment.rb +26 -0
  21. data/lib/vagrant-skytap/action/delete_vm.rb +27 -0
  22. data/lib/vagrant-skytap/action/existence_check.rb +35 -0
  23. data/lib/vagrant-skytap/action/fetch_environment.rb +32 -0
  24. data/lib/vagrant-skytap/action/initialize_api_client.rb +28 -0
  25. data/lib/vagrant-skytap/action/is_running.rb +19 -0
  26. data/lib/vagrant-skytap/action/is_stopped.rb +19 -0
  27. data/lib/vagrant-skytap/action/is_suspended.rb +19 -0
  28. data/lib/vagrant-skytap/action/message_already_created.rb +16 -0
  29. data/lib/vagrant-skytap/action/message_already_running.rb +16 -0
  30. data/lib/vagrant-skytap/action/message_environment_url.rb +16 -0
  31. data/lib/vagrant-skytap/action/message_not_created.rb +16 -0
  32. data/lib/vagrant-skytap/action/message_will_not_destroy.rb +16 -0
  33. data/lib/vagrant-skytap/action/mixin_machine_index.rb +22 -0
  34. data/lib/vagrant-skytap/action/prepare_nfs_settings.rb +46 -0
  35. data/lib/vagrant-skytap/action/prepare_nfs_valid_ids.rb +28 -0
  36. data/lib/vagrant-skytap/action/read_ssh_info.rb +23 -0
  37. data/lib/vagrant-skytap/action/read_state.rb +42 -0
  38. data/lib/vagrant-skytap/action/run_environment.rb +53 -0
  39. data/lib/vagrant-skytap/action/run_vm.rb +51 -0
  40. data/lib/vagrant-skytap/action/set_hostname.rb +31 -0
  41. data/lib/vagrant-skytap/action/set_up_vm.rb +21 -0
  42. data/lib/vagrant-skytap/action/stop_environment.rb +43 -0
  43. data/lib/vagrant-skytap/action/stop_vm.rb +43 -0
  44. data/lib/vagrant-skytap/action/store_extra_data.rb +35 -0
  45. data/lib/vagrant-skytap/action/suspend_environment.rb +32 -0
  46. data/lib/vagrant-skytap/action/suspend_vm.rb +32 -0
  47. data/lib/vagrant-skytap/action/timed_provision.rb +21 -0
  48. data/lib/vagrant-skytap/action/update_hardware.rb +37 -0
  49. data/lib/vagrant-skytap/action.rb +272 -0
  50. data/lib/vagrant-skytap/api/busyable.rb +37 -0
  51. data/lib/vagrant-skytap/api/client.rb +127 -0
  52. data/lib/vagrant-skytap/api/credentials.rb +41 -0
  53. data/lib/vagrant-skytap/api/environment.rb +99 -0
  54. data/lib/vagrant-skytap/api/interface.rb +123 -0
  55. data/lib/vagrant-skytap/api/network.rb +40 -0
  56. data/lib/vagrant-skytap/api/public_ip.rb +103 -0
  57. data/lib/vagrant-skytap/api/published_service.rb +90 -0
  58. data/lib/vagrant-skytap/api/resource.rb +44 -0
  59. data/lib/vagrant-skytap/api/runstate_operations.rb +63 -0
  60. data/lib/vagrant-skytap/api/specified_attributes.rb +27 -0
  61. data/lib/vagrant-skytap/api/vm.rb +88 -0
  62. data/lib/vagrant-skytap/api/vpn.rb +146 -0
  63. data/lib/vagrant-skytap/api/vpn_attachment.rb +57 -0
  64. data/lib/vagrant-skytap/config.rb +106 -0
  65. data/lib/vagrant-skytap/core_ext/object/blank.rb +82 -0
  66. data/lib/vagrant-skytap/core_ext/object/tap.rb +8 -0
  67. data/lib/vagrant-skytap/core_ext/try.rb +42 -0
  68. data/lib/vagrant-skytap/environment_properties.rb +11 -0
  69. data/lib/vagrant-skytap/errors.rb +59 -0
  70. data/lib/vagrant-skytap/plugin.rb +73 -0
  71. data/lib/vagrant-skytap/properties.rb +42 -0
  72. data/lib/vagrant-skytap/provider.rb +50 -0
  73. data/lib/vagrant-skytap/setup_helper.rb +193 -0
  74. data/lib/vagrant-skytap/util/ip_address.rb +69 -0
  75. data/lib/vagrant-skytap/util/subnet.rb +97 -0
  76. data/lib/vagrant-skytap/util/timer.rb +17 -0
  77. data/lib/vagrant-skytap/version.rb +5 -0
  78. data/lib/vagrant-skytap/version.rb.orig +5 -0
  79. data/lib/vagrant-skytap/vm_properties.rb +22 -0
  80. data/lib/vagrant-skytap.rb +23 -0
  81. data/locales/en.yml +127 -0
  82. data/skytap-dummy.box +0 -0
  83. data/spec/acceptance/base.rb +2 -0
  84. data/spec/acceptance/provider/halt_spec.rb +3 -0
  85. data/spec/acceptance/shared/context_skytap.rb +3 -0
  86. data/spec/spec_helper.rb +1 -0
  87. data/spec/support/isolated_environment.rb +45 -0
  88. data/spec/unit/base.rb +57 -0
  89. data/spec/unit/config_spec.rb +73 -0
  90. data/spec/unit/environment_spec.rb +144 -0
  91. data/spec/unit/skeletons/empty_environment.json +19 -0
  92. data/spec/unit/skeletons/network1.json +36 -0
  93. data/spec/unit/skeletons/vm1.json +85 -0
  94. data/spec/unit/support/dummy_communicator.rb +83 -0
  95. data/spec/unit/support/dummy_provider.rb +41 -0
  96. data/spec/unit/support/isolated_environment.rb +217 -0
  97. data/spec/unit/support/shared/action_synced_folders_context.rb +15 -0
  98. data/spec/unit/support/shared/base_context.rb +116 -0
  99. data/spec/unit/support/shared/capability_helpers_context.rb +29 -0
  100. data/spec/unit/support/shared/plugin_command_context.rb +12 -0
  101. data/spec/unit/support/shared/skytap_context.rb +3 -0
  102. data/spec/unit/vm_spec.rb +118 -0
  103. data/tasks/acceptance.rake +22 -0
  104. data/tasks/bundler.rake +3 -0
  105. data/tasks/test.rake +14 -0
  106. data/vagrant-skytap.gemspec +62 -0
  107. data/vagrant-spec.config.rb +10 -0
  108. metadata +247 -0
@@ -0,0 +1,106 @@
1
+ require "vagrant"
2
+
3
+ module VagrantPlugins
4
+ module Skytap
5
+ class Config < Vagrant.plugin("2", :config)
6
+ # The user id for accessing Skytap.
7
+ #
8
+ # @return [String]
9
+ attr_accessor :username
10
+
11
+ # The secret API token for accessing Skytap.
12
+ #
13
+ # @return [String]
14
+ attr_accessor :api_token
15
+
16
+ # The base URL for Skytap API calls.
17
+ #
18
+ # @return [String]
19
+ attr_accessor :base_url
20
+
21
+ # The url of the source VM to use.
22
+ #
23
+ # @return [String]
24
+ attr_accessor :vm_url
25
+
26
+ # The timeout to wait for a VM to become ready.
27
+ #
28
+ # @return [Fixnum]
29
+ attr_accessor :instance_ready_timeout
30
+
31
+ # The total number of virtual CPUs for this VM.
32
+ #
33
+ # @return [Integer]
34
+ attr_accessor :cpus
35
+
36
+ # The number of virtual CPUs per socket.
37
+ #
38
+ # @return [Integer]
39
+ attr_accessor :cpuspersocket
40
+
41
+ # The RAM to use for this machine (measured in MB).
42
+ #
43
+ # @return [Integer]
44
+ attr_accessor :ram
45
+
46
+ # The VMware guest OS setting for this machine.
47
+ #
48
+ # @return [String]
49
+ attr_accessor :guestos
50
+
51
+ def initialize(region_specific=false)
52
+ @username = UNSET_VALUE
53
+ @api_token = UNSET_VALUE
54
+ @base_url = UNSET_VALUE
55
+ @vm_url = UNSET_VALUE
56
+ @instance_ready_timeout = UNSET_VALUE
57
+ @region = UNSET_VALUE
58
+
59
+ @cpus = UNSET_VALUE
60
+ @cpuspersocket = UNSET_VALUE
61
+ @ram = UNSET_VALUE
62
+ @guestos = UNSET_VALUE
63
+ end
64
+
65
+ #-------------------------------------------------------------------
66
+ # Internal methods.
67
+ #-------------------------------------------------------------------
68
+
69
+ def finalize!
70
+ # Try to get access keys from standard Skytap environment variables; they
71
+ # will default to nil if the environment variables are not present.
72
+ @username = ENV['VAGRANT_SKYTAP_USERNAME'] if @username == UNSET_VALUE
73
+ @api_token = ENV['VAGRANT_SKYTAP_API_TOKEN'] if @api_token == UNSET_VALUE
74
+
75
+ # Base URL for API calls.
76
+ @base_url = "https://cloud.skytap.com/" if @base_url == UNSET_VALUE
77
+
78
+ # Source VM url must be set.
79
+ @vm_url = nil if @vm_url == UNSET_VALUE
80
+
81
+ # Set the default timeout for waiting for an instance to be ready
82
+ @instance_ready_timeout = 120 if @instance_ready_timeout == UNSET_VALUE
83
+
84
+ # Hardware settings default to nil (will be obtained
85
+ # from the source VM)
86
+ @cpus = nil if @cpus == UNSET_VALUE
87
+ @cpuspersocket = nil if @cpuspersocket == UNSET_VALUE
88
+ @ram = nil if @ram == UNSET_VALUE
89
+ @guestos = nil if @guestos == UNSET_VALUE
90
+
91
+ # Mark that we finalized
92
+ @__finalized = true
93
+ end
94
+
95
+ def validate(machine)
96
+ errors = _detected_errors
97
+
98
+ errors << I18n.t('vagrant_skytap.config.username_required') unless username
99
+ errors << I18n.t('vagrant_skytap.config.api_token_required') unless api_token
100
+ errors << I18n.t('vagrant_skytap.config.vm_url_required') unless vm_url
101
+
102
+ { "Skytap Provider" => errors }
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,82 @@
1
+ # Copied from Rails ActiveSupport 2.3.18:
2
+ # activesupport-2.3.18/lib/active_support/core_ext/object/blank.rb
3
+ #
4
+ # == License
5
+ # Active Support is released under the MIT license.
6
+
7
+ class Object
8
+ # An object is blank if it's false, empty, or a whitespace string.
9
+ # For example, "", " ", +nil+, [], and {} are blank.
10
+ #
11
+ # This simplifies:
12
+ #
13
+ # if !address.nil? && !address.empty?
14
+ #
15
+ # ...to:
16
+ #
17
+ # if !address.blank?
18
+ def blank?
19
+ respond_to?(:empty?) ? empty? : !self
20
+ end
21
+
22
+ # An object is present if it's not blank.
23
+ def present?
24
+ !blank?
25
+ end
26
+
27
+ # Returns object if it's #present? otherwise returns nil.
28
+ # object.presence is equivalent to object.present? ? object : nil.
29
+ #
30
+ # This is handy for any representation of objects where blank is the same
31
+ # as not present at all. For example, this simplifies a common check for
32
+ # HTTP POST/query parameters:
33
+ #
34
+ # state = params[:state] if params[:state].present?
35
+ # country = params[:country] if params[:country].present?
36
+ # region = state || country || 'US'
37
+ #
38
+ # ...becomes:
39
+ #
40
+ # region = params[:state].presence || params[:country].presence || 'US'
41
+ def presence
42
+ self if present?
43
+ end
44
+ end
45
+
46
+ class NilClass #:nodoc:
47
+ def blank?
48
+ true
49
+ end
50
+ end
51
+
52
+ class FalseClass #:nodoc:
53
+ def blank?
54
+ true
55
+ end
56
+ end
57
+
58
+ class TrueClass #:nodoc:
59
+ def blank?
60
+ false
61
+ end
62
+ end
63
+
64
+ class Array #:nodoc:
65
+ alias_method :blank?, :empty?
66
+ end
67
+
68
+ class Hash #:nodoc:
69
+ alias_method :blank?, :empty?
70
+ end
71
+
72
+ class String #:nodoc:
73
+ def blank?
74
+ self !~ /\S/
75
+ end
76
+ end
77
+
78
+ class Numeric #:nodoc:
79
+ def blank?
80
+ false
81
+ end
82
+ end
@@ -0,0 +1,8 @@
1
+ unless Object.respond_to?(:tap)
2
+ class Object
3
+ def tap
4
+ yield self
5
+ self
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,42 @@
1
+ # Copied from Rails ActiveSupport 2.3.18:
2
+ # activesupport-2.3.18/lib/active_support/core_ext/try.rb
3
+ #
4
+ # == License
5
+ # Active Support is released under the MIT license.
6
+ #
7
+ class Object
8
+ # Invokes the method identified by the symbol +method+, passing it any arguments
9
+ # and/or the block specified, just like the regular Ruby <tt>Object#send</tt> does.
10
+ #
11
+ # *Unlike* that method however, a +NoMethodError+ exception will *not* be raised
12
+ # and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass.
13
+ #
14
+ # ==== Examples
15
+ #
16
+ # Without try
17
+ # @person && @person.name
18
+ # or
19
+ # @person ? @person.name : nil
20
+ #
21
+ # With try
22
+ # @person.try(:name)
23
+ #
24
+ # +try+ also accepts arguments and/or a block, for the method it is trying
25
+ # Person.try(:find, 1)
26
+ # @people.try(:collect) {|p| p.name}
27
+ #--
28
+ # This method definition below is for rdoc purposes only. The alias_method call
29
+ # below overrides it as an optimization since +try+ behaves like +Object#send+,
30
+ # unless called on +NilClass+.
31
+ def try(method, *args, &block)
32
+ send(method, *args, &block)
33
+ end
34
+ remove_method :try
35
+ alias_method :try, :__send__
36
+ end
37
+
38
+ class NilClass
39
+ def try(*args)
40
+ nil
41
+ end
42
+ end
@@ -0,0 +1,11 @@
1
+ require 'vagrant-skytap/properties'
2
+
3
+ module VagrantPlugins
4
+ module Skytap
5
+ class EnvironmentProperties < Properties
6
+ def self.filename
7
+ 'environment'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,59 @@
1
+ require "vagrant"
2
+
3
+ module VagrantPlugins
4
+ module Skytap
5
+ module Errors
6
+ class VagrantSkytapError < Vagrant::Errors::VagrantError
7
+ error_namespace("vagrant_skytap.errors")
8
+ end
9
+
10
+ class InstanceReadyTimeout < VagrantSkytapError
11
+ error_key(:instance_ready_timeout)
12
+ end
13
+
14
+ class RsyncError < VagrantSkytapError
15
+ error_key(:rsync_error)
16
+ end
17
+
18
+ class MkdirError < VagrantSkytapError
19
+ error_key(:mkdir_error)
20
+ end
21
+
22
+ class Unauthorized < VagrantSkytapError
23
+ error_key(:unauthorized)
24
+ end
25
+
26
+ class DoesNotExist < VagrantSkytapError
27
+ error_key(:does_not_exist)
28
+ end
29
+
30
+ class ResourceBusy < VagrantSkytapError
31
+ error_key(:resource_busy)
32
+ end
33
+
34
+ class RateLimited < VagrantSkytapError
35
+ error_key(:rate_limited)
36
+ end
37
+
38
+ class UnprocessableEntity < VagrantSkytapError
39
+ error_key(:unprocessable_entity)
40
+ end
41
+
42
+ class OperationFailed < VagrantSkytapError
43
+ error_key(:operation_failed)
44
+ end
45
+
46
+ class VpnConnectionFailed < VagrantSkytapError
47
+ error_key(:vpn_connection_failed)
48
+ end
49
+
50
+ class SourceVmNotStopped < VagrantSkytapError
51
+ error_key(:source_vm_not_stopped)
52
+ end
53
+
54
+ class NoConnectionOptions < VagrantSkytapError
55
+ error_key(:no_connection_options)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,73 @@
1
+ begin
2
+ require "vagrant"
3
+ rescue LoadError
4
+ raise "The Vagrant Skytap plugin must be run within Vagrant."
5
+ end
6
+
7
+ # This is a sanity check to make sure no one is attempting to install
8
+ # this into an early Vagrant version.
9
+ if Vagrant::VERSION < "1.2.0"
10
+ raise "The Vagrant Skytap plugin is only compatible with Vagrant 1.2+"
11
+ end
12
+
13
+ module VagrantPlugins
14
+ module Skytap
15
+ class Plugin < Vagrant.plugin("2")
16
+ name "Skytap"
17
+ description <<-DESC
18
+ This plugin installs a provider that allows Vagrant to manage
19
+ machines in Skytap (EC2/VPC).
20
+ DESC
21
+
22
+ config(:skytap, :provider) do
23
+ require_relative "config"
24
+ Config
25
+ end
26
+
27
+ provider(:skytap, parallel: false) do
28
+ # Setup logging and i18n
29
+ setup_logging
30
+ setup_i18n
31
+
32
+ # Return the provider
33
+ require_relative "provider"
34
+ Provider
35
+ end
36
+
37
+ # This initializes the internationalization strings.
38
+ def self.setup_i18n
39
+ I18n.load_path << File.expand_path("locales/en.yml", Skytap.source_root)
40
+ I18n.reload!
41
+ end
42
+
43
+ # This sets up our log level to be whatever VAGRANT_LOG is.
44
+ def self.setup_logging
45
+ require "log4r"
46
+
47
+ level = nil
48
+ begin
49
+ level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase)
50
+ rescue NameError
51
+ # This means that the logging constant wasn't found,
52
+ # which is fine. We just keep `level` as `nil`. But
53
+ # we tell the user.
54
+ level = nil
55
+ end
56
+
57
+ # Some constants, such as "true" resolve to booleans, so the
58
+ # above error checking doesn't catch it. This will check to make
59
+ # sure that the log level is an integer, as Log4r requires.
60
+ level = nil if !level.is_a?(Integer)
61
+
62
+ # Set the logging level on all "vagrant" namespaced
63
+ # logs as long as we have a valid level.
64
+ if level
65
+ logger = Log4r::Logger.new("vagrant_skytap")
66
+ logger.outputters = Log4r::Outputter.stderr
67
+ logger.level = level
68
+ logger = nil
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,42 @@
1
+ require 'yaml'
2
+
3
+ module VagrantPlugins
4
+ module Skytap
5
+ class Properties
6
+ attr_reader :properties
7
+ attr_reader :data_dir
8
+
9
+ def self.filename
10
+ raise NotImplementedError.new('Must override')
11
+ end
12
+
13
+ def self.read(data_dir)
14
+ file = data_dir.join(filename)
15
+ if file.exist?
16
+ YAML.load_file(file)
17
+ end
18
+ end
19
+
20
+ def self.write(data_dir, properties={})
21
+ existing_props = read(data_dir) || {}
22
+ props = existing_props.merge(properties)
23
+
24
+ IO.write(data_dir.join(filename),
25
+ YAML.dump(props))
26
+ end
27
+
28
+ def initialize(data_dir)
29
+ @properties = self.class.read(data_dir)
30
+ @data_dir = data_dir
31
+ end
32
+
33
+ def read
34
+ self.class.read(@data_dir)
35
+ end
36
+
37
+ def write(properties={})
38
+ self.class.write(@data_dir, properties)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,50 @@
1
+ require "log4r"
2
+ require "vagrant"
3
+
4
+ module VagrantPlugins
5
+ module Skytap
6
+ class Provider < Vagrant.plugin("2", :provider)
7
+ def initialize(machine)
8
+ @machine = machine
9
+ end
10
+
11
+ def action(name)
12
+ # Attempt to get the action method from the Action class if it
13
+ # exists, otherwise return nil to show that we don't support the
14
+ # given action.
15
+ action_method = "action_#{name}"
16
+ return Action.send(action_method) if Action.respond_to?(action_method)
17
+ nil
18
+ end
19
+
20
+ def ssh_info
21
+ # Run a custom action called "read_ssh_info" which does what it
22
+ # says and puts the resulting SSH info into the `:machine_ssh_info`
23
+ # key in the environment.
24
+ env = @machine.action("read_ssh_info")
25
+ env[:machine_ssh_info]
26
+ end
27
+
28
+ def state
29
+ # Run a custom action we define called "read_state" which does
30
+ # what it says. It puts the state in the `:machine_state_id`
31
+ # key in the environment.
32
+ env = @machine.action("read_state")
33
+
34
+ state_id = env[:machine_state_id]
35
+
36
+ # Get the short and long description
37
+ short = I18n.t("vagrant_skytap.states.short_#{state_id}")
38
+ long = I18n.t("vagrant_skytap.states.long_#{state_id}")
39
+
40
+ # Return the MachineState object
41
+ Vagrant::MachineState.new(state_id, short, long)
42
+ end
43
+
44
+ def to_s
45
+ id = @machine.id.nil? ? "new" : @machine.id
46
+ "Skytap (#{id})"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,193 @@
1
+ require 'log4r'
2
+ require 'yaml'
3
+ require 'vagrant-skytap/vm_properties'
4
+ require 'vagrant-skytap/api/vpn'
5
+ require 'net/ssh/transport/session'
6
+
7
+ module VagrantPlugins
8
+ module Skytap
9
+ class SetupHelper
10
+ attr_reader :env, :environment, :machine, :provider_config
11
+ attr_reader :username, :password, :host, :port
12
+
13
+ def self.run!(env, environment)
14
+ new(env, environment).run!
15
+ end
16
+
17
+ def initialize(env, environment)
18
+ @env = env
19
+ @logger = Log4r::Logger.new("vagrant_skytap::setup_helper")
20
+ @environment = environment
21
+ @machine = env[:machine]
22
+ @provider_config = env[:machine].provider_config
23
+ @username = @machine.config.ssh.username
24
+ @password = @machine.config.ssh.password
25
+ @host = @machine.config.ssh.host
26
+ @port = @machine.config.ssh.port || Net::SSH::Transport::Session::DEFAULT_PORT
27
+ end
28
+
29
+ def current_vm
30
+ environment.current_vm
31
+ end
32
+
33
+ def run!
34
+ ask_routing
35
+ ask_credentials
36
+ write_properties
37
+ end
38
+
39
+
40
+ private
41
+
42
+
43
+ def ask_credentials
44
+ @logger.debug("ask_credentials")
45
+ return if username && password
46
+
47
+ env[:ui].info("Note that the machine password will be stored in " \
48
+ "cleartext on your local filesystem.")
49
+
50
+ creds = current_vm.credentials.select(&:recognized?)
51
+
52
+ if username
53
+ env[:ui].info("SSH username found in Vagrantfile: #{username}")
54
+ match = creds.detect{|c| c.username == username}
55
+
56
+ if match
57
+ @logger.info("Found username in Vagrantfile. Using matching password from credentials.")
58
+ env[:ui].info("Matched SSH password in Skytap VM credentials.")
59
+ @password = match.password
60
+ else
61
+ @logger.info("Found username in Vagrantfile. Will use manual password entry.")
62
+ end
63
+ elsif creds.present?
64
+ question = "How do you want to choose SSH credentials for machine '#{@machine.name}'?"
65
+ choices = creds.collect do |c|
66
+ "Use VM credentials stored in Skytap: #{c}"
67
+ end
68
+ choices << 'Type credentials manually'
69
+
70
+ ask_from_list(question, choices, 0) do |i|
71
+ if cred = creds[i]
72
+ @username = cred.username
73
+ @password = cred.password
74
+ end
75
+ end
76
+ else
77
+ @logger.info("No login credentials found for the VM. Prompting for manual username/password entry.")
78
+ end
79
+
80
+ @username ||= ask_username
81
+ @password ||= ask_password
82
+ end
83
+
84
+ def ask_username
85
+ @username = ask('Enter SSH username:').strip
86
+ end
87
+
88
+ def ask_password
89
+ @password = ask('Enter SSH password (no output will appear):', echo: false).strip
90
+ end
91
+
92
+ def ask_routing
93
+ @logger.debug("ask_routing")
94
+ return if host && port
95
+
96
+ iface = current_vm.interfaces.first
97
+ choices = connection_choices(iface).select(&:valid?)
98
+ raise Errors::NoConnectionOptions unless choices.present?
99
+
100
+ question = "How do you want to connect to machine '#{@machine.name}'?"
101
+ ask_from_list(question, choices, 0) do |i, choice|
102
+ @host, @port = choice.choose
103
+ end
104
+ end
105
+
106
+ def connection_choices(iface)
107
+ vpn_choices(iface)
108
+ end
109
+
110
+ def vpn_choices(iface)
111
+ candidates = vpns.select do |vpn|
112
+ vpn.nat_enabled? || vpn.subsumes?(iface.network)
113
+ end
114
+
115
+ candidates.collect {|vpn| vpn.choice_for_setup(iface.vm) }
116
+ end
117
+
118
+ def public_ip_choices(iface)
119
+ ips = iface.public_ips + iface.available_ips
120
+ ips.collect {|ip| ip.choice_for_setup(iface) }
121
+ end
122
+
123
+ def published_service_choices(iface)
124
+ iface.published_service_choices
125
+ end
126
+
127
+ def vpns
128
+ VagrantPlugins::Skytap::API::Vpn.all(env, query: {region: environment.region})
129
+ end
130
+
131
+ # Given a message and an array of choices, displays them to the user as a
132
+ # numbered list and asks the user to choose one. Returns the index that
133
+ # the user chose.
134
+ #
135
+ # The choices are presented to the user starting at 1, but the return
136
+ # value is a 0-based Ruby index. If specified, +default_index+ should be
137
+ # 0-based.
138
+ #
139
+ # If a block is given, yields chosen index.
140
+ def ask_from_list(message, choices, default_index=nil, &block)
141
+ if default_index && (default_index < 0 || default_index >= choices.size)
142
+ raise ArgumentError.new("Bad value for default #{default_index.inspect}")
143
+ end
144
+
145
+ numbered_choices = choices.each_with_index.collect do |choice, i|
146
+ "#{i+1}. #{choice}"
147
+ end.join("\n")
148
+
149
+ default_choice = " [#{default_index+1}]" if default_index
150
+ prompt = "Enter choice number:#{default_choice} "
151
+
152
+ index = nil
153
+
154
+ until index && index >= 0 && index < choices.length
155
+ input = env[:ui].ask([message, numbered_choices, prompt].join("\n\n"),
156
+ prefix: false)
157
+ begin
158
+ if default_index && input.blank?
159
+ index = default_index
160
+ else
161
+ index = Integer(input, 10) - 1
162
+ end
163
+ rescue ArgumentError
164
+ # No-op
165
+ end
166
+ end
167
+
168
+ yield index, choices[index] if block_given?
169
+
170
+ index
171
+ end
172
+
173
+ def ask(message, options={})
174
+ prompt = message
175
+ if default = options[:default]
176
+ prompt = "#{message} [#{default}] "
177
+ else
178
+ prompt = "#{message} "
179
+ end
180
+
181
+ env[:ui].ask(prompt, {prefix: false}.merge(options))
182
+ end
183
+
184
+ def write_properties
185
+ VmProperties.write(env[:machine].data_dir,
186
+ 'username' => username,
187
+ 'password' => password,
188
+ 'host' => host,
189
+ 'port' => port)
190
+ end
191
+ end
192
+ end
193
+ end