vagrant-openstack-provider 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 (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/Appraisals +13 -0
  4. data/CHANGELOG.md +5 -0
  5. data/Gemfile +14 -0
  6. data/LICENSE.txt +22 -0
  7. data/RELEASE.md +15 -0
  8. data/Rakefile +21 -0
  9. data/Vagrantfile +37 -0
  10. data/dummy.box +0 -0
  11. data/example_box/README.md +13 -0
  12. data/example_box/metadata.json +3 -0
  13. data/features/provision.feature +35 -0
  14. data/features/steps/sdk_steps.rb +13 -0
  15. data/features/steps/server_steps.rb +25 -0
  16. data/features/support/env.rb +37 -0
  17. data/features/support/fog_mock.rb +19 -0
  18. data/features/vagrant-openstack.feature +70 -0
  19. data/gemfiles/latest_stable.gemfile +13 -0
  20. data/gemfiles/oldest_current.gemfile +13 -0
  21. data/gemfiles/previous_release.gemfile +13 -0
  22. data/lib/vagrant-openstack.rb +53 -0
  23. data/lib/vagrant-openstack/action.rb +123 -0
  24. data/lib/vagrant-openstack/action/connect_openstack.rb +30 -0
  25. data/lib/vagrant-openstack/action/create_server.rb +134 -0
  26. data/lib/vagrant-openstack/action/delete_server.rb +26 -0
  27. data/lib/vagrant-openstack/action/is_created.rb +16 -0
  28. data/lib/vagrant-openstack/action/message_already_created.rb +16 -0
  29. data/lib/vagrant-openstack/action/message_not_created.rb +16 -0
  30. data/lib/vagrant-openstack/action/read_ssh_info.rb +52 -0
  31. data/lib/vagrant-openstack/action/read_state.rb +40 -0
  32. data/lib/vagrant-openstack/action/sync_folders.rb +125 -0
  33. data/lib/vagrant-openstack/config.rb +229 -0
  34. data/lib/vagrant-openstack/errors.rb +36 -0
  35. data/lib/vagrant-openstack/openstack_client.rb +91 -0
  36. data/lib/vagrant-openstack/plugin.rb +37 -0
  37. data/lib/vagrant-openstack/provider.rb +50 -0
  38. data/lib/vagrant-openstack/version.rb +5 -0
  39. data/locales/en.yml +85 -0
  40. data/spec/vagrant-openstack/config_spec.rb +184 -0
  41. data/stackrc +32 -0
  42. data/vagrant-openstack.gemspec +23 -0
  43. metadata +135 -0
@@ -0,0 +1,125 @@
1
+ require "log4r"
2
+ require 'rbconfig'
3
+ require "vagrant/util/subprocess"
4
+
5
+ module VagrantPlugins
6
+ module Openstack
7
+ module Action
8
+
9
+ class SyncFolders
10
+ def initialize(app, env)
11
+ @app = app
12
+ end
13
+
14
+ def call(env)
15
+ sync_method = env[:machine].provider_config.sync_method
16
+ if sync_method == "rsync"
17
+ RsyncFolders.new(@app, env).call(env)
18
+ elsif sync_method == "none"
19
+ NoSyncFolders.new(@app, env).call(env)
20
+ else
21
+ raise Errors::SyncMethodError, :sync_method_value => sync_method
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ class NoSyncFolders
28
+ def initialize(app, env)
29
+ @app = app
30
+ end
31
+
32
+ def call(env)
33
+ @app.call(env)
34
+ env[:ui].info("Sync folders are disabled")
35
+ end
36
+ end
37
+
38
+ # This middleware uses `rsync` to sync the folders over to the
39
+ # remote instance.
40
+ class RsyncFolders
41
+ def initialize(app, env)
42
+ @app = app
43
+ @logger = Log4r::Logger.new("vagrant_openstack::action::sync_folders")
44
+ @host_os = RbConfig::CONFIG['host_os']
45
+ end
46
+
47
+ def call(env)
48
+ @app.call(env)
49
+
50
+ ssh_info = env[:machine].ssh_info
51
+
52
+ config = env[:machine].provider_config
53
+ rsync_includes = config.rsync_includes.to_a
54
+
55
+ env[:machine].config.vm.synced_folders.each do |id, data|
56
+ hostpath = File.expand_path(data[:hostpath], env[:root_path])
57
+ guestpath = data[:guestpath]
58
+
59
+ # Make sure there is a trailing slash on the host path to
60
+ # avoid creating an additional directory with rsync
61
+ hostpath = "#{hostpath}/" if hostpath !~ /\/$/
62
+
63
+ # If on Windows, modify the path to work with cygwin rsync
64
+ if @host_os =~ /mswin|mingw|cygwin/
65
+ hostpath = hostpath.sub(/^([A-Za-z]):\//, "/cygdrive/#{$1.downcase}/")
66
+ end
67
+
68
+ env[:ui].info(I18n.t("vagrant_openstack.rsync_folder",
69
+ :hostpath => hostpath,
70
+ :guestpath => guestpath))
71
+
72
+ # Create the guest path
73
+ env[:machine].communicate.sudo("mkdir -p '#{guestpath}'")
74
+ env[:machine].communicate.sudo(
75
+ "chown -R #{ssh_info[:username]} '#{guestpath}'")
76
+
77
+ # Generate rsync include commands
78
+ includes = rsync_includes.each_with_object([]) { |incl, incls|
79
+ incls << "--include"
80
+ incls << incl
81
+ }
82
+
83
+ # Rsync over to the guest path using the SSH info. add
84
+ # .hg/ to exclude list as that isn't covered in
85
+ # --cvs-exclude
86
+ command = [
87
+ "rsync", "--verbose", "--archive", "-z",
88
+ "--cvs-exclude",
89
+ "--exclude", ".hg/",
90
+ *includes,
91
+ "-e", "ssh -p #{ssh_info[:port]} -o StrictHostKeyChecking=no #{ssh_key_options(ssh_info)}",
92
+ hostpath,
93
+ "#{ssh_info[:username]}@#{ssh_info[:host]}:#{guestpath}"]
94
+ command.compact!
95
+
96
+ # during rsync, ignore files specified in .hgignore and
97
+ # .gitignore traditional .gitignore or .hgignore files
98
+ ignore_files = [".hgignore", ".gitignore"]
99
+ ignore_files.each do |ignore_file|
100
+ abs_ignore_file = env[:root_path].to_s + "/" + ignore_file
101
+ if File.exist?(abs_ignore_file)
102
+ command = command + ["--exclude-from", abs_ignore_file]
103
+ end
104
+ end
105
+
106
+ r = Vagrant::Util::Subprocess.execute(*command)
107
+ if r.exit_code != 0
108
+ raise Errors::RsyncError,
109
+ :guestpath => guestpath,
110
+ :hostpath => hostpath,
111
+ :stderr => r.stderr
112
+ end
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ def ssh_key_options(ssh_info)
119
+ # Ensure that `private_key_path` is an Array (for Vagrant < 1.4)
120
+ Array(ssh_info[:private_key_path]).map { |path| "-i '#{path}' " }.join
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,229 @@
1
+ require "vagrant"
2
+ require "fog"
3
+
4
+ module VagrantPlugins
5
+ module Openstack
6
+ class Config < Vagrant.plugin("2", :config)
7
+ # The API key to access Openstack.
8
+ #
9
+ # @return [String]
10
+ attr_accessor :api_key
11
+
12
+ # The region to access Openstack. If nil, it will default
13
+ # to DFW.
14
+ # (formerly know as 'endpoint')
15
+ #
16
+ # expected to be a symbol - :dfw (default), :ord, :lon
17
+ #
18
+ # Users should preference the openstack_region setting over openstack_compute_url
19
+ attr_accessor :openstack_region
20
+
21
+ # The compute_url to access Openstack. If nil, it will default
22
+ # to DFW.
23
+ # (formerly know as 'endpoint')
24
+ #
25
+ # expected to be a string url -
26
+ # 'https://dfw.servers.api.openstackcloud.com/v2'
27
+ # 'https://ord.servers.api.openstackcloud.com/v2'
28
+ # 'https://lon.servers.api.openstackcloud.com/v2'
29
+ #
30
+ # alternatively, can use constants if you require 'fog/openstack' in your Vagrantfile
31
+ # Fog::Compute::OpenstackV2::DFW_ENDPOINT
32
+ # Fog::Compute::OpenstackV2::ORD_ENDPOINT
33
+ # Fog::Compute::OpenstackV2::LON_ENDPOINT
34
+ #
35
+ # Users should preference the openstack_region setting over openstack_compute_url
36
+ attr_accessor :openstack_compute_url
37
+
38
+ # The authenication endpoint. This defaults to Openstack's global authentication endpoint.
39
+ # Users of the London data center should specify the following:
40
+ # https://lon.identity.api.openstackcloud.com/v2.0
41
+ attr_writer :openstack_auth_url
42
+
43
+ # Network configurations for the instance
44
+ #
45
+ # @return [String]
46
+ attr_accessor :network
47
+
48
+ # The flavor of server to launch, either the ID or name. This
49
+ # can also be a regular expression to partially match a name.
50
+ attr_accessor :flavor
51
+
52
+ # The name or ID of the image to use. This can also be a regular
53
+ # expression to partially match a name.
54
+ attr_accessor :image
55
+
56
+ # Alternately, if a keypair were already uploaded to Openstack,
57
+ # the key name could be provided.
58
+ #
59
+ # @return [String]
60
+ attr_accessor :key_name
61
+
62
+ # A Hash of metadata that will be sent to the instance for configuration
63
+ #
64
+ # @return [Hash]
65
+ attr_accessor :metadata
66
+
67
+ # The option that indicates RackConnect usage or not.
68
+ #
69
+ # @return [Boolean]
70
+ attr_accessor :rackconnect
71
+
72
+ #
73
+ # The name of the openstack project on witch the vm will be created.
74
+ #
75
+ attr_accessor :tenant_name
76
+
77
+ # The name of the server. This defaults to the name of the machine
78
+ # defined by Vagrant (via `config.vm.define`), but can be overriden
79
+ # here.
80
+ attr_accessor :server_name
81
+
82
+ # Specify the availability zone in which to create the instance
83
+ attr_accessor :availability_zone
84
+
85
+ # The username to access Openstack.
86
+ #
87
+ # @return [String]
88
+ attr_accessor :username
89
+
90
+ # The name of the keypair to use.
91
+ #
92
+ # @return [String]
93
+ attr_accessor :keypair_name
94
+
95
+ # The SSH username to use with this OpenStack instance. This overrides
96
+ # the `config.ssh.username` variable.
97
+ #
98
+ # @return [String]
99
+ attr_accessor :ssh_username
100
+
101
+ # The SSH timeout use after server creation. If server startup is too long
102
+ # the timeout value can be increase with this variable. Default is 60 seconds
103
+ #
104
+ # @return [Integer]
105
+ attr_accessor :ssh_timeout
106
+
107
+ # The disk configuration value.
108
+ # * AUTO - The server is built with a single partition the size of the target flavor disk. The file system is automatically adjusted to fit the entire partition.
109
+ # This keeps things simple and automated. AUTO is valid only for images and servers with a single partition that use the EXT3 file system.
110
+ # This is the default setting for applicable Openstack base images.
111
+ #
112
+ # * MANUAL - The server is built using whatever partition scheme and file system is in the source image. If the target flavor disk is larger,
113
+ # the remaining disk space is left unpartitioned. This enables images to have non-EXT3 file systems, multiple partitions,
114
+ # and so on, and enables you to manage the disk configuration.
115
+ #
116
+ # This defaults to MANUAL
117
+ attr_accessor :disk_config
118
+
119
+ # Opt files/directories in to the rsync operation performed by this provider
120
+ #
121
+ # @return [Array]
122
+ attr_accessor :rsync_includes
123
+
124
+ # The floating IP address from the IP pool which will be assigned to the instance.
125
+ #
126
+ # @return [String]
127
+ attr_accessor :floating_ip
128
+
129
+ # Sync folder method. Can be either "rsync" or "none"
130
+ #
131
+ # @return [String]
132
+ attr_accessor :sync_method
133
+
134
+ def initialize
135
+ @api_key = UNSET_VALUE
136
+ @openstack_region = UNSET_VALUE
137
+ @openstack_compute_url = UNSET_VALUE
138
+ @openstack_auth_url = UNSET_VALUE
139
+ @flavor = UNSET_VALUE
140
+ @image = UNSET_VALUE
141
+ @rackconnect = UNSET_VALUE
142
+ @availability_zone = UNSET_VALUE
143
+ @tenant_name = UNSET_VALUE
144
+ @server_name = UNSET_VALUE
145
+ @username = UNSET_VALUE
146
+ @disk_config = UNSET_VALUE
147
+ @network = UNSET_VALUE
148
+ @rsync_includes = []
149
+ @keypair_name = UNSET_VALUE
150
+ @ssh_username = UNSET_VALUE
151
+ @ssh_timeout = UNSET_VALUE
152
+ @floating_ip = UNSET_VALUE
153
+ @sync_method = UNSET_VALUE
154
+ end
155
+
156
+ def finalize!
157
+ @api_key = nil if @api_key == UNSET_VALUE
158
+ @openstack_region = nil if @openstack_region == UNSET_VALUE
159
+ @openstack_compute_url = nil if @openstack_compute_url == UNSET_VALUE
160
+ @openstack_auth_url = nil if @openstack_auth_url == UNSET_VALUE
161
+ @flavor = /m1.tiny/ if @flavor == UNSET_VALUE # TODO No default value
162
+ @image = /cirros/ if @image == UNSET_VALUE # TODO No default value
163
+ @rackconnect = nil if @rackconnect == UNSET_VALUE
164
+ @availability_zone = nil if @availability_zone == UNSET_VALUE
165
+ @tenant_name = nil if @tenant_name == UNSET_VALUE
166
+ @server_name = nil if @server_name == UNSET_VALUE
167
+ @metadata = nil if @metadata == UNSET_VALUE
168
+ @network = nil if @network == UNSET_VALUE
169
+ @username = nil if @username == UNSET_VALUE
170
+ @disk_config = nil if @disk_config == UNSET_VALUE
171
+ @rsync_includes = nil if @rsync_includes.empty?
172
+ @floating_ip = nil if @floating_ip == UNSET_VALUE
173
+ @sync_method = "rsync" if @sync_method == UNSET_VALUE
174
+
175
+ # Keypair defaults to nil
176
+ @keypair_name = nil if @keypair_name == UNSET_VALUE
177
+
178
+ # The SSH values by default are nil, and the top-level config
179
+ # `config.ssh` values are used.
180
+ @ssh_username = nil if @ssh_username == UNSET_VALUE
181
+ @ssh_timeout = 60 if @ssh_timeout == UNSET_VALUE
182
+ end
183
+
184
+ # @note Currently, you must authenticate against the UK authenication endpoint to access the London Data center.
185
+ # Hopefully this method makes the experience more seemless for users of the UK cloud.
186
+ def openstack_auth_url
187
+ if (@openstack_auth_url.nil? || @openstack_auth_url == UNSET_VALUE) && lon_region?
188
+ Fog::Openstack::UK_AUTH_ENDPOINT
189
+ else
190
+ @openstack_auth_url
191
+ end
192
+ end
193
+
194
+ def rsync_include(inc)
195
+ @rsync_includes << inc
196
+ end
197
+
198
+ def validate(machine)
199
+ errors = _detected_errors
200
+
201
+ errors << I18n.t("vagrant_openstack.config.api_key required") if !@api_key
202
+ errors << I18n.t("vagrant_openstack.config.username required") if !@username
203
+ errors << I18n.t("vagrant_openstack.config.keypair_name required") if !@keypair_name
204
+
205
+ {
206
+ :openstack_compute_url => @openstack_compute_url,
207
+ :openstack_auth_url => @openstack_auth_url
208
+ }.each_pair do |key, value|
209
+ errors << I18n.t("vagrant_openstack.config.invalid_uri", :key => key, :uri => value) unless value.nil? || valid_uri?(value)
210
+ end
211
+
212
+ { "Openstack Provider" => errors }
213
+ end
214
+
215
+ private
216
+
217
+ def lon_region?
218
+ openstack_region && openstack_region != UNSET_VALUE && openstack_region.to_sym == :lon
219
+ end
220
+
221
+ private
222
+
223
+ def valid_uri? value
224
+ uri = URI.parse value
225
+ uri.kind_of?(URI::HTTP)
226
+ end
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,36 @@
1
+ require "vagrant"
2
+
3
+ module VagrantPlugins
4
+ module Openstack
5
+ module Errors
6
+ class VagrantOpenstackError < Vagrant::Errors::VagrantError
7
+ error_namespace("vagrant_openstack.errors")
8
+ end
9
+
10
+ class CreateBadState < VagrantOpenstackError
11
+ error_key(:create_bad_state)
12
+ end
13
+
14
+ class NoMatchingFlavor < VagrantOpenstackError
15
+ error_key(:no_matching_flavor)
16
+ end
17
+
18
+ class NoMatchingImage < VagrantOpenstackError
19
+ error_key(:no_matching_image)
20
+ end
21
+
22
+ class SyncMethodError < VagrantOpenstackError
23
+ error_key(:sync_method_error)
24
+ end
25
+
26
+ class RsyncError < VagrantOpenstackError
27
+ error_key(:rsync_error)
28
+ end
29
+
30
+ class SshUnavailable < VagrantOpenstackError
31
+ error_key(:ssh_unavailble)
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,91 @@
1
+ require "log4r"
2
+ require "restclient"
3
+ require "json"
4
+
5
+ module VagrantPlugins
6
+ module Openstack
7
+ class OpenstackClient
8
+
9
+ def initialize()
10
+ @logger = Log4r::Logger.new("vagrant_openstack::openstack_client")
11
+ @token = nil
12
+ end
13
+
14
+ def authenticate(env)
15
+ @logger.debug("Authenticating on Keystone")
16
+ config = env[:machine].provider_config
17
+ authentication = RestClient.post(config.openstack_auth_url, {
18
+ :auth => {
19
+ :tenantName => config.tenant_name,
20
+ :passwordCredentials => {
21
+ :username => config.username,
22
+ :password => config.api_key
23
+ }
24
+ }
25
+ }.to_json,
26
+ :content_type => :json,
27
+ :accept => :json)
28
+
29
+ @token = JSON.parse(authentication)['access']['token']['id']
30
+ end
31
+
32
+ def get_all_flavors(env)
33
+ config = env[:machine].provider_config
34
+ flavors_json = RestClient.get(config.openstack_compute_url + "/flavors",
35
+ {"X-Auth-Token" => @token, :accept => :json})
36
+ return JSON.parse(flavors_json)['flavors'].map { |fl| Item.new(fl['id'], fl['name']) }
37
+ end
38
+
39
+ def get_all_images(env)
40
+ config = env[:machine].provider_config
41
+ images_json = RestClient.get(config.openstack_compute_url + "/images",
42
+ {"X-Auth-Token" => @token, :accept => :json})
43
+ return JSON.parse(images_json)['images'].map { |im| Item.new(im['id'], im['name']) }
44
+ end
45
+
46
+ def create_server(env, name, image_ref, flavor_ref, keypair)
47
+ config = env[:machine].provider_config
48
+ server = RestClient.post(config.openstack_compute_url + "/servers", {
49
+ :server => {
50
+ :name => name,
51
+ :imageRef => image_ref,
52
+ :flavorRef => flavor_ref,
53
+ :key_name => keypair
54
+ }
55
+ }.to_json,
56
+ "X-Auth-Token" => @token,
57
+ :accept => :json,
58
+ :content_type => :json)
59
+ return JSON.parse(server)['server']['id']
60
+ end
61
+
62
+ def get_server_details(env, server_id)
63
+ config = env[:machine].provider_config
64
+ server_details = RestClient.get(config.openstack_compute_url + "/servers/" + server_id,
65
+ "X-Auth-Token" => @token,
66
+ :accept => :json)
67
+ return JSON.parse(server_details)['server']
68
+ end
69
+
70
+ def add_floating_ip(env, server_id, floating_ip)
71
+ config = env[:machine].provider_config
72
+ server_details = RestClient.post(config.openstack_compute_url + "/servers/" + server_id + "/action", {
73
+ :addFloatingIp => {
74
+ :address => floating_ip
75
+ }
76
+ }.to_json,
77
+ "X-Auth-Token" => @token,
78
+ :accept => :json,
79
+ :content_type => :json)
80
+ end
81
+ end
82
+
83
+ class Item
84
+ attr_accessor :id, :name
85
+ def initialize(id, name)
86
+ @id = id
87
+ @name = name
88
+ end
89
+ end
90
+ end
91
+ end