inception-server 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/.chef/knife.rb +4 -0
  2. data/.gitignore +21 -0
  3. data/.kitchen.yml +47 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +18 -0
  6. data/Berksfile +8 -0
  7. data/Berksfile.lock +9 -0
  8. data/ChangeLog.md +20 -0
  9. data/Gemfile +27 -0
  10. data/Guardfile +6 -0
  11. data/LICENSE.txt +22 -0
  12. data/README.md +126 -0
  13. data/Rakefile +66 -0
  14. data/TODO.md +25 -0
  15. data/bin/inception +8 -0
  16. data/bin/inception-server +8 -0
  17. data/config/ssh/kitchen-aws +23 -0
  18. data/cookbooks/bosh_inception/README.md +15 -0
  19. data/cookbooks/bosh_inception/attributes/default.rb +25 -0
  20. data/cookbooks/bosh_inception/files/default/Gemfile.cf +5 -0
  21. data/cookbooks/bosh_inception/files/default/Gemfile.micro +5 -0
  22. data/cookbooks/bosh_inception/metadata.rb +32 -0
  23. data/cookbooks/bosh_inception/recipes/default.rb +16 -0
  24. data/cookbooks/bosh_inception/recipes/install_bosh.rb +37 -0
  25. data/cookbooks/bosh_inception/recipes/install_ruby.rb +10 -0
  26. data/cookbooks/bosh_inception/recipes/mount_store_volume.rb +24 -0
  27. data/cookbooks/bosh_inception/recipes/packages.rb +23 -0
  28. data/cookbooks/bosh_inception/recipes/setup_dotfog.rb +29 -0
  29. data/cookbooks/bosh_inception/recipes/setup_git.rb +34 -0
  30. data/cookbooks/bosh_inception/recipes/useful_dirs.rb +13 -0
  31. data/inception-server.gemspec +43 -0
  32. data/lib/inception/cli.rb +141 -0
  33. data/lib/inception/cli_helpers/display.rb +26 -0
  34. data/lib/inception/cli_helpers/interactions.rb +15 -0
  35. data/lib/inception/cli_helpers/prepare_deploy_settings.rb +89 -0
  36. data/lib/inception/cli_helpers/provider.rb +14 -0
  37. data/lib/inception/cli_helpers/settings.rb +53 -0
  38. data/lib/inception/inception_server.rb +304 -0
  39. data/lib/inception/inception_server_cookbook.rb +90 -0
  40. data/lib/inception/next_deploy_actions.rb +20 -0
  41. data/lib/inception/providers/README.md +5 -0
  42. data/lib/inception/providers/clients/aws_provider_client.rb +144 -0
  43. data/lib/inception/providers/clients/fog_provider_client.rb +185 -0
  44. data/lib/inception/providers/clients/openstack_provider_client.rb +84 -0
  45. data/lib/inception/providers/constants/aws_constants.rb +25 -0
  46. data/lib/inception/providers/constants/openstack_constants.rb +12 -0
  47. data/lib/inception/providers.rb +28 -0
  48. data/lib/inception/version.rb +3 -0
  49. data/lib/inception.rb +9 -0
  50. data/nodes/.gitkeep +0 -0
  51. data/spec/assets/.gitkeep +0 -0
  52. data/spec/assets/gitconfig +5 -0
  53. data/spec/assets/settings/aws-before-server.yml +14 -0
  54. data/spec/assets/settings/aws-created-server.yml +31 -0
  55. data/spec/integration/.gitkeep +0 -0
  56. data/spec/integration/aws/aws_basic_spec.rb +38 -0
  57. data/spec/spec_helper.rb +50 -0
  58. data/spec/support/aws/aws_helpers.rb +73 -0
  59. data/spec/support/settings_helper.rb +20 -0
  60. data/spec/support/stdout_capture.rb +17 -0
  61. data/spec/unit/.gitkeep +0 -0
  62. data/spec/unit/cli_delete_spec.rb +39 -0
  63. data/spec/unit/cli_deploy_aws_spec.rb +83 -0
  64. data/spec/unit/cli_ssh_spec.rb +80 -0
  65. data/spec/unit/inception_server_cookbook_spec.rb +62 -0
  66. data/spec/unit/inception_server_spec.rb +58 -0
  67. data/spec/unit/providers/aws_spec.rb +198 -0
  68. data/test/integration/default/bats/discover_user.bash +2 -0
  69. data/test/integration/default/bats/dotfog.bats +11 -0
  70. data/test/integration/default/bats/install_ruby.bats +8 -0
  71. data/test/integration/default/bats/useful_dirs.bats +8 -0
  72. data/test/integration/default/bats/user.bats +9 -0
  73. data/test/integration/default/bats/verify_bosh.bats +18 -0
  74. data/test/integration/default/bats/verify_git.bats +18 -0
  75. metadata +361 -0
