inception 0.1.0
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.
- 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
|