inception 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/.chef/knife.rb +4 -0
  2. data/.gitignore +20 -0
  3. data/.kitchen.yml +42 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +23 -0
  6. data/Berksfile +8 -0
  7. data/Berksfile.lock +9 -0
  8. data/Gemfile +18 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +139 -0
  11. data/Rakefile +66 -0
  12. data/TODO.md +26 -0
  13. data/bin/bosh-inception +8 -0
  14. data/config/ssh/kitchen-aws +23 -0
  15. data/cookbooks/bosh_inception/README.md +15 -0
  16. data/cookbooks/bosh_inception/attributes/default.rb +20 -0
  17. data/cookbooks/bosh_inception/files/default/Gemfile.cf +4 -0
  18. data/cookbooks/bosh_inception/files/default/Gemfile.micro +5 -0
  19. data/cookbooks/bosh_inception/metadata.rb +32 -0
  20. data/cookbooks/bosh_inception/recipes/default.rb +15 -0
  21. data/cookbooks/bosh_inception/recipes/install_bosh.rb +37 -0
  22. data/cookbooks/bosh_inception/recipes/install_ruby.rb +10 -0
  23. data/cookbooks/bosh_inception/recipes/mount_store_volume.rb +24 -0
  24. data/cookbooks/bosh_inception/recipes/packages.rb +23 -0
  25. data/cookbooks/bosh_inception/recipes/setup_git.rb +34 -0
  26. data/cookbooks/bosh_inception/recipes/useful_dirs.rb +13 -0
  27. data/inception.gemspec +42 -0
  28. data/lib/bosh/providers.rb +41 -0
  29. data/lib/bosh/providers/README.md +5 -0
  30. data/lib/bosh/providers/cli/aws_provider_cli.rb +58 -0
  31. data/lib/bosh/providers/cli/openstack_provider_cli.rb +47 -0
  32. data/lib/bosh/providers/cli/provider_cli.rb +17 -0
  33. data/lib/bosh/providers/clients/aws_provider_client.rb +168 -0
  34. data/lib/bosh/providers/clients/fog_provider_client.rb +161 -0
  35. data/lib/bosh/providers/clients/openstack_provider_client.rb +65 -0
  36. data/lib/bosh/providers/constants/aws_constants.rb +25 -0
  37. data/lib/bosh/providers/constants/openstack_constants.rb +12 -0
  38. data/lib/inception.rb +9 -0
  39. data/lib/inception/cli.rb +136 -0
  40. data/lib/inception/cli_helpers/display.rb +26 -0
  41. data/lib/inception/cli_helpers/infrastructure.rb +157 -0
  42. data/lib/inception/cli_helpers/interactions.rb +15 -0
  43. data/lib/inception/cli_helpers/prepare_deploy_settings.rb +89 -0
  44. data/lib/inception/cli_helpers/provider.rb +14 -0
  45. data/lib/inception/cli_helpers/settings.rb +47 -0
  46. data/lib/inception/inception_server.rb +305 -0
  47. data/lib/inception/inception_server_cookbook.rb +89 -0
  48. data/lib/inception/next_deploy_actions.rb +20 -0
  49. data/lib/inception/version.rb +3 -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 +39 -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/bosh/providers/aws_spec.rb +199 -0
  63. data/spec/unit/cli_delete_spec.rb +39 -0
  64. data/spec/unit/cli_deploy_aws_spec.rb +84 -0
  65. data/spec/unit/cli_ssh_spec.rb +82 -0
  66. data/spec/unit/inception_server_cookbook_spec.rb +61 -0
  67. data/spec/unit/inception_server_spec.rb +58 -0
  68. data/test/integration/default/bats/discover_user.bash +2 -0
  69. data/test/integration/default/bats/install_ruby.bats +8 -0
  70. data/test/integration/default/bats/useful_dirs.bats +8 -0
  71. data/test/integration/default/bats/user.bats +9 -0
  72. data/test/integration/default/bats/verify_bosh.bats +13 -0
  73. data/test/integration/default/bats/verify_git.bats +18 -0
  74. 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
@@ -0,0 +1,9 @@
1
+ require "inception/version"
2
+
3
+ module Inception
4
+ end
5
+
6
+ require "bosh/providers"
7
+ require "inception/inception_server"
8
+ require "inception/inception_server_cookbook"
9
+ require "inception/next_deploy_actions"
@@ -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