@@ -0,0 +1,29 @@
1
+ if node.fog.empty?
2
+ file "/home/#{node.user.username}/.fog" do
3
+ owner "root"
4
+ group "root"
5
+ mode "0755"
6
+ action :delete
7
+ end
8
+ else
9
+ ruby_block "create .fog file" do
10
+ block do
11
+ credentials = node.fog.inject({}) do |options, (key, value)|
12
+ options[(key.to_sym rescue key) || key] = value
13
+ options
14
+ end
15
+ fog_file = { default: credentials }
16
+ dotfog = File.expand_path("/home/#{node.user.username}/.fog")
17
+ File.open(dotfog, "w") do |f|
18
+ f << fog_file.to_yaml
19
+ end
20
+ end
21
+ end
22
+
23
+ file "/home/#{node.user.username}/.fog" do
24
+ owner node.user.username
25
+ group node.user.username
26
+ mode "0600"
27
+ action :touch
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ #
2
+ # Cookbook Name:: bosh_inception
3
+ # Recipe:: git_config
4
+ #
5
+ # Copyright (c) 2013 Dr Nic Williams, Stark & Wayne, LLC
6
+ #
7
+ # MIT License
8
+ #
9
+
10
+ include_recipe "hub"
11
+
12
+ execute "git config user.name" do
13
+ command "git config --global --replace-all user.name '#{node.git.name}'"
14
+ user node.user.username
15
+ group node.user.username
16
+ action :run
17
+ environment ({'HOME' => "/home/#{node.user.username}"})
18
+ end
19
+
20
+ execute "git config user.email" do
21
+ command "git config --global --replace-all user.email '#{node.git.email}'"
22
+ user node.user.username
23
+ group node.user.username
24
+ action :run
25
+ environment ({'HOME' => "/home/#{node.user.username}"})
26
+ end
27
+
28
+ execute "git config color.ui" do
29
+ command "git config --global color.ui true"
30
+ user node.user.username
31
+ group node.user.username
32
+ action :run
33
+ environment ({'HOME' => "/home/#{node.user.username}"})
34
+ end
@@ -0,0 +1,13 @@
1
+ %w[microboshes microboshes/deployments deployments releases repos stemcells systems tmp bosh_cache].each do |dir|
2
+ directory "/var/vcap/store/#{dir}" do
3
+ owner node.user.username
4
+ group node.user.username
5
+ mode "0755"
6
+ recursive true
7
+ action :create
8
+ end
9
+ end
10
+
11
+ link "/home/#{node.user.username}/.bosh_cache" do
12
+ to "/var/vcap/store/bosh_cache"
13
+ end
@@ -0,0 +1,43 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'inception/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "inception-server"
8
+ spec.version = Inception::VERSION
9
+ spec.authors = ["Dr Nic Williams"]
10
+ spec.email = ["drnicwilliams@gmail.com"]
11
+ spec.description = %q{Create an inception server for Bosh & general inception of new universes}
12
+ spec.summary = %q{CLI, with chef recipes, for creating and preparing an inception server for deploying/developing a Bosh universe.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "thor"
22
+ spec.add_dependency "highline"
23
+ spec.add_dependency "escape"
24
+ spec.add_dependency "json"
25
+ spec.add_dependency "readwritesettings", "~> 3.0"
26
+
27
+ # for inception/providers
28
+ spec.add_dependency "fog"
29
+ spec.add_dependency "cyoi" # choose your own infrastructure
30
+
31
+ # for running cookbooks on inception server
32
+ spec.add_dependency "knife-solo", "~> 0.3.0.pre4"
33
+
34
+ spec.add_development_dependency "bundler", "~> 1.3"
35
+ spec.add_development_dependency "rake"
36
+
37
+ # gems for the ruby unit & integration tests
38
+ spec.add_development_dependency "rspec"
39
+
40
+ # gems for the cookbook tests
41
+ spec.add_development_dependency "test-kitchen", "~> 1.0.0.alpha.6"
42
+ spec.add_development_dependency "berkshelf"
43
+ end
@@ -0,0 +1,141 @@
1
+ require "thor"
2
+ require "highline"
3
+ require "fileutils"
4
+ require "json"
5
+
6
+ # to prompt user for infrastructure choice/credentials
7
+ require "cyoi/cli/provider"
8
+
9
+ # for the #sh helper
10
+ require "rake"
11
+ require "rake/file_utils"
12
+
13
+ require "escape"
14
+ require "inception/cli_helpers/display"
15
+ require "inception/cli_helpers/interactions"
16
+ require "inception/cli_helpers/provider"
17
+ require "inception/cli_helpers/settings"
18
+ require "inception/cli_helpers/prepare_deploy_settings"
19
+
20
+ module Inception
21
+ class Cli < Thor
22
+ include FileUtils
23
+ include Inception::CliHelpers::Display
24
+ include Inception::CliHelpers::Interactions
25
+ include Inception::CliHelpers::Provider
26
+ include Inception::CliHelpers::Settings
27
+ include Inception::CliHelpers::PrepareDeploySettings
28
+
29
+ desc "deploy", "Create/upgrade a Bosh inception server"
30
+ def deploy
31
+ migrate_old_settings
32
+ configure_provider
33
+ prepare_deploy_settings
34
+ perform_deploy
35
+ converge_cookbooks
36
+ end
37
+
38
+ desc "delete", "Destroy target Bosh inception server, volumes & release the IP address"
39
+ # method_option :"non-interactive", aliases: ["-n"], type: :boolean, desc: "Don't ask questions, just get crankin'"
40
+ def delete
41
+ migrate_old_settings
42
+ perform_delete(options[:"non-interactive"])
43
+ end
44
+
45
+ desc "ssh [COMMAND]", "Open an ssh session to the inception server [do nothing if local machine is the inception server]"
46
+ long_desc <<-DESC
47
+ If a command is supplied, it will be run, otherwise a session will be opened.
48
+ DESC
49
+ def ssh(cmd=nil)
50
+ migrate_old_settings
51
+ run_ssh_command_or_open_tunnel(cmd)
52
+ end
53
+
54
+ desc "tmux", "Open an ssh (with tmux) session to the inception server [do nothing if local machine is inception server]"
55
+ long_desc <<-DESC
56
+ Opens a connection using ssh and attaches to the most recent tmux session;
57
+ giving you persistance across disconnects.
58
+ DESC
59
+ def tmux
60
+ migrate_old_settings
61
+ run_ssh_command_or_open_tunnel(["-t", "tmux attach || tmux new-session"])
62
+ end
63
+
64
+ no_tasks do
65
+ def configure_provider
66
+ save_settings!
67
+ provider_cli = Cyoi::Cli::Provider.new([settings_dir])
68
+ provider_cli.execute!
69
+ reload_settings!
70
+ end
71
+
72
+ # update settings.git.name/git.email from local ~/.gitconfig if available
73
+ # provision public IP address for inception server if not allocated one
74
+ # Note: helper methods are in inception/cli_helpers/prepare_deploy_settings.rb
75
+ def prepare_deploy_settings
76
+ header "Preparing deployment settings"
77
+ update_git_config
78
+ provision_or_reuse_public_ip_address_for_inception unless settings.exists?("inception.provisioned.ip_address")
79
+ recreate_key_pair_for_inception unless settings.exists?("inception.key_pair.private_key")
80
+ recreate_private_key_file_for_inception
81
+ validate_deploy_settings
82
+ setup_next_deploy_actions
83
+ end
84
+
85
+ def perform_deploy
86
+ header "Provision inception server"
87
+ server = InceptionServer.new(provider_client, settings.inception, settings_ssh_dir)
88
+ server.create
89
+ ensure
90
+ # after any error handling, still save the current InceptionServer state back into settings.inception
91
+ settings["inception"] = server.export_attributes
92
+ save_settings!
93
+ end
94
+
95
+ def setup_next_deploy_actions
96
+ settings["next_deploy_actions"] ||= {}
97
+ @next_deploy_actions = NextDeployActions.new(settings.next_deploy_actions, options)
98
+ end
99
+
100
+ # Perform converge chef cookbooks upon inception server
101
+ # Does not update settings
102
+ def converge_cookbooks
103
+ if @next_deploy_actions.skip_chef_converge?
104
+ header "Prepare inception server", skip: "Requested to be skipped on this deploy."
105
+ else
106
+ header "Prepare inception server"
107
+ server = InceptionServer.new(provider_client, settings.inception, settings_ssh_dir)
108
+ cookbook = InceptionServerCookbook.new(server, settings, settings_dir)
109
+ cookbook.prepare
110
+ settings.set("cookbook.prepared", true)
111
+ save_settings!
112
+ cookbook.converge
113
+ end
114
+ end
115
+
116
+ def perform_delete(non_interactive)
117
+ server = InceptionServer.new(provider_client, settings.inception, settings_ssh_dir)
118
+ header "Deleting inception server, volumes and releasing IP address"
119
+ server.delete_all
120
+ ensure
121
+ # after any error handling, still save the current InceptionServer state back into settings.inception
122
+ settings["inception"] = server.export_attributes
123
+ settings.delete("cookbook")
124
+ save_settings!
125
+ end
126
+
127
+ def run_ssh_command_or_open_tunnel(cmd)
128
+ recreate_private_key_file_for_inception
129
+ unless settings.exists?("inception.provisioned.host")
130
+ exit "inception server has not finished launching; run to complete: inception deploy"
131
+ end
132
+
133
+ server = InceptionServer.new(provider_client, settings.inception, settings_ssh_dir)
134
+ username = settings.inception.provisioned.username
135
+ host = settings.inception.provisioned.host
136
+ result = system Escape.shell_command(["ssh", "-i", server.private_key_path, "#{username}@#{host}", cmd].flatten.compact)
137
+ exit result
138
+ end
139
+ end
140
+ end
141
+ end
@@ -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,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.public_key", key_pair.public_key) # might be provided
43
+ settings.set("inception.key_pair.fingerprint", key_pair.fingerprint)
44
+ save_settings!
45
+ end
46
+
47
+ def private_key_path_for_inception
48
+ @private_key_path_for_inception ||= File.join(settings_dir, "ssh", settings.inception.key_pair.name)
49
+ end
50
+
51
+ # The keys for the inception server originate from the provider and are cached in
52
+ # the manifest. The private key is stored locally; the public key is placed
53
+ # on the inception server.
54
+ def recreate_private_key_file_for_inception
55
+ mkdir_p(File.dirname(private_key_path_for_inception))
56
+ File.chmod(0700, File.dirname(private_key_path_for_inception))
57
+ File.open(private_key_path_for_inception, "w") { |file| file << settings.inception.key_pair.private_key }
58
+ File.chmod(0600, private_key_path_for_inception)
59
+ end
60
+
61
+
62
+ # Required settings:
63
+ # * git.name
64
+ # * git.email
65
+ def validate_deploy_settings
66
+ begin
67
+ settings.git.name
68
+ settings.git.email
69
+ rescue ReadWriteSettings::MissingSetting => e
70
+ error "Please setup local git user.name & user.email config; or specify git.name & git.email in settings.yml"
71
+ end
72
+
73
+ begin
74
+ settings.provider.name
75
+ settings.provider.credentials
76
+ rescue ReadWriteSettings::MissingSetting => e
77
+ error "Wooh there, we need provider.name & 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 ReadWriteSettings::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
+ Inception::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,53 @@
1
+ require "readwritesettings"
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 ~/.inception_server; 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"] || "~/.inception_server")
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) if File.directory?(settings_ssh_dir)
32
+ ReadWriteSettings.new(settings_path)
33
+ end
34
+ end
35
+
36
+ # Saves current nested ReadWriteSettings into pure Hash-based YAML file
37
+ # Recreates accessors on ReadWriteSettings 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 reload_settings!
44
+ @settings = nil
45
+ settings
46
+ end
47
+
48
+ def migrate_old_settings
49
+ settings
50
+ end
51
+
52
+ end
53
+ end