inception-server 0.2.1

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