vagrant-openstack-provider 0.1

Sign up to get free protection for your applications and to get access to all the features.
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