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,168 @@
|
|
1
|
+
# Copyright (c) 2012-2013 Stark & Wayne, LLC
|
2
|
+
|
3
|
+
module Bosh; module Providers; module Clients; end; end; end
|
4
|
+
|
5
|
+
require "bosh/providers/clients/fog_provider_client"
|
6
|
+
require "bosh/providers/constants/aws_constants"
|
7
|
+
|
8
|
+
class Bosh::Providers::Clients::AwsProviderClient < Bosh::Providers::Clients::FogProviderClient
|
9
|
+
include Bosh::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 raring_image_id(region=nil)
|
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
|
+
def bootstrap(new_attributes = {})
|
124
|
+
new_attributes[:image_id] ||= raring_image_id(fog_compute.region)
|
125
|
+
vpc = new_attributes[:subnet_id]
|
126
|
+
|
127
|
+
server = fog_compute.servers.new(new_attributes)
|
128
|
+
|
129
|
+
unless new_attributes[:key_name]
|
130
|
+
raise "please provide :key_name attribute"
|
131
|
+
end
|
132
|
+
unless private_key_path = new_attributes.delete(:private_key_path)
|
133
|
+
raise "please provide :private_key_path attribute"
|
134
|
+
end
|
135
|
+
|
136
|
+
if vpc
|
137
|
+
# TODO setup security group on new server
|
138
|
+
else
|
139
|
+
# make sure port 22 is open in the first security group
|
140
|
+
security_group = fog_compute.security_groups.get(server.groups.first)
|
141
|
+
authorized = security_group.ip_permissions.detect do |ip_permission|
|
142
|
+
ip_permission['ipRanges'].first && ip_permission['ipRanges'].first['cidrIp'] == '0.0.0.0/0' &&
|
143
|
+
ip_permission['fromPort'] == 22 &&
|
144
|
+
ip_permission['ipProtocol'] == 'tcp' &&
|
145
|
+
ip_permission['toPort'] == 22
|
146
|
+
end
|
147
|
+
unless authorized
|
148
|
+
security_group.authorize_port_range(22..22)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
server.save
|
153
|
+
unless Fog.mocking?
|
154
|
+
server.wait_for { ready? }
|
155
|
+
server.setup(:keys => [private_key_path])
|
156
|
+
end
|
157
|
+
server
|
158
|
+
end
|
159
|
+
|
160
|
+
# Construct a Fog::Compute object
|
161
|
+
# Uses +attributes+ which normally originates from +settings.provider+
|
162
|
+
def setup_fog_connection
|
163
|
+
configuration = Fog.symbolize_credentials(attributes.credentials)
|
164
|
+
configuration[:provider] = "AWS"
|
165
|
+
configuration[:region] = attributes.region
|
166
|
+
@fog_compute = Fog::Compute.new(configuration)
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# Copyright (c) 2012-2013 Stark & Wayne, LLC
|
2
|
+
|
3
|
+
require "fog"
|
4
|
+
module Bosh; module Providers; module Clients; end; end; end
|
5
|
+
|
6
|
+
class Bosh::Providers::Clients::FogProviderClient
|
7
|
+
attr_reader :fog_compute
|
8
|
+
attr_reader :attributes
|
9
|
+
|
10
|
+
def initialize(attributes)
|
11
|
+
@attributes = attributes.is_a?(Hash) ? Settingslogic.new(attributes) : attributes
|
12
|
+
raise "@attributes must be Settingslogic (or Hash)" unless @attributes.is_a?(Settingslogic)
|
13
|
+
setup_fog_connection
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup_fog_connection
|
17
|
+
raise "must implement"
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_key_pair(key_pair_name)
|
21
|
+
fog_compute.key_pairs.create(:name => key_pair_name)
|
22
|
+
end
|
23
|
+
|
24
|
+
# set_resource_name(fog_server, "inception")
|
25
|
+
# set_resource_name(volume, "inception-root")
|
26
|
+
# set_resource_name(volume, "inception-store")
|
27
|
+
def set_resource_name(resource, name)
|
28
|
+
fog_compute.tags.create :key => "Name", :value => name, :resource_id => resource.id
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete_key_pair_if_exists(key_pair_name)
|
32
|
+
if fog_key_pair = fog_compute.key_pairs.get(key_pair_name)
|
33
|
+
fog_key_pair.destroy
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete_servers_with_name(name)
|
38
|
+
fog_compute.servers.select {|s| s.tags["Name"].downcase == name.downcase }.each do |server|
|
39
|
+
puts "Destroying server #{server.id}..."
|
40
|
+
server.destroy
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def delete_volumes_with_name(name)
|
45
|
+
fog_compute.volumes.select do |v|
|
46
|
+
volume_name = v.tags["Name"]
|
47
|
+
volume_name && volume_name.downcase == name.downcase
|
48
|
+
end.each do |volume|
|
49
|
+
puts "Destroying volume #{volume.id}..."
|
50
|
+
volume.destroy
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Destroy all IP addresses that aren't bound to a server
|
55
|
+
def cleanup_unused_ip_addresses
|
56
|
+
fog_compute.addresses.each do |a|
|
57
|
+
unless a.server
|
58
|
+
puts "Deleting unused IP address #{a.public_ip}..."
|
59
|
+
a.destroy
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Creates or reuses an security group and opens ports.
|
65
|
+
#
|
66
|
+
# +security_group_name+ is the name to be created or reused
|
67
|
+
# +ports+ is a hash of name/port for ports to open, for example:
|
68
|
+
# {
|
69
|
+
# ssh: 22,
|
70
|
+
# http: 80,
|
71
|
+
# https: 443
|
72
|
+
# }
|
73
|
+
# protocol defaults to TCP
|
74
|
+
# You can also use a more verbose +ports+ using the format:
|
75
|
+
# {
|
76
|
+
# ssh: 22,
|
77
|
+
# http: { ports: (80..82) },
|
78
|
+
# mosh: { protocol: "udp", ports: (60000..60050) }
|
79
|
+
# mosh: { protocol: "rdp", ports: (3398..3398), ip_ranges: [ { cidrIp: "196.212.12.34/32" } ] }
|
80
|
+
# }
|
81
|
+
# In this example,
|
82
|
+
# * TCP 22 will be opened for ssh from any ip_range,
|
83
|
+
# * TCP ports 80, 81, 82 for http from any ip_range,
|
84
|
+
# * UDP 60000 -> 60050 for mosh from any ip_range and
|
85
|
+
# * TCP 3398 for RDP from ip range: 96.212.12.34/32
|
86
|
+
def create_security_group(security_group_name, description, ports)
|
87
|
+
security_groups = fog_compute.security_groups
|
88
|
+
unless sg = security_groups.find { |s| s.name == security_group_name }
|
89
|
+
sg = fog_compute.security_groups.create(name: security_group_name, description: description)
|
90
|
+
puts "Created security group #{security_group_name}"
|
91
|
+
else
|
92
|
+
puts "Reusing security group #{security_group_name}"
|
93
|
+
end
|
94
|
+
ip_permissions = ip_permissions(sg)
|
95
|
+
ports_opened = 0
|
96
|
+
ports.each do |name, port_defn|
|
97
|
+
(protocol, port_range, ip_range) = extract_port_definition(port_defn)
|
98
|
+
unless port_open?(ip_permissions, port_range, protocol, ip_range)
|
99
|
+
authorize_port_range(sg, port_range, protocol, ip_range)
|
100
|
+
puts " -> opened #{name} ports #{protocol.upcase} #{port_range.min}..#{port_range.max} from IP range #{ip_range}"
|
101
|
+
ports_opened += 1
|
102
|
+
end
|
103
|
+
end
|
104
|
+
puts " -> no additional ports opened" if ports_opened == 0
|
105
|
+
true
|
106
|
+
end
|
107
|
+
|
108
|
+
def port_open?(ip_permissions, port_range, protocol, ip_range)
|
109
|
+
ip_permissions && ip_permissions.find do |ip|
|
110
|
+
ip["ipProtocol"] == protocol \
|
111
|
+
&& ip["ipRanges"].detect { |range| range["cidrIp"] == ip_range } \
|
112
|
+
&& ip["fromPort"] <= port_range.min \
|
113
|
+
&& ip["toPort"] >= port_range.max
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def authorize_port_range(sg, port_range, protocol, ip_range)
|
118
|
+
sg.authorize_port_range(port_range, {:ip_protocol => protocol, :cidr_ip => ip_range})
|
119
|
+
end
|
120
|
+
|
121
|
+
def ip_permissions(sg)
|
122
|
+
sg.ip_permissions
|
123
|
+
end
|
124
|
+
|
125
|
+
# Any of the following +port_defn+ can be used:
|
126
|
+
# {
|
127
|
+
# ssh: 22,
|
128
|
+
# http: { ports: (80..82) },
|
129
|
+
# mosh: { protocol: "udp", ports: (60000..60050) }
|
130
|
+
# mosh: { protocol: "rdp", ports: (3398..3398), ip_range: "196.212.12.34/32" }
|
131
|
+
# }
|
132
|
+
# In this example,
|
133
|
+
# * TCP 22 will be opened for ssh from any ip_range,
|
134
|
+
# * TCP ports 80, 81, 82 for http from any ip_range,
|
135
|
+
# * UDP 60000 -> 60050 for mosh from any ip_range and
|
136
|
+
# * TCP 3398 for RDP from ip range: 96.212.12.34/32
|
137
|
+
def extract_port_definition(port_defn)
|
138
|
+
protocol = "tcp"
|
139
|
+
ip_range = "0.0.0.0/0"
|
140
|
+
if port_defn.is_a? Integer
|
141
|
+
port_range = (port_defn..port_defn)
|
142
|
+
elsif port_defn.is_a? Range
|
143
|
+
port_range = port_defn
|
144
|
+
elsif port_defn.is_a? Hash
|
145
|
+
protocol = port_defn[:protocol] if port_defn[:protocol]
|
146
|
+
port_range = port_defn[:ports] if port_defn[:ports]
|
147
|
+
ip_range = port_defn[:ip_range] if port_defn[:ip_range]
|
148
|
+
end
|
149
|
+
[protocol, port_range, ip_range]
|
150
|
+
end
|
151
|
+
|
152
|
+
def provision_or_reuse_public_ip_address(options={})
|
153
|
+
provision_public_ip_address(options) || find_unused_public_ip_address(options)
|
154
|
+
end
|
155
|
+
|
156
|
+
def find_unused_public_ip_address(options={})
|
157
|
+
if address = fog_compute.addresses.find { |s| s.server_id.nil? }
|
158
|
+
address.public_ip
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# Copyright (c) 2012-2013 Stark & Wayne, LLC
|
2
|
+
|
3
|
+
module Bosh; module Providers; module Clients; end; end; end
|
4
|
+
|
5
|
+
require "bosh/providers/clients/fog_provider_client"
|
6
|
+
require "bosh/providers/constants/openstack_constants"
|
7
|
+
|
8
|
+
class Bosh::Providers::Clients::OpenStackProviderClient < Bosh::Providers::Clients::FogProviderClient
|
9
|
+
# @return [String] provisions a new public IP address in target region
|
10
|
+
# TODO nil if none available
|
11
|
+
def provision_public_ip_address(options={})
|
12
|
+
address = fog_compute.addresses.create
|
13
|
+
address.ip
|
14
|
+
# TODO catch error and return nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def associate_ip_address_with_server(ip_address, server)
|
18
|
+
address = fog_compute.addresses.find { |a| a.ip == ip_address }
|
19
|
+
address.server = server
|
20
|
+
end
|
21
|
+
|
22
|
+
# Hook method for FogProviderClient#create_security_group
|
23
|
+
def ip_permissions(sg)
|
24
|
+
sg.rules
|
25
|
+
end
|
26
|
+
|
27
|
+
# Hook method for FogProviderClient#create_security_group
|
28
|
+
def authorize_port_range(sg, port_range, protocol, ip_range)
|
29
|
+
sg.create_security_group_rule(port_range.min, port_range.max, protocol, ip_range)
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_server_device(server, device)
|
33
|
+
va = fog_compute.get_server_volumes(server.id).body['volumeAttachments']
|
34
|
+
va.find { |v| v["device"] == device }
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_and_attach_volume(name, disk_size, server, device)
|
38
|
+
volume = fog_compute.volumes.create(:name => name,
|
39
|
+
:description => "",
|
40
|
+
:size => disk_size,
|
41
|
+
:availability_zone => server.availability_zone)
|
42
|
+
volume.wait_for { volume.status == 'available' }
|
43
|
+
volume.attach(server.id, device)
|
44
|
+
volume.wait_for { volume.status == 'in-use' }
|
45
|
+
end
|
46
|
+
|
47
|
+
def delete_security_group_and_servers(sg_name)
|
48
|
+
raise "not implemented yet"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Construct a Fog::Compute object
|
52
|
+
# Uses +attributes+ which normally originates from +settings.provider+
|
53
|
+
def setup_fog_connection
|
54
|
+
configuration = Fog.symbolize_credentials(attributes.credentials)
|
55
|
+
configuration[:provider] = "OpenStack"
|
56
|
+
unless attributes.region == openstack_constants.no_region_code
|
57
|
+
configuration[:openstack_region] = attributes.region
|
58
|
+
end
|
59
|
+
@fog_compute = Fog::Compute.new(configuration)
|
60
|
+
end
|
61
|
+
|
62
|
+
def openstack_constants
|
63
|
+
Bosh::Providers::Constants::OpenStackConstants
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Copyright (c) 2012-2013 Stark & Wayne, LLC
|
2
|
+
|
3
|
+
module Bosh; module Providers; module Constants; end; end; end
|
4
|
+
|
5
|
+
module Bosh::Providers::Constants::AwsConstants
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# http://docs.aws.amazon.com/general/latest/gr/rande.html#region
|
9
|
+
def region_labels
|
10
|
+
[
|
11
|
+
{ label: "US East (Northern Virginia) Region", code: "us-east-1" },
|
12
|
+
{ label: "US West (Oregon) Region", code: "us-west-2" },
|
13
|
+
{ label: "US West (Northern California) Region", code: "us-west-1" },
|
14
|
+
{ label: "EU (Ireland) Region", code: "eu-west-1" },
|
15
|
+
{ label: "Asia Pacific (Singapore) Region", code: "ap-southeast-1" },
|
16
|
+
{ label: "Asia Pacific (Sydney) Region", code: "ap-southeast-2" },
|
17
|
+
{ label: "Asia Pacific (Tokyo) Region", code: "ap-northeast-1" },
|
18
|
+
{ label: "South America (Sao Paulo) Region", code: "sa-east-1" },
|
19
|
+
]
|
20
|
+
end
|
21
|
+
|
22
|
+
def default_region_code
|
23
|
+
"us-east-1"
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Copyright (c) 2012-2013 Stark & Wayne, LLC
|
2
|
+
|
3
|
+
module Bosh; module Providers; module Constants; end; end; end
|
4
|
+
|
5
|
+
module Bosh::Providers::Constants::OpenStackConstants
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# explicit value representing "no region requested"
|
9
|
+
def no_region_code
|
10
|
+
"no-region-requested"
|
11
|
+
end
|
12
|
+
end
|
data/lib/inception.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "highline"
|
3
|
+
require "fileutils"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
# for the #sh helper
|
7
|
+
require "rake"
|
8
|
+
require "rake/file_utils"
|
9
|
+
|
10
|
+
require "escape"
|
11
|
+
require "inception/cli_helpers/display"
|
12
|
+
require "inception/cli_helpers/infrastructure"
|
13
|
+
require "inception/cli_helpers/interactions"
|
14
|
+
require "inception/cli_helpers/provider"
|
15
|
+
require "inception/cli_helpers/settings"
|
16
|
+
require "inception/cli_helpers/prepare_deploy_settings"
|
17
|
+
|
18
|
+
module Inception
|
19
|
+
class Cli < Thor
|
20
|
+
include FileUtils
|
21
|
+
include Inception::CliHelpers::Display
|
22
|
+
include Inception::CliHelpers::Infrastructure
|
23
|
+
include Inception::CliHelpers::Interactions
|
24
|
+
include Inception::CliHelpers::Provider
|
25
|
+
include Inception::CliHelpers::Settings
|
26
|
+
include Inception::CliHelpers::PrepareDeploySettings
|
27
|
+
|
28
|
+
desc "deploy", "Create/upgrade a Bosh inception server"
|
29
|
+
def deploy
|
30
|
+
migrate_old_settings
|
31
|
+
configure_provider
|
32
|
+
prepare_deploy_settings
|
33
|
+
perform_deploy
|
34
|
+
converge_cookbooks
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "delete", "Destroy target Bosh inception server, volumes & release the IP address"
|
38
|
+
method_option :"non-interactive", aliases: ["-n"], type: :boolean, desc: "Don't ask questions, just get crankin'"
|
39
|
+
def delete
|
40
|
+
migrate_old_settings
|
41
|
+
perform_delete(options[:"non-interactive"])
|
42
|
+
end
|
43
|
+
|
44
|
+
desc "ssh [COMMAND]", "Open an ssh session to the inception server [do nothing if local machine is the inception server]"
|
45
|
+
long_desc <<-DESC
|
46
|
+
If a command is supplied, it will be run, otherwise a session will be opened.
|
47
|
+
DESC
|
48
|
+
def ssh(cmd=nil)
|
49
|
+
migrate_old_settings
|
50
|
+
run_ssh_command_or_open_tunnel(cmd)
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "tmux", "Open an ssh (with tmux) session to the inception server [do nothing if local machine is inception server]"
|
54
|
+
long_desc <<-DESC
|
55
|
+
Opens a connection using ssh and attaches to the most recent tmux session;
|
56
|
+
giving you persistance across disconnects.
|
57
|
+
DESC
|
58
|
+
def tmux
|
59
|
+
migrate_old_settings
|
60
|
+
run_ssh_command_or_open_tunnel(["-t", "tmux attach || tmux new-session"])
|
61
|
+
end
|
62
|
+
|
63
|
+
no_tasks do
|
64
|
+
# update settings.git.name/git.email from local ~/.gitconfig if available
|
65
|
+
# provision public IP address for inception server if not allocated one
|
66
|
+
# Note: helper methods are in inception/cli_helpers/prepare_deploy_settings.rb
|
67
|
+
def prepare_deploy_settings
|
68
|
+
header "Preparing deployment settings"
|
69
|
+
update_git_config
|
70
|
+
provision_or_reuse_public_ip_address_for_inception unless settings.exists?("inception.provisioned.ip_address")
|
71
|
+
recreate_key_pair_for_inception unless settings.exists?("inception.key_pair.private_key")
|
72
|
+
recreate_private_key_file_for_inception
|
73
|
+
validate_deploy_settings
|
74
|
+
setup_next_deploy_actions
|
75
|
+
end
|
76
|
+
|
77
|
+
def perform_deploy
|
78
|
+
header "Provision inception server"
|
79
|
+
server = InceptionServer.new(provider_client, settings.inception, settings_ssh_dir)
|
80
|
+
server.create
|
81
|
+
ensure
|
82
|
+
# after any error handling, still save the current InceptionServer state back into settings.inception
|
83
|
+
settings["inception"] = server.export_attributes
|
84
|
+
save_settings!
|
85
|
+
end
|
86
|
+
|
87
|
+
def setup_next_deploy_actions
|
88
|
+
settings["next_deploy_actions"] ||= {}
|
89
|
+
@next_deploy_actions = NextDeployActions.new(settings.next_deploy_actions, options)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Perform converge chef cookbooks upon inception server
|
93
|
+
# Does not update settings
|
94
|
+
def converge_cookbooks
|
95
|
+
if @next_deploy_actions.skip_chef_converge?
|
96
|
+
header "Prepare inception server", skip: "Requested to be skipped on this deploy."
|
97
|
+
else
|
98
|
+
header "Prepare inception server"
|
99
|
+
server = InceptionServer.new(provider_client, settings.inception, settings_ssh_dir)
|
100
|
+
cookbook = InceptionServerCookbook.new(server, settings, settings_dir)
|
101
|
+
cookbook.prepare
|
102
|
+
settings.set("cookbook.prepared", true)
|
103
|
+
save_settings!
|
104
|
+
cookbook.converge
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def perform_delete(non_interactive)
|
109
|
+
server = InceptionServer.new(provider_client, settings.inception, settings_ssh_dir)
|
110
|
+
if non_interactive
|
111
|
+
header "Deleting inception server, volumes and releasing IP address"
|
112
|
+
server.delete_all
|
113
|
+
else
|
114
|
+
raise "Interactive delete not implemented yet"
|
115
|
+
end
|
116
|
+
ensure
|
117
|
+
# after any error handling, still save the current InceptionServer state back into settings.inception
|
118
|
+
settings["inception"] = server.export_attributes
|
119
|
+
save_settings!
|
120
|
+
end
|
121
|
+
|
122
|
+
def run_ssh_command_or_open_tunnel(cmd)
|
123
|
+
recreate_private_key_file_for_inception
|
124
|
+
unless settings.exists?("inception.provisioned.host")
|
125
|
+
exit "inception server has not finished launching; run to complete: inception deploy"
|
126
|
+
end
|
127
|
+
|
128
|
+
server = InceptionServer.new(provider_client, settings.inception, settings_ssh_dir)
|
129
|
+
username = settings.inception.provisioned.username
|
130
|
+
host = settings.inception.provisioned.host
|
131
|
+
result = system Escape.shell_command(["ssh", "-i", server.private_key_path, "#{username}@#{host}", cmd].flatten.compact)
|
132
|
+
exit result
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|