inception-server 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/.chef/knife.rb +4 -0
  2. data/.gitignore +21 -0
  3. data/.kitchen.yml +47 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +18 -0
  6. data/Berksfile +8 -0
  7. data/Berksfile.lock +9 -0
  8. data/ChangeLog.md +20 -0
  9. data/Gemfile +27 -0
  10. data/Guardfile +6 -0
  11. data/LICENSE.txt +22 -0
  12. data/README.md +126 -0
  13. data/Rakefile +66 -0
  14. data/TODO.md +25 -0
  15. data/bin/inception +8 -0
  16. data/bin/inception-server +8 -0
  17. data/config/ssh/kitchen-aws +23 -0
  18. data/cookbooks/bosh_inception/README.md +15 -0
  19. data/cookbooks/bosh_inception/attributes/default.rb +25 -0
  20. data/cookbooks/bosh_inception/files/default/Gemfile.cf +5 -0
  21. data/cookbooks/bosh_inception/files/default/Gemfile.micro +5 -0
  22. data/cookbooks/bosh_inception/metadata.rb +32 -0
  23. data/cookbooks/bosh_inception/recipes/default.rb +16 -0
  24. data/cookbooks/bosh_inception/recipes/install_bosh.rb +37 -0
  25. data/cookbooks/bosh_inception/recipes/install_ruby.rb +10 -0
  26. data/cookbooks/bosh_inception/recipes/mount_store_volume.rb +24 -0
  27. data/cookbooks/bosh_inception/recipes/packages.rb +23 -0
  28. data/cookbooks/bosh_inception/recipes/setup_dotfog.rb +29 -0
  29. data/cookbooks/bosh_inception/recipes/setup_git.rb +34 -0
  30. data/cookbooks/bosh_inception/recipes/useful_dirs.rb +13 -0
  31. data/inception-server.gemspec +43 -0
  32. data/lib/inception/cli.rb +141 -0
  33. data/lib/inception/cli_helpers/display.rb +26 -0
  34. data/lib/inception/cli_helpers/interactions.rb +15 -0
  35. data/lib/inception/cli_helpers/prepare_deploy_settings.rb +89 -0
  36. data/lib/inception/cli_helpers/provider.rb +14 -0
  37. data/lib/inception/cli_helpers/settings.rb +53 -0
  38. data/lib/inception/inception_server.rb +304 -0
  39. data/lib/inception/inception_server_cookbook.rb +90 -0
  40. data/lib/inception/next_deploy_actions.rb +20 -0
  41. data/lib/inception/providers/README.md +5 -0
  42. data/lib/inception/providers/clients/aws_provider_client.rb +144 -0
  43. data/lib/inception/providers/clients/fog_provider_client.rb +185 -0
  44. data/lib/inception/providers/clients/openstack_provider_client.rb +84 -0
  45. data/lib/inception/providers/constants/aws_constants.rb +25 -0
  46. data/lib/inception/providers/constants/openstack_constants.rb +12 -0
  47. data/lib/inception/providers.rb +28 -0
  48. data/lib/inception/version.rb +3 -0
  49. data/lib/inception.rb +9 -0
  50. data/nodes/.gitkeep +0 -0
  51. data/spec/assets/.gitkeep +0 -0
  52. data/spec/assets/gitconfig +5 -0
  53. data/spec/assets/settings/aws-before-server.yml +14 -0
  54. data/spec/assets/settings/aws-created-server.yml +31 -0
  55. data/spec/integration/.gitkeep +0 -0
  56. data/spec/integration/aws/aws_basic_spec.rb +38 -0
  57. data/spec/spec_helper.rb +50 -0
  58. data/spec/support/aws/aws_helpers.rb +73 -0
  59. data/spec/support/settings_helper.rb +20 -0
  60. data/spec/support/stdout_capture.rb +17 -0
  61. data/spec/unit/.gitkeep +0 -0
  62. data/spec/unit/cli_delete_spec.rb +39 -0
  63. data/spec/unit/cli_deploy_aws_spec.rb +83 -0
  64. data/spec/unit/cli_ssh_spec.rb +80 -0
  65. data/spec/unit/inception_server_cookbook_spec.rb +62 -0
  66. data/spec/unit/inception_server_spec.rb +58 -0
  67. data/spec/unit/providers/aws_spec.rb +198 -0
  68. data/test/integration/default/bats/discover_user.bash +2 -0
  69. data/test/integration/default/bats/dotfog.bats +11 -0
  70. data/test/integration/default/bats/install_ruby.bats +8 -0
  71. data/test/integration/default/bats/useful_dirs.bats +8 -0
  72. data/test/integration/default/bats/user.bats +9 -0
  73. data/test/integration/default/bats/verify_bosh.bats +18 -0
  74. data/test/integration/default/bats/verify_git.bats +18 -0
  75. metadata +361 -0
