inception 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.chef/knife.rb +4 -0
- data/.gitignore +20 -0
- data/.kitchen.yml +42 -0
- data/.rspec +3 -0
- data/.travis.yml +23 -0
- data/Berksfile +8 -0
- data/Berksfile.lock +9 -0
- data/Gemfile +18 -0
- data/LICENSE.txt +22 -0
- data/README.md +139 -0
- data/Rakefile +66 -0
- data/TODO.md +26 -0
- data/bin/bosh-inception +8 -0
- data/config/ssh/kitchen-aws +23 -0
- data/cookbooks/bosh_inception/README.md +15 -0
- data/cookbooks/bosh_inception/attributes/default.rb +20 -0
- data/cookbooks/bosh_inception/files/default/Gemfile.cf +4 -0
- data/cookbooks/bosh_inception/files/default/Gemfile.micro +5 -0
- data/cookbooks/bosh_inception/metadata.rb +32 -0
- data/cookbooks/bosh_inception/recipes/default.rb +15 -0
- data/cookbooks/bosh_inception/recipes/install_bosh.rb +37 -0
- data/cookbooks/bosh_inception/recipes/install_ruby.rb +10 -0
- data/cookbooks/bosh_inception/recipes/mount_store_volume.rb +24 -0
- data/cookbooks/bosh_inception/recipes/packages.rb +23 -0
- data/cookbooks/bosh_inception/recipes/setup_git.rb +34 -0
- data/cookbooks/bosh_inception/recipes/useful_dirs.rb +13 -0
- data/inception.gemspec +42 -0
- data/lib/bosh/providers.rb +41 -0
- data/lib/bosh/providers/README.md +5 -0
- data/lib/bosh/providers/cli/aws_provider_cli.rb +58 -0
- data/lib/bosh/providers/cli/openstack_provider_cli.rb +47 -0
- data/lib/bosh/providers/cli/provider_cli.rb +17 -0
- data/lib/bosh/providers/clients/aws_provider_client.rb +168 -0
- data/lib/bosh/providers/clients/fog_provider_client.rb +161 -0
- data/lib/bosh/providers/clients/openstack_provider_client.rb +65 -0
- data/lib/bosh/providers/constants/aws_constants.rb +25 -0
- data/lib/bosh/providers/constants/openstack_constants.rb +12 -0
- data/lib/inception.rb +9 -0
- data/lib/inception/cli.rb +136 -0
- data/lib/inception/cli_helpers/display.rb +26 -0
- data/lib/inception/cli_helpers/infrastructure.rb +157 -0
- data/lib/inception/cli_helpers/interactions.rb +15 -0
- data/lib/inception/cli_helpers/prepare_deploy_settings.rb +89 -0
- data/lib/inception/cli_helpers/provider.rb +14 -0
- data/lib/inception/cli_helpers/settings.rb +47 -0
- data/lib/inception/inception_server.rb +305 -0
- data/lib/inception/inception_server_cookbook.rb +89 -0
- data/lib/inception/next_deploy_actions.rb +20 -0
- data/lib/inception/version.rb +3 -0
- data/nodes/.gitkeep +0 -0
- data/spec/assets/.gitkeep +0 -0
- data/spec/assets/gitconfig +5 -0
- data/spec/assets/settings/aws-before-server.yml +14 -0
- data/spec/assets/settings/aws-created-server.yml +31 -0
- data/spec/integration/.gitkeep +0 -0
- data/spec/integration/aws/aws_basic_spec.rb +39 -0
- data/spec/spec_helper.rb +50 -0
- data/spec/support/aws/aws_helpers.rb +73 -0
- data/spec/support/settings_helper.rb +20 -0
- data/spec/support/stdout_capture.rb +17 -0
- data/spec/unit/.gitkeep +0 -0
- data/spec/unit/bosh/providers/aws_spec.rb +199 -0
- data/spec/unit/cli_delete_spec.rb +39 -0
- data/spec/unit/cli_deploy_aws_spec.rb +84 -0
- data/spec/unit/cli_ssh_spec.rb +82 -0
- data/spec/unit/inception_server_cookbook_spec.rb +61 -0
- data/spec/unit/inception_server_spec.rb +58 -0
- data/test/integration/default/bats/discover_user.bash +2 -0
- data/test/integration/default/bats/install_ruby.bats +8 -0
- data/test/integration/default/bats/useful_dirs.bats +8 -0
- data/test/integration/default/bats/user.bats +9 -0
- data/test/integration/default/bats/verify_bosh.bats +13 -0
- data/test/integration/default/bats/verify_git.bats +18 -0
- metadata +342 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
module Inception::CliHelpers
|
2
|
+
module Display
|
3
|
+
# Display header for a new section of the bootstrapper
|
4
|
+
def header(title, options={})
|
5
|
+
say "" # golden whitespace
|
6
|
+
if skipping = options[:skipping]
|
7
|
+
say "Skipping #{title}", [:yellow, :bold]
|
8
|
+
say skipping
|
9
|
+
else
|
10
|
+
say title, [:green, :bold]
|
11
|
+
end
|
12
|
+
say "" # more golden whitespace
|
13
|
+
end
|
14
|
+
|
15
|
+
def error(message)
|
16
|
+
say message, :red
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
|
20
|
+
def confirm(message)
|
21
|
+
say "Confirming: #{message}", green
|
22
|
+
say "" # bonus golden whitespace
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module Inception::CliHelpers
|
2
|
+
module Infrastructure
|
3
|
+
# Prompts user to choose an Iaas provider
|
4
|
+
# Sets settings.provider.name
|
5
|
+
def configure_provider
|
6
|
+
unless valid_infrastructure?
|
7
|
+
choose_fog_provider unless settings.exists?("provider.name")
|
8
|
+
choose_provider unless settings.exists?("provider.name")
|
9
|
+
setup_provider_credentials
|
10
|
+
end
|
11
|
+
confirm_infrastructure
|
12
|
+
end
|
13
|
+
|
14
|
+
# Displays a prompt for known IaaS that are configured
|
15
|
+
# within .fog config file if found.
|
16
|
+
#
|
17
|
+
# If no ~/.fog file found or user chooses "Alternate credentials"
|
18
|
+
# then no changes are made to settings.
|
19
|
+
#
|
20
|
+
# For example:
|
21
|
+
#
|
22
|
+
# 1. AWS (default)
|
23
|
+
# 2. AWS (bosh)
|
24
|
+
# 3. Alternate credentials
|
25
|
+
# Choose infrastructure: 1
|
26
|
+
#
|
27
|
+
# If .fog config only contains one provider, do not prompt.
|
28
|
+
#
|
29
|
+
# fog config file looks like:
|
30
|
+
# :default:
|
31
|
+
# :aws_access_key_id: PERSONAL_ACCESS_KEY
|
32
|
+
# :aws_secret_access_key: PERSONAL_SECRET
|
33
|
+
# :bosh:
|
34
|
+
# :aws_access_key_id: SPECIAL_IAM_ACCESS_KEY
|
35
|
+
# :aws_secret_access_key: SPECIAL_IAM_SECRET_KEY
|
36
|
+
#
|
37
|
+
# Convert this into:
|
38
|
+
# { "AWS (default)" => {:aws_access_key_id => ...}, "AWS (bosh)" => {...} }
|
39
|
+
#
|
40
|
+
# Then display options to user to choose.
|
41
|
+
#
|
42
|
+
# Currently detects following fog providers:
|
43
|
+
# * AWS
|
44
|
+
# * OpenStack
|
45
|
+
#
|
46
|
+
# If "Alternate credentials" is selected, then user is prompted for fog
|
47
|
+
# credentials:
|
48
|
+
# * provider?
|
49
|
+
# * access keys?
|
50
|
+
# * API URI or region?
|
51
|
+
#
|
52
|
+
# Sets (unless 'Alternate credentials' is chosen)
|
53
|
+
# * settings.provider.name
|
54
|
+
# * settings.provider.credentials
|
55
|
+
#
|
56
|
+
# For AWS, the latter has keys:
|
57
|
+
# {:aws_access_key_id, :aws_secret_access_key}
|
58
|
+
#
|
59
|
+
# For OpenStack, the latter has keys:
|
60
|
+
# {:openstack_username, :openstack_api_key, :openstack_tenant
|
61
|
+
# :openstack_auth_url, :openstack_region }
|
62
|
+
def choose_fog_provider
|
63
|
+
fog_providers = {}
|
64
|
+
# Prepare menu options:
|
65
|
+
# each provider/profile name gets a menu choice option
|
66
|
+
fog_config.inject({}) do |iaas_options, fog_profile|
|
67
|
+
profile_name, profile = fog_profile
|
68
|
+
if profile[:aws_access_key_id]
|
69
|
+
# TODO does fog have inbuilt detection algorithm?
|
70
|
+
fog_providers["AWS (#{profile_name})"] = {
|
71
|
+
"name" => "aws",
|
72
|
+
"provider" => "AWS",
|
73
|
+
"aws_access_key_id" => profile[:aws_access_key_id],
|
74
|
+
"aws_secret_access_key" => profile[:aws_secret_access_key]
|
75
|
+
}
|
76
|
+
end
|
77
|
+
if profile[:openstack_username]
|
78
|
+
# TODO does fog have inbuilt detection algorithm?
|
79
|
+
fog_providers["OpenStack (#{profile_name})"] = {
|
80
|
+
"name" => "openstack",
|
81
|
+
"provider" => "OpenStack",
|
82
|
+
"openstack_username" => profile[:openstack_username],
|
83
|
+
"openstack_api_key" => profile[:openstack_api_key],
|
84
|
+
"openstack_tenant" => profile[:openstack_tenant],
|
85
|
+
"openstack_auth_url" => profile[:openstack_auth_url],
|
86
|
+
"openstack_region" => profile[:openstack_region]
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
# Display menu
|
91
|
+
# Include "Alternate credentials" as the last option
|
92
|
+
if fog_providers.keys.size > 0
|
93
|
+
hl.choose do |menu|
|
94
|
+
menu.prompt = "Choose infrastructure: "
|
95
|
+
fog_providers.each do |label, credentials|
|
96
|
+
menu.choice(label) do
|
97
|
+
settings.set("provider.name", credentials.delete("name"))
|
98
|
+
settings.set("provider.credentials", credentials)
|
99
|
+
save_settings!
|
100
|
+
end
|
101
|
+
end
|
102
|
+
menu.choice("Alternate credentials")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Prompts user to pick from the supported regions
|
108
|
+
def choose_provider
|
109
|
+
hl.choose do |menu|
|
110
|
+
menu.prompt = "Choose infrastructure: "
|
111
|
+
menu.choice("AWS") do
|
112
|
+
settings.provider["name"] = "aws"
|
113
|
+
end
|
114
|
+
menu.choice("OpenStack") do
|
115
|
+
settings.provider["name"] = "openstack"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def setup_provider_credentials
|
121
|
+
say "Using provider #{settings.provider.name}:"
|
122
|
+
say ""
|
123
|
+
settings.set_default("provider", {}) # to ensure settings.provider exists
|
124
|
+
provider_cli = Bosh::Providers.provider_cli(settings.provider.name, settings.provider)
|
125
|
+
provider_cli.perform
|
126
|
+
settings["provider"] = provider_cli.export_attributes
|
127
|
+
settings.create_accessors!
|
128
|
+
end
|
129
|
+
|
130
|
+
def valid_infrastructure?
|
131
|
+
settings.exists?("provider.name") &&
|
132
|
+
settings.exists?("provider.region") &&
|
133
|
+
settings.exists?("provider.credentials") &&
|
134
|
+
provider_client
|
135
|
+
end
|
136
|
+
|
137
|
+
def confirm_infrastructure
|
138
|
+
confirm "Using #{settings.provider.name}/#{settings.provider.region}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def fog_config
|
142
|
+
@fog_config ||= begin
|
143
|
+
if File.exists?(File.expand_path(fog_config_path))
|
144
|
+
say "Found infrastructure API credentials at #{fog_config_path} (override with $FOG)"
|
145
|
+
YAML.load_file(File.expand_path(fog_config_path))
|
146
|
+
else
|
147
|
+
say "No existing #{fog_config_path} fog configuration file", :yellow
|
148
|
+
{}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def fog_config_path
|
154
|
+
ENV['FOG'] || "~/.fog"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Inception::CliHelpers
|
2
|
+
module Interactions
|
3
|
+
def cyan; "\033[36m" end
|
4
|
+
def clear; "\033[0m" end
|
5
|
+
def bold; "\033[1m" end
|
6
|
+
def red; "\033[31m" end
|
7
|
+
def green; "\033[32m" end
|
8
|
+
def yellow; "\033[33m" end
|
9
|
+
|
10
|
+
# Helper to access HighLine for ask & menu prompts
|
11
|
+
def hl
|
12
|
+
@hl ||= HighLine.new
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Inception::CliHelpers
|
2
|
+
module PrepareDeploySettings
|
3
|
+
def update_git_config
|
4
|
+
gitconfig = File.expand_path("~/.gitconfig")
|
5
|
+
if File.exists?(gitconfig)
|
6
|
+
say "Using your git user.name (#{`git config -f #{gitconfig} user.name`.strip})"
|
7
|
+
settings.set("git.name", `git config -f #{gitconfig} user.name`.strip)
|
8
|
+
settings.set("git.email", `git config -f #{gitconfig} user.email`.strip)
|
9
|
+
save_settings!
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Attempt to provision a new public IP; if none available,
|
14
|
+
# then look for a pre-provisioned public IP that's not assigned
|
15
|
+
# to a server; else error. The user needs to go get more
|
16
|
+
# public IP addresses in this region.
|
17
|
+
def provision_or_reuse_public_ip_address_for_inception
|
18
|
+
say "Acquiring a public IP address... "
|
19
|
+
if public_ip = provider_client.provision_or_reuse_public_ip_address
|
20
|
+
say public_ip, :green
|
21
|
+
settings.set("inception.provisioned.ip_address", public_ip)
|
22
|
+
save_settings!
|
23
|
+
else
|
24
|
+
say "none available.", :red
|
25
|
+
error "Please rustle up at least one public IP address and try again."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def default_server_name
|
30
|
+
"inception"
|
31
|
+
end
|
32
|
+
|
33
|
+
def default_key_pair_name
|
34
|
+
default_server_name
|
35
|
+
end
|
36
|
+
|
37
|
+
def recreate_key_pair_for_inception
|
38
|
+
key_pair_name = settings.set_default("inception.key_pair.name", default_key_pair_name)
|
39
|
+
provider_client.delete_key_pair_if_exists(key_pair_name)
|
40
|
+
key_pair = provider_client.create_key_pair(key_pair_name)
|
41
|
+
settings.set("inception.key_pair.private_key", key_pair.private_key)
|
42
|
+
settings.set("inception.key_pair.fingerprint", key_pair.fingerprint)
|
43
|
+
save_settings!
|
44
|
+
end
|
45
|
+
|
46
|
+
def private_key_path_for_inception
|
47
|
+
@private_key_path_for_inception ||= File.join(settings_dir, "ssh", settings.inception.key_pair.name)
|
48
|
+
end
|
49
|
+
|
50
|
+
# The keys for the inception server originate from the provider and are cached in
|
51
|
+
# the manifest. The private key is stored locally; the public key is placed
|
52
|
+
# on the inception server.
|
53
|
+
def recreate_private_key_file_for_inception
|
54
|
+
mkdir_p(File.dirname(private_key_path_for_inception))
|
55
|
+
File.chmod(0700, File.dirname(private_key_path_for_inception))
|
56
|
+
File.open(private_key_path_for_inception, "w") { |file| file << settings.inception.key_pair.private_key }
|
57
|
+
File.chmod(0600, private_key_path_for_inception)
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
# Required settings:
|
62
|
+
# * git.name
|
63
|
+
# * git.email
|
64
|
+
def validate_deploy_settings
|
65
|
+
begin
|
66
|
+
settings.git.name
|
67
|
+
settings.git.email
|
68
|
+
rescue Settingslogic::MissingSetting => e
|
69
|
+
error "Please setup local git user.name & user.email config; or specify git.name & git.email in settings.yml"
|
70
|
+
end
|
71
|
+
|
72
|
+
begin
|
73
|
+
settings.provider.name
|
74
|
+
settings.provider.region
|
75
|
+
settings.provider.credentials
|
76
|
+
rescue Settingslogic::MissingSetting => e
|
77
|
+
error "Wooh there, we need provider.name, provider.region, provider.credentials in settings.yml to proceed."
|
78
|
+
end
|
79
|
+
|
80
|
+
begin
|
81
|
+
settings.inception.provisioned.ip_address
|
82
|
+
settings.inception.key_pair.name
|
83
|
+
settings.inception.key_pair.private_key
|
84
|
+
rescue Settingslogic::MissingSetting => e
|
85
|
+
error "Wooh there, we need inception.provisioned.ip_address, inception.key_pair.name, & inception.key_pair.private_key in settings.yml to proceed."
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Inception::CliHelpers
|
2
|
+
module Provider
|
3
|
+
def provider_client
|
4
|
+
@provider_client ||= begin
|
5
|
+
Bosh::Providers.provider_client(settings.provider)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# If the +provider_client+ uses fog, then this will return its +fog_compute+ client object
|
10
|
+
def fog_compute
|
11
|
+
provider_client.respond_to?(:fog_compute) ? provider_client.fog_compute : nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "settingslogic"
|
2
|
+
|
3
|
+
module Inception::CliHelpers
|
4
|
+
module Settings
|
5
|
+
include FileUtils
|
6
|
+
|
7
|
+
# The base directory for holding the manifest settings file
|
8
|
+
# and private keys
|
9
|
+
#
|
10
|
+
# Defaults to ~/.bosh_inception; and can be overridden with either:
|
11
|
+
# * $SETTINGS - to a folder (supported method)
|
12
|
+
def settings_dir
|
13
|
+
@settings_dir ||= File.expand_path(ENV["SETTINGS"] || "~/.bosh_inception")
|
14
|
+
end
|
15
|
+
|
16
|
+
def settings_ssh_dir
|
17
|
+
File.join(settings_dir, "ssh")
|
18
|
+
end
|
19
|
+
|
20
|
+
def settings_path
|
21
|
+
@settings_path ||= File.join(settings_dir, "settings.yml")
|
22
|
+
end
|
23
|
+
|
24
|
+
def settings
|
25
|
+
@settings ||= begin
|
26
|
+
unless File.exists?(settings_path)
|
27
|
+
mkdir_p(settings_ssh_dir)
|
28
|
+
File.open(settings_path, "w") { |file| file << "--- {}" }
|
29
|
+
end
|
30
|
+
chmod(0600, settings_path)
|
31
|
+
chmod(0700, settings_ssh_dir)
|
32
|
+
Settingslogic.new(settings_path)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Saves current nested Settingslogic into pure Hash-based YAML file
|
37
|
+
# Recreates accessors on Settingslogic object (since something has changed)
|
38
|
+
def save_settings!
|
39
|
+
File.open(settings_path, "w") { |f| f << settings.to_nested_hash.to_yaml }
|
40
|
+
settings.create_accessors!
|
41
|
+
end
|
42
|
+
|
43
|
+
def migrate_old_settings
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,305 @@
|
|
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 [Bosh::Providers::FogProvider] - interact with IaaS
|
14
|
+
# @attributes [Settingslogic]
|
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) ? Settingslogic.new(attributes) : attributes
|
43
|
+
raise "@attributes must be Settingslogic (or Hash)" unless @attributes.is_a?(Settingslogic)
|
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
|
+
# Flavor/instance type of the server to be provisioned
|
147
|
+
# TODO: DEFAULT_FLAVOR should become IaaS/provider specific
|
148
|
+
def flavor
|
149
|
+
@attributes["flavor"] ||= DEFAULT_FLAVOR
|
150
|
+
end
|
151
|
+
|
152
|
+
# Size of attached persistent disk for the inception server
|
153
|
+
def disk_size
|
154
|
+
@attributes["disk_size"] ||= DEFAULT_DISK_SIZE
|
155
|
+
end
|
156
|
+
|
157
|
+
def ip_address
|
158
|
+
provisioned.ip_address
|
159
|
+
end
|
160
|
+
|
161
|
+
def image_id
|
162
|
+
@attributes["image_id"] ||= @provider_client.raring_image_id
|
163
|
+
end
|
164
|
+
|
165
|
+
# The progresive/final attributes of the provisioned Inception server &
|
166
|
+
# persistent disk.
|
167
|
+
def provisioned
|
168
|
+
@attributes["provisioned"] = {} unless @attributes["provisioned"]
|
169
|
+
@attributes.provisioned
|
170
|
+
end
|
171
|
+
|
172
|
+
# Because @attributes["provisioned"] is not the same as @attributes.provisioned
|
173
|
+
# we need a helper to export the complete nested attributes.
|
174
|
+
def export_attributes
|
175
|
+
attrs = attributes.to_nested_hash
|
176
|
+
attrs["provisioned"] = provisioned.to_nested_hash
|
177
|
+
attrs
|
178
|
+
end
|
179
|
+
|
180
|
+
def disk_devices
|
181
|
+
provisioned["disk_device"] ||= default_disk_device
|
182
|
+
end
|
183
|
+
|
184
|
+
def external_disk_device
|
185
|
+
disk_devices["external"]
|
186
|
+
end
|
187
|
+
|
188
|
+
def default_disk_device
|
189
|
+
case @provider_client
|
190
|
+
when Bosh::Providers::Clients::AwsProviderClient
|
191
|
+
{ "external" => "/dev/sdf", "internal" => "/dev/xvdf" }
|
192
|
+
when Bosh::Providers::Clients::OpenStackProviderClient
|
193
|
+
{ "external" => "/dev/vdc", "internal" => "/dev/vdc" }
|
194
|
+
else
|
195
|
+
raise "Please implement InceptionServer#default_disk_device for #{@provider_client.class}"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def user_host
|
200
|
+
"#{provisioned.username}@#{provisioned.host}"
|
201
|
+
end
|
202
|
+
|
203
|
+
def fog_server
|
204
|
+
@fog_server ||= begin
|
205
|
+
if server_id = provisioned["server_id"]
|
206
|
+
fog_compute.servers.get(server_id)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def fog_compute
|
212
|
+
@provider_client.fog_compute
|
213
|
+
end
|
214
|
+
|
215
|
+
protected
|
216
|
+
# set_resource_name(fog_server, "inception")
|
217
|
+
# set_resource_name(volume, "inception-root")
|
218
|
+
# set_resource_name(volume, "inception-store")
|
219
|
+
def set_resource_name(resource, name)
|
220
|
+
@provider_client.set_resource_name(resource, name)
|
221
|
+
end
|
222
|
+
|
223
|
+
def fog_attributes
|
224
|
+
{
|
225
|
+
:image_id => image_id,
|
226
|
+
:groups => security_groups,
|
227
|
+
:key_name => key_name,
|
228
|
+
:private_key_path => private_key_path,
|
229
|
+
:flavor_id => flavor,
|
230
|
+
:public_ip_address => ip_address,
|
231
|
+
:bits => 64,
|
232
|
+
:username => "ubuntu",
|
233
|
+
}
|
234
|
+
end
|
235
|
+
|
236
|
+
def validate_attributes_for_bootstrap
|
237
|
+
missing_attributes = []
|
238
|
+
missing_attributes << "provisioned.ip_address" unless @attributes.exists?("provisioned.ip_address")
|
239
|
+
missing_attributes << "key_pair.private_key" unless @attributes.exists?("key_pair.private_key")
|
240
|
+
if missing_attributes.size > 0
|
241
|
+
raise "Missing InceptionServer attributes: #{missing_attributes.join(', ')}"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# ssh group must be first (bootstrap method looks for port 22 in first group)
|
246
|
+
def ensure_required_security_groups
|
247
|
+
if @attributes["security_groups"] && @attributes["security_groups"].is_a?(Array)
|
248
|
+
unless @attributes["security_groups"].include?("ssh")
|
249
|
+
@attributes["security_groups"] = ["ssh", *@attributes["security_groups"]]
|
250
|
+
end
|
251
|
+
else
|
252
|
+
@attributes["security_groups"] = ["ssh"]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def create_missing_default_security_groups
|
257
|
+
# provider method only creates group if missing
|
258
|
+
@provider_client.create_security_group("ssh", "ssh", {ssh: 22})
|
259
|
+
end
|
260
|
+
|
261
|
+
def bootstrap_vm
|
262
|
+
unless fog_server
|
263
|
+
say "Booting #{flavor} inception server..."
|
264
|
+
@fog_server = @provider_client.bootstrap(fog_attributes)
|
265
|
+
provisioned["server_id"] = fog_server.id
|
266
|
+
provisioned["host"] = fog_server.dns_name || fog_server.public_ip_address
|
267
|
+
provisioned["username"] = fog_attributes[:username]
|
268
|
+
end
|
269
|
+
set_resource_name(fog_server, server_name)
|
270
|
+
end
|
271
|
+
|
272
|
+
def attach_persistent_disk
|
273
|
+
unless Fog.mocking?
|
274
|
+
Fog.wait_for(60) { fog_server.sshable?(ssh_options) }
|
275
|
+
end
|
276
|
+
|
277
|
+
unless volume = @provider_client.find_server_device(fog_server, external_disk_device)
|
278
|
+
say "Provisioning #{disk_size}Gb persistent disk for inception server..."
|
279
|
+
volume = @provider_client.create_and_attach_volume("Inception Disk", disk_size, fog_server, external_disk_device)
|
280
|
+
disk_devices["volume_id"] = volume.id
|
281
|
+
end
|
282
|
+
set_resource_name(volume, server_name)
|
283
|
+
end
|
284
|
+
|
285
|
+
def ssh_options
|
286
|
+
{
|
287
|
+
keys: [private_key_path]
|
288
|
+
}
|
289
|
+
end
|
290
|
+
|
291
|
+
# Poll a fog model until it terminates; print . each second
|
292
|
+
def wait_for_termination(fog_model, state_to_wait_for="terminated")
|
293
|
+
fog_model.wait_for do
|
294
|
+
print "."
|
295
|
+
state == state_to_wait_for
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
protected
|
300
|
+
# TODO emit events rather than writing directly to STDOUT
|
301
|
+
def say(*args)
|
302
|
+
puts(*args)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|