@@ -0,0 +1,304 @@
1
+ require "fog"
2
+
3
+ module Inception
4
+ class InceptionServer
5
+
6
+ DEFAULT_SERVER_NAME = "inception"
7
+ DEFAULT_FLAVOR = "m1.small"
8
+ DEFAULT_DISK_SIZE = 16
9
+ DEFAULT_SECURITY_GROUPS = ["ssh"]
10
+
11
+ attr_reader :attributes
12
+
13
+ # @provider_client [Inception::Providers::FogProvider] - interact with IaaS
14
+ # @attributes [ReadWriteSettings]
15
+ #
16
+ # Required @attributes:
17
+ # {
18
+ # "name" => "inception",
19
+ # "ip_address" => "54.214.15.178",
20
+ # "key_pair" => {
21
+ # "name" => "inception",
22
+ # "private_key" => "private_key",
23
+ # "public_key" => "public_key"
24
+ # }
25
+ # }
26
+ #
27
+ # Including optional @attributes and default values:
28
+ # {
29
+ # "name" => "inception",
30
+ # "ip_address" => "54.214.15.178",
31
+ # "security_groups" => ["ssh"],
32
+ # "flavor" => "m1.small",
33
+ # "key_pair" => {
34
+ # "name" => "inception",
35
+ # "private_key" => "private_key",
36
+ # "public_key" => "public_key"
37
+ # }
38
+ # }
39
+ def initialize(provider_client, attributes, ssh_dir)
40
+ @provider_client = provider_client
41
+ @ssh_dir = ssh_dir
42
+ @attributes = attributes.is_a?(Hash) ? ReadWriteSettings.new(attributes) : attributes
43
+ raise "@attributes must be ReadWriteSettings (or Hash)" unless @attributes.is_a?(ReadWriteSettings)
44
+ end
45
+
46
+ # Create the underlying server, with key pair & security groups, unless it is already created
47
+ #
48
+ # The @attributes hash is updated with a `provisioned` key during/after creation.
49
+ # When saved as YAML it might look like:
50
+ # inception:
51
+ # provisioned:
52
+ # image_id: ami-123456
53
+ # server_id: i-e7f005d2
54
+ # security_groups:
55
+ # - ssh
56
+ # - mosh
57
+ # username: ubuntu
58
+ # disk_device: /dev/sdi
59
+ # host: ec2-54-214-15-178.us-west-2.compute.amazonaws.com
60
+ # validated: true
61
+ # converged: true
62
+ def create
63
+ validate_attributes_for_bootstrap
64
+ ensure_required_security_groups
65
+ create_missing_default_security_groups
66
+ bootstrap_vm
67
+ attach_persistent_disk
68
+ end
69
+
70
+ # Delete the server, volume and release the IP address
71
+ def delete_all
72
+ delete_server
73
+ delete_volume
74
+ delete_key_pair
75
+ release_ip_address
76
+ end
77
+
78
+ def delete_server
79
+ @fog_server = nil # force reload of fog_server model
80
+ if fog_server
81
+ print "Deleting server... "
82
+ fog_server.destroy
83
+ wait_for_termination(fog_server) unless Fog.mocking?
84
+ puts "done."
85
+ else
86
+ puts "Server already destroyed"
87
+ end
88
+ provisioned.delete("host")
89
+ provisioned.delete("server_id")
90
+ provisioned.delete("username")
91
+ end
92
+
93
+ def delete_volume
94
+ volume_id = provisioned.exists?("disk_device.volume_id")
95
+ if volume_id && (volume = fog_compute.volumes.get(volume_id)) && volume.ready?
96
+ print "Deleting volume... "
97
+ volume.destroy
98
+ wait_for_termination(volume, "deleting")
99
+ puts ""
100
+ else
101
+ puts "Volume already destroyed"
102
+ end
103
+ provisioned.delete("disk_device")
104
+ end
105
+
106
+ def delete_key_pair
107
+ key_pair_name = attributes.exists?("key_pair.name")
108
+ if key_pair_name && key_pair = fog_compute.key_pairs.get(key_pair_name)
109
+ puts "Deleting key pair '#{key_pair_name}'"
110
+ key_pair.destroy
111
+ else
112
+ puts "Keypair already destroyed"
113
+ end
114
+ attributes.delete("key_pair")
115
+ end
116
+
117
+
118
+ def release_ip_address
119
+ public_ip = provisioned.exists?("ip_address")
120
+ if public_ip && ip_address = fog_compute.addresses.get(public_ip)
121
+ puts "Releasing IP address #{public_ip}"
122
+ ip_address.destroy
123
+ else
124
+ puts "IP address already released"
125
+ end
126
+ provisioned.delete("ip_address")
127
+ end
128
+
129
+ def security_groups
130
+ @attributes.security_groups
131
+ end
132
+
133
+ def server_name
134
+ @attributes["name"] ||= DEFAULT_SERVER_NAME
135
+ @attributes.name
136
+ end
137
+
138
+ def key_name
139
+ @attributes.key_pair.name
140
+ end
141
+
142
+ def private_key_path
143
+ @private_key_path ||= File.join(@ssh_dir, key_name)
144
+ end
145
+
146
+ def public_key
147
+ @attributes.exists?("key_pair.public_key")
148
+ end
149
+
150
+ # Flavor/instance type of the server to be provisioned
151
+ # TODO: DEFAULT_FLAVOR should become IaaS/provider specific
152
+ def flavor
153
+ @attributes["flavor"] ||= DEFAULT_FLAVOR
154
+ end
155
+
156
+ # Size of attached persistent disk for the inception server
157
+ def disk_size
158
+ @attributes["disk_size"] ||= DEFAULT_DISK_SIZE
159
+ end
160
+
161
+ def ip_address
162
+ provisioned.ip_address
163
+ end
164
+
165
+ def image_id
166
+ @attributes["image_id"] ||= @provider_client.image_id
167
+ end
168
+
169
+ # The progresive/final attributes of the provisioned Inception server &
170
+ # persistent disk.
171
+ def provisioned
172
+ @attributes["provisioned"] = {} unless @attributes["provisioned"]
173
+ @attributes.provisioned
174
+ end
175
+
176
+ # Because @attributes["provisioned"] is not the same as @attributes.provisioned
177
+ # we need a helper to export the complete nested attributes.
178
+ def export_attributes
179
+ attrs = attributes.to_nested_hash
180
+ attrs["provisioned"] = provisioned.to_nested_hash
181
+ attrs
182
+ end
183
+
184
+ def disk_devices
185
+ provisioned["disk_device"] ||= default_disk_device
186
+ end
187
+
188
+ def external_disk_device
189
+ disk_devices["external"]
190
+ end
191
+
192
+ def default_disk_device
193
+ case @provider_client
194
+ when Inception::Providers::Clients::AwsProviderClient
195
+ { "external" => "/dev/sdf", "internal" => "/dev/xvdf" }
196
+ when Inception::Providers::Clients::OpenStackProviderClient
197
+ { "external" => "/dev/vdc", "internal" => "/dev/vdc" }
198
+ else
199
+ raise "Please implement InceptionServer#default_disk_device for #{@provider_client.class}"
200
+ end
201
+ end
202
+
203
+ def user_host
204
+ "#{provisioned.username}@#{provisioned.host}"
205
+ end
206
+
207
+ def fog_server
208
+ @fog_server ||= begin
209
+ if server_id = provisioned["server_id"]
210
+ fog_compute.servers.get(server_id)
211
+ end
212
+ end
213
+ end
214
+
215
+ def fog_compute
216
+ @provider_client.fog_compute
217
+ end
218
+
219
+ protected
220
+ # set_resource_name(fog_server, "inception")
221
+ # set_resource_name(volume, "inception-root")
222
+ # set_resource_name(volume, "inception-store")
223
+ def set_resource_name(resource, name)
224
+ @provider_client.set_resource_name(resource, name)
225
+ end
226
+
227
+ def fog_attributes
228
+ @provider_client.fog_attributes(self)
229
+ end
230
+
231
+ def validate_attributes_for_bootstrap
232
+ missing_attributes = []
233
+ missing_attributes << "provisioned.ip_address" unless @attributes.exists?("provisioned.ip_address")
234
+ missing_attributes << "key_pair.private_key" unless @attributes.exists?("key_pair.private_key")
235
+ if missing_attributes.size > 0
236
+ raise "Missing InceptionServer attributes: #{missing_attributes.join(', ')}"
237
+ end
238
+ end
239
+
240
+ # ssh group must be first (bootstrap method looks for port 22 in first group)
241
+ def ensure_required_security_groups
242
+ if @attributes["security_groups"] && @attributes["security_groups"].is_a?(Array)
243
+ unless @attributes["security_groups"].include?("ssh")
244
+ @attributes["security_groups"] = ["ssh", *@attributes["security_groups"]]
245
+ end
246
+ else
247
+ @attributes["security_groups"] = ["ssh"]
248
+ end
249
+ end
250
+
251
+ def create_missing_default_security_groups
252
+ # provider method only creates group if missing
253
+ @provider_client.create_security_group("ssh", "ssh", {ssh: 22})
254
+ end
255
+
256
+ def bootstrap_vm
257
+ unless fog_server
258
+ print "Booting #{flavor} inception server... "
259
+ @fog_server = @provider_client.bootstrap(fog_attributes)
260
+ provisioned["server_id"] = fog_server.id
261
+ provisioned["host"] = fog_server.dns_name || fog_server.public_ip_address
262
+ provisioned["username"] = fog_attributes[:username]
263
+ puts provisioned.server_id
264
+ end
265
+ set_resource_name(fog_server, server_name)
266
+ end
267
+
268
+ def attach_persistent_disk
269
+ unless Fog.mocking?
270
+ print "Confirming ssh access to server... "
271
+ Fog.wait_for(60) { fog_server.sshable?(ssh_options) }
272
+ puts "done"
273
+ end
274
+
275
+ unless volume = @provider_client.find_server_device(fog_server, external_disk_device)
276
+ print "Provisioning #{disk_size}Gb persistent disk for inception server... "
277
+ volume = @provider_client.create_and_attach_volume("Inception Disk", disk_size, fog_server, external_disk_device)
278
+ disk_devices["volume_id"] = volume.id
279
+ puts disk_devices.volume_id
280
+ end
281
+ set_resource_name(volume, server_name)
282
+ end
283
+
284
+ def ssh_options
285
+ {
286
+ keys: [private_key_path]
287
+ }
288
+ end
289
+
290
+ # Poll a fog model until it terminates; print . each second
291
+ def wait_for_termination(fog_model, state_to_wait_for="terminated")
292
+ fog_model.wait_for do
293
+ print "."
294
+ state == state_to_wait_for
295
+ end
296
+ end
297
+
298
+ protected
299
+ # TODO emit events rather than writing directly to STDOUT
300
+ def say(*args)
301
+ puts(*args)
302
+ end
303
+ end
304
+ end
@@ -0,0 +1,90 @@
1
+ module Inception
2
+ # Perform converge chef cookbooks upon inception server
3
+ class InceptionServerCookbook
4
+ include FileUtils
5
+
6
+ attr_reader :server, :settings, :project_dir
7
+
8
+ class InvalidTarget < StandardError; end
9
+
10
+ def initialize(inception_server, settings, project_dir)
11
+ @server = inception_server
12
+ @settings = settings
13
+ @project_dir = project_dir
14
+ end
15
+
16
+ def prepare
17
+ FileUtils.chdir(project_dir) do
18
+ prepare_project_dir
19
+ knife_solo :prepare unless ignore_chef_preparations?
20
+ end
21
+ end
22
+
23
+ # To be invoked within the settings_dir
24
+ def converge
25
+ FileUtils.chdir(project_dir) do
26
+ knife_solo :cook
27
+ end
28
+ end
29
+
30
+ def ignore_chef_preparations?
31
+ @settings.exists?("cookbook.prepared")
32
+ end
33
+
34
+ def user_host; server.user_host; end
35
+ def key_path; server.private_key_path; end
36
+
37
+ def knife_solo(command)
38
+ attributes = cookbook_attributes_for_inception.to_json
39
+ sh %Q{knife solo #{command} #{user_host} -i #{key_path} -j '#{attributes}' -r 'bosh_inception'}
40
+ end
41
+
42
+ protected
43
+ def prepare_project_dir
44
+ prepare_cookbook
45
+ prepare_knife_config
46
+ prepare_berksfile
47
+ end
48
+
49
+ def prepare_cookbook
50
+ mkdir_p("cookbooks")
51
+ rm_rf("cookbooks/bosh_inception")
52
+ cp_r(inception_cookbook_path, "cookbooks/")
53
+ end
54
+
55
+ def prepare_knife_config
56
+ mkdir_p("nodes") # needed for knife solo
57
+ end
58
+
59
+ def prepare_berksfile
60
+ unless File.exists?("Berksfile")
61
+ cp_r(File.join(gem_root_path, "Berksfile"), "Berksfile")
62
+ end
63
+ end
64
+
65
+ def cookbook_attributes_for_inception
66
+ {
67
+ "disk" => {
68
+ "mounted" => true,
69
+ "device" => settings.inception.provisioned.disk_device.internal
70
+ },
71
+ "git" => {
72
+ "name" => settings.git.name,
73
+ "email" => settings.git.email
74
+ },
75
+ "user" => {
76
+ "username" => settings.inception.provisioned.username
77
+ },
78
+ "fog" => settings.provider.credentials
79
+ }
80
+ end
81
+
82
+ def gem_root_path
83
+ File.expand_path("../../..", __FILE__)
84
+ end
85
+
86
+ def inception_cookbook_path
87
+ File.join(gem_root_path, "cookbooks/bosh_inception")
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,20 @@
1
+ module Inception
2
+
3
+ class NextDeployActions
4
+ def initialize(attributes, cli_options)
5
+ @attributes = attributes.is_a?(Hash) ? ReadWriteSettings.new(attributes) : attributes
6
+ raise "@attributes must be ReadWriteSettings (or Hash)" unless @attributes.is_a?(ReadWriteSettings)
7
+ raise "@cli_options must be Hash" unless cli_options.is_a?(Hash)
8
+ apply_cli_options(cli_options)
9
+ end
10
+
11
+ def skip_chef_converge?
12
+ @attributes["no_converge"] || @attributes["no-converge"] || @attributes["skip_chef_converge"]
13
+ end
14
+
15
+ protected
16
+ def apply_cli_options(cli_options)
17
+ @attributes.merge(cli_options)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ # Providers
2
+
3
+ Currently these files are kept in sync with bosh-cloudfoundry project.
4
+
5
+ TODO - extract into shared library.
@@ -0,0 +1,144 @@
1
+ # Copyright (c) 2012-2013 Stark & Wayne, LLC
2
+
3
+ module Inception; module Providers; module Clients; end; end; end
4
+
5
+ require "inception/providers/clients/fog_provider_client"
6
+ require "inception/providers/constants/aws_constants"
7
+
8
+ class Inception::Providers::Clients::AwsProviderClient < Inception::Providers::Clients::FogProviderClient
9
+ include Inception::Providers::Constants::AwsConstants
10
+
11
+ # @return [Integer] megabytes of RAM for requested flavor of server
12
+ def ram_for_server_flavor(server_flavor_id)
13
+ if flavor = fog_compute_flavor(server_flavor_id)
14
+ flavor[:ram]
15
+ else
16
+ raise "Unknown AWS flavor '#{server_flavor_id}'"
17
+ end
18
+ end
19
+
20
+ # @return [Hash] e.g. { :bits => 0, :cores => 2, :disk => 0,
21
+ # :id => 't1.micro', :name => 'Micro Instance', :ram => 613}
22
+ # or nil if +server_flavor_id+ is not a supported flavor ID
23
+ def fog_compute_flavor(server_flavor_id)
24
+ aws_compute_flavors.find { |fl| fl[:id] == server_flavor_id }
25
+ end
26
+
27
+ # @return [Array] of [Hash] for each supported compute flavor
28
+ # Example [Hash] { :bits => 0, :cores => 2, :disk => 0,
29
+ # :id => 't1.micro', :name => 'Micro Instance', :ram => 613}
30
+ def aws_compute_flavors
31
+ Fog::Compute::AWS::FLAVORS
32
+ end
33
+
34
+ def aws_compute_flavor_ids
35
+ aws_compute_flavors.map { |fl| fl[:id] }
36
+ end
37
+
38
+ # Provision an EC2 or VPC elastic IP addess.
39
+ # * VPC - provision_public_ip_address(vpc: true)
40
+ # * EC2 - provision_public_ip_address
41
+ # @return [String] provisions a new public IP address in target region
42
+ # TODO nil if none available
43
+ def provision_public_ip_address(options={})
44
+ if options.delete(:vpc)
45
+ options[:domain] = "vpc"
46
+ else
47
+ options[:domain] = options.delete(:domain) || "standard"
48
+ end
49
+ address = fog_compute.addresses.create(options)
50
+ address.public_ip
51
+ # TODO catch error and return nil
52
+ end
53
+
54
+ def associate_ip_address_with_server(ip_address, server)
55
+ address = fog_compute.addresses.get(ip_address)
56
+ address.server = server
57
+ end
58
+
59
+ def create_vpc(name, cidr_block)
60
+ vpc = fog_compute.vpcs.create(name: name, cidr_block: cidr_block)
61
+ vpc.id
62
+ end
63
+
64
+ # Creates a VPC subnet
65
+ # @return [String] the subnet_id
66
+ def create_subnet(vpc_id, cidr_block)
67
+ subnet = fog_compute.subnets.create(vpc_id: vpc_id, cidr_block: cidr_block)
68
+ subnet.subnet_id
69
+ end
70
+
71
+ def create_internet_gateway(vpc_id)
72
+ gateway = fog_compute.internet_gateways.create(vpc_id: vpc_id)
73
+ gateway.id
74
+ end
75
+
76
+ def find_server_device(server, device)
77
+ server.volumes.all.find {|v| v.device == device}
78
+ end
79
+
80
+ def create_and_attach_volume(name, disk_size, server, device)
81
+ volume = fog_compute.volumes.create(
82
+ size: disk_size,
83
+ name: name,
84
+ description: '',
85
+ device: device,
86
+ availability_zone: server.availability_zone)
87
+ # TODO: the following works in fog 1.9.0+ (but which has a bug in bootstrap)
88
+ # https://github.com/fog/fog/issues/1516
89
+ #
90
+ # volume.wait_for { volume.status == 'available' }
91
+ # volume.attach(server.id, "/dev/vdc")
92
+ # volume.wait_for { volume.status == 'in-use' }
93
+ #
94
+ # Instead, using:
95
+ volume.server = server
96
+ end
97
+
98
+ # Ubuntu 13.04
99
+ def image_id
100
+ region = fog_compute.region
101
+ # http://cloud-images.ubuntu.com/locator/ec2/
102
+ image_id = case region.to_s
103
+ when "ap-northeast-1"
104
+ "ami-6b26ab6a"
105
+ when "ap-southeast-1"
106
+ "ami-2b511e79"
107
+ when "eu-west-1"
108
+ "ami-3d160149"
109
+ when "sa-east-1"
110
+ "ami-28e43e35"
111
+ when "us-east-1"
112
+ "ami-c30360aa"
113
+ when "us-west-1"
114
+ "ami-d383af96"
115
+ when "ap-southeast-2"
116
+ "ami-84a333be"
117
+ when "us-west-2"
118
+ "ami-bf1d8a8f"
119
+ end
120
+ image_id || raise("Please add Ubuntu 13.04 64bit (EBS) AMI image id to aws.rb#raring_image_id method for region '#{region}'")
121
+ end
122
+
123
+ # Construct a Fog::Compute object
124
+ # Uses +attributes+ which normally originates from +settings.provider+
125
+ def setup_fog_connection
126
+ configuration = Fog.symbolize_credentials(attributes.credentials)
127
+ configuration[:provider] = "AWS"
128
+ configuration[:region] = attributes.region
129
+ @fog_compute = Fog::Compute.new(configuration)
130
+ end
131
+
132
+ def fog_attributes(inception_server)
133
+ {
134
+ image_id: inception_server.image_id,
135
+ groups: inception_server.security_groups,
136
+ key_name: inception_server.key_name,
137
+ private_key_path: inception_server.private_key_path,
138
+ flavor_id: inception_server.flavor,
139
+ public_ip_address: inception_server.ip_address,
140
+ bits: 64,
141
+ username: "ubuntu",
142
+ }
143
+ end
144
+ end