amoeba_deploy_tools 0.0.1 → 0.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4a65c50aee5219d621d7c8b828cfb969e3f28983
4
- data.tar.gz: eb296c2a985c9e907ce16c9579363b21dbb12607
3
+ metadata.gz: 34b12c26a76d623203a1e6ec7b139d49c3296f9f
4
+ data.tar.gz: 9e27589f78df53ab436c5282a9e4660eca3acf9f
5
5
  SHA512:
6
- metadata.gz: 85f437f9e3c7b0f6e4de987fe7ba8f9910453c769fd23448059989463afa22685c2b042c02964660da88f8f58c627fef3b0fb7ffcddca79eb444b948cf497f5d
7
- data.tar.gz: ca7f024bebd8cbc71fc1dfd15242743ce56f6381aeb58496f402d3c2451b11d36a40be3412f8a95a6ab367e6cbd21f7e31ff5e019e362df6e8e6e2e3ed02c889
6
+ metadata.gz: 8065541a517c22f166f98f752d2f87af8629f2f9722da2b1f885be41a943607f38cb039d5bd7aae35ffb3a8125b37647cf712be5eecdb0443cbe5b2d5e7c7951
7
+ data.tar.gz: 02b8bed43ff7194127257da289b2b292130e6d2fee4d4636d6e9b35ae75038f1ae4e4d2cf9a58bc4ff4aeb9177d868bc3b3392642e4780b1756bc4209b4faae2
data/.gitignore ADDED
@@ -0,0 +1,34 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+
20
+ # Project files
21
+ .idea
22
+
23
+ # For librarian
24
+ /cookbooks
25
+ /tmp
26
+ *.swp
27
+
28
+ # RVM stuff we'll ignore for this gem for max flexibility
29
+ .ruby-version
30
+ .ruby-gemset
31
+ .rvmrc
32
+
33
+ # Ignore Gemfile.lock in this since it's a gem
34
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format doc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your dependencies in gemspec file
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Amoeba Consulting
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ Amoeba Deploy Tools
2
+ ===================
3
+
4
+ Amoeba Deploy Tools (ADT) is a ruby gem that enables rapid creation of servers using the Chef config
5
+ management system. Using Chef today there are many tools and best practices, and we have found that
6
+ often setting up a chef "kitchen" can be tedious. Additionally, we believe in supporting server-less
7
+ deploys. That is, we don't like to maintain and run a separate chef server just to manage our boxes.
8
+
9
+ ADT integrates a number of other tools (`chef-solo`, and `librarian`, and others) and we provide a
10
+ [skeleton Kitchen](http://github.com/AmoebaConsulting/amoeba-kitchen-skel), so you can fork our
11
+ kitchen, install this gem, and deploy a node in minutes.
12
+
13
+ ## Short introduction
14
+
15
+ First, you need ruby installed. Next, add `amoeba-deploy-tools` to your project Gemfile, or install
16
+ via the `gem` command:
17
+
18
+ > gem install amoeba-deploy-tools
19
+
20
+ If you are preparing a server to install an application, change to that project's directory.
21
+ Otherwise, switch to a dir where you plan on running ADT from. And run:
22
+
23
+ > amoeba init
24
+
25
+ The command will walk you through the process of initializing your kitchen. Now you've got a
26
+ kitchen all ready to go. We recommend you add it to version control (git). We do not recommend you
27
+ add the .amoeba.yml configuration file to git.
28
+
29
+ Next you must create a node definition, and run a deploy. A node definition sits in the kitchen's
30
+ `nodes/` directory. See this directory for a sample node you can copy / rename to make your own.
31
+
32
+ Once you have a node defined, just run:
33
+
34
+ > amoeba node bootstrap --node <node-name>
35
+
36
+ ... and the node will be provisioned. After provisioning, the node's metadata will be stored in the
37
+ `data_bags` folder. Our skeleton kitchen includes a basenode recipe that creates a deployment user
38
+ on the destination box, and disables root access. This user's name will be cached in the node's
39
+ databag and used for subsequent operations to the box. Now, as you make changes to the node, or any
40
+ cookbooks you create in `site-cookbooks`, you can push those changes by running:
41
+
42
+ > amoeba node push --node <node-name>
43
+
44
+ Finally to setup Capistrano, you need only add the following to your existing Capfile:
45
+
46
+ require 'amoeba_deploy_tools/capistrano'
47
+
48
+ For a full list of commends, run `amoeba help` or see below.
49
+
50
+ ## Detailed Information
51
+
52
+ In essence, ADT is controlled by a configuration file, `.amoeba.yml`, specifying where your kitchen
53
+ will be located, and a copy of the kitchen itself. In the future, we will support specifying
54
+ deployment-related configuration in this file (such as SSH information). So we recommend you keep it
55
+ gitignored if it contains sensitive information. The kitchen should be kept under version control,
56
+ and you can either fork it prior to running `amoeba init` or you can let `amoeba init` make a copy
57
+ of our skeleton which you can add to version control later.
58
+
59
+ ## Vagrant Testing
60
+
61
+ Using Vagrant to test your nodes is easy! Just install Vagrant, run `amoeba init` and modify the
62
+ kitchen's `Vagrantfile`, as necessary. Run `vagrant up` and watch as your VM comes alive.
63
+
64
+ Then, you can ensure the provider is setup correctly (see `data_bags/providers/vagrant.json`). You
65
+ must ensure the SSH port matches that in `Vagrantfile` (by default 2222), and you must point the
66
+ provider to the SSH key Vagrant uses (if you just installed Vagrant, this is by default correct,
67
+ `~/.vagrant.d/insecure_private_key`). You can, however, change the private key (for security
68
+ reasons if you plan on distributing the VM or using it in production) by specifying an alternative
69
+ one in the `Vagrantfile` configuration key `config.ssh.private_key_path` (see
70
+ [the following documentation](http://docs-v1.vagrantup.com/v1/docs/config/ssh/private_key_path.html)
71
+ on Vagrant for more information).
72
+
73
+ ## Commands
74
+
75
+ ### amoeba init [url] [--skeleton]
76
+
77
+ The URL is optional, but if specified will be used as the library's git repo. You can also specify
78
+ `--skeleton`, which will use the specified URL as a skeleton, and make a copy of it (useful if you
79
+ do not already have a Kitchen you wish to use in git, but want to start a new one based on the URL
80
+ specified).
81
+
82
+ The default URL is [Amoeba's skeleton](https://github.com/AmoebaConsulting/amoeba-kitchen-skel),
83
+ which will be copied (as if `--skeleton` was specified).
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'amoeba_deploy_tools/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'amoeba_deploy_tools'
8
+ s.version = AmoebaDeployTools::VERSION
9
+ s.authors = ['Daniel Jabbour', 'Hike Danakian']
10
+ s.email = 'sayhi@amoe.ba'
11
+ s.summary = 'Easy Chef Solo / Knife operation with Amoeba Deploy Tools.'
12
+ s.description = 'Easy Chef Solo / Knife operation with Amoeba Deploy Tools.'
13
+ s.homepage = 'https://github.com/AmoebaConsulting/amoeba-deploy-tools'
14
+ s.license = 'MIT'
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.test_files = `git ls-files -- spec/*`.split("\n")
19
+ s.require_paths = ['lib']
20
+
21
+ s.required_ruby_version = '>= 1.9.2'
22
+
23
+ s.add_development_dependency 'bundler', '~> 1.3'
24
+ s.add_development_dependency 'rake'
25
+ s.add_development_dependency 'rspec', '~> 2.14.1'
26
+ s.add_development_dependency 'pry', '~> 0.9.12.4'
27
+
28
+ s.add_dependency 'hashie', '~> 2.0.5'
29
+ s.add_dependency 'thor', '~> 0.18.1'
30
+ s.add_dependency 'cocaine', '~> 0.5.3'
31
+
32
+ s.add_dependency 'chef', '~> 11.8.0'
33
+ s.add_dependency 'librarian-chef', '~> 0.0.2'
34
+ s.add_dependency 'knife-solo', '~> 0.4.0'
35
+ s.add_dependency 'knife-solo_data_bag', '~> 0.4.0'
36
+ end
@@ -0,0 +1,5 @@
1
+ def _cset(name, *args, &block)
2
+ unless exists?(name)
3
+ set(name, *args, &block)
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ require 'amoeba_deploy_tools/capistrano/common'
2
+
3
+ Capistrano::Configuration.instance(:must_exist).load do
4
+ _cset(:database_yml_path){ "#{shared_path}/config/database.yml" }
5
+
6
+ namespace :amoeba_deploy_tools do
7
+ desc 'Link the shared/config files into the current/config dir.'
8
+ task :symlink_configs, :roles => :app do
9
+ run [ "cd #{latest_release}",
10
+ "ln -nfs #{database_yml_path} config/"
11
+ ].join(' && ')
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ require 'capistrano/version'
2
+
3
+ if defined?(Capistrano::VERSION) && Capistrano::VERSION >= '3.0'
4
+ raise 'We do not yet support Capistrano v3.0. Please downgrade, send us a pull request, or symlink database.yml yourself.'
5
+ else
6
+ require 'amoeba_deploy_tools/capistrano/recipes'
7
+
8
+ Capistrano::Configuration.instance(:must_exist).load do
9
+ after 'deploy:finalize_update', 'amoeba_deploy_tools:symlink_configs'
10
+ end
11
+ end
@@ -0,0 +1,105 @@
1
+ require 'thor'
2
+ require 'hashie/mash'
3
+
4
+ module AmoebaDeployTools
5
+ class Command < Thor
6
+
7
+ include AmoebaDeployTools::Concerns::Hooks
8
+
9
+ option :node, desc: 'name of the node you wish to operate on (set default in .amoeba.yml)'
10
+ option :'log-level', desc: 'log level to output (DEBUG, INFO, WARN (default), or ERROR)'
11
+ option :dry, type: :boolean, default: false, desc: 'Don\'t actually execute any chef commands (just show what would be run)'
12
+
13
+ # Note that all subcommands will inherit this class. This means any setup done in here
14
+ # will be duplicated if it runs at initialization (since the main command and subcommand are
15
+ # both evaluated at runtime). Thus, it's important not to put anything in the constructor.
16
+ # If you wish to setup any global state, do so in the Amoeba class initializer.
17
+ def initialize(args=[], options={}, config={})
18
+ super
19
+ end
20
+
21
+ no_commands do
22
+ def config
23
+ return @amoebaConfig if @amoebaConfig
24
+
25
+ @amoebaConfig = Config.load('.amoeba.yml')
26
+ end
27
+
28
+ def kitchen_path
29
+ return @kitchen if @kitchen
30
+
31
+ if config.kitchen_.path
32
+ @kitchen = config.kitchen.path
33
+ else
34
+ @kitchen = '.'
35
+ logger.warn 'Using local dir as kitchen path, no `.amoeba.yml` config found. Consider running `amoeba init`'
36
+ end
37
+
38
+ say_fatal "ERROR: Could not find amoeba kitchen: #{@kitchen}" unless Dir.exists? @kitchen
39
+
40
+ @kitchen
41
+ end
42
+
43
+ def inside_kitchen(&block)
44
+ Bundler.with_clean_env do
45
+ Dir.chdir(kitchen_path) { block.call }
46
+ end
47
+ end
48
+
49
+ # The node must be specified unless you set a default one. You can specify it with the
50
+ # `--node [name]` option, or by setting `[:node][:default]` in your `.amoeba.yml``
51
+ def node
52
+ return @node if @node
53
+
54
+ node_name = options[:node] || config.node_.default_
55
+ say_fatal 'ERROR: must specify --node or have a default node in your config file' unless node_name
56
+
57
+ inside_kitchen do
58
+ node_filename = File.expand_path(File.join('nodes', "#{node_name}.json"))
59
+ if node_name.nil? || !File.exists?(node_filename)
60
+ say_fatal "ERROR: Could not find node JSON file: #{node_filename}"
61
+ end
62
+
63
+ @node = Config.load(node_filename, format: :json)
64
+ @node.tap {|n| n.filename = node_filename } if @node
65
+ end
66
+ end
67
+
68
+ def remote_node
69
+ data_bag(:nodes)[node.name]
70
+ end
71
+
72
+ def data_bag(name)
73
+ DataBag.new(name, kitchen_path)
74
+ end
75
+
76
+ def deployment
77
+ return @deployment if @deployment
78
+
79
+ @deployment = Hashie::Mash.new
80
+ @deployment.deep_merge!(node.deployment) if node.deployment
81
+
82
+ provider = data_bag(:providers)[node.deployment.provider] if node.deployment_.provider
83
+
84
+ @deployment.deep_merge!(provider) if provider
85
+ @deployment.deep_merge!(remote_node.deployment) if remote_node.deployment
86
+ @deployment.deep_merge!(node.deployment)
87
+
88
+ return @deployment
89
+ end
90
+
91
+ def logger
92
+ Logger.instance
93
+ end
94
+
95
+ def validate_chef_id!(name)
96
+ say_fatal "You must specify a key name for your data bag id." unless name
97
+ unless name =~ /^[a-zA-Z0-9\_\-]+$/
98
+ say_fatal "Your data bag name must only contain alphanums, dashes, and underscores. `#{name}` is invalid!"
99
+ end
100
+ return true
101
+ end
102
+
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,118 @@
1
+ module AmoebaDeployTools
2
+
3
+ DEFAULT_SKELETON_REPO = 'https://github.com/AmoebaConsulting/amoeba-kitchen-skel.git'
4
+
5
+ class Amoeba < Command
6
+
7
+ # Any "global" setup can be done here, as the "amoeba" command will always be initialized
8
+ def initialize(args=[], options={}, config={})
9
+ super
10
+ setup_logger
11
+ setup_cocaine
12
+
13
+ # Fix JSON so it doesn't try to dereference json_class in JSON responses
14
+ # See http://blog.defunct.ca/2013/02/01/query-chef-server-api-from-ruby-script/
15
+ JSON.create_id = ''
16
+ end
17
+
18
+ desc 'init (url optional)', 'Setup Amoeba Deploy Tools (either by creating a new kitchen or locating an existing one)'
19
+ method_options :skeleton => :boolean
20
+ def init(url=nil)
21
+ # Store the user-specified URL if it exists
22
+ user_url = url if url
23
+
24
+ # Check if we're in a git repo
25
+ if system('git rev-parse > /dev/null 2>&1')
26
+ project_dir = %x{git rev-parse --show-toplevel}
27
+
28
+ default_kitchen_dir = File.expand_path("#{File.basename(project_dir).chop}-kitchen",
29
+ File.expand_path('..', project_dir))
30
+ else
31
+ default_kitchen_dir = File.expand_path('.', 'kitchen')
32
+ end
33
+
34
+ kitchen_dir = ask "Where should the new kitchen be located? (default: #{default_kitchen_dir})"
35
+ kitchen_dir = default_kitchen_dir if kitchen_dir.empty?
36
+
37
+ if File.exist?(kitchen_dir)
38
+ say 'Existing kitchen found! Will not overwrite.', :yellow
39
+ else
40
+ # Copy (not clone) the repo if the URL isn't specified. If it is, obey the --skeleton param
41
+ copy = url ? options[:skeleton] : true
42
+
43
+ # If there was no specified URL, use default one
44
+ url ||= DEFAULT_SKELETON_REPO
45
+
46
+ git_opts = copy ? '--depth 1' : ''
47
+ if system("git clone #{git_opts} #{url} #{kitchen_dir}")
48
+ if copy
49
+ git_dir = File.expand_path('.git', kitchen_dir)
50
+ if File.directory?(git_dir)
51
+ FileUtils.rm_rf(git_dir)
52
+ end
53
+ say_bold "New kitchen created at: #{kitchen_dir}. Please add it to version control"
54
+ else
55
+ say_bold "Kitchen from #{url} has been 'git clone'-ed into your kitchen directory"
56
+ end
57
+ else
58
+ say_fatal "ERROR: Kitchen directory cannot be cloned from URL #{url}"
59
+ end
60
+ end
61
+
62
+ # Okay, the kitchen exists (one way or another)
63
+
64
+ config.kitchen!.url = user_url if user_url && !options[:skeleton]
65
+ config.kitchen!.path = kitchen_dir.to_s
66
+ config.save
67
+
68
+ say_bold 'Saving ./amoeba.yml config file. We suggest you `git ignore` this (contains local settings).'
69
+ end
70
+
71
+ desc 'sync OPTS', 'Not yet implemented.'
72
+ def sync
73
+ end
74
+
75
+ desc 'update OPTS', 'Not yet implemented.'
76
+ def update
77
+ end
78
+
79
+ desc 'app [COMMAND]', 'Manage the deployed application (see `amoeba app help`)'
80
+ subcommand 'app', AmoebaDeployTools::App
81
+
82
+ desc 'node [COMMAND]', 'Deploy and configure nodes (see `amoeba node help`)'
83
+ subcommand 'node', AmoebaDeployTools::Node
84
+
85
+ desc 'key [COMMAND]', 'Manage private keys (see `amoeba key help`)'
86
+ subcommand 'key', AmoebaDeployTools::Key
87
+
88
+ desc 'ssl [COMMAND]', 'Manage SSL certificates (see `amoeba ssl help`)'
89
+ subcommand 'ssl', AmoebaDeployTools::Ssl
90
+
91
+ no_commands do
92
+ def setup_logger
93
+ # Default logging level is warn. You can change this in your .amoeba.yml config
94
+ # by setting `logLevel` or by passing --logLevel option
95
+ level = 'WARN'
96
+ level = config.log_level if config.log_level
97
+ level = options[:'log-level'] if options[:'log-level']
98
+ begin
99
+ level = AmoebaDeployTools::Logger.const_get level.upcase
100
+ rescue NameError
101
+ say "WARNING: Invalid log level: #{level}. Defaulting to WARN.", :red
102
+ level = AmoebaDeployTools::Logger::WARN
103
+ end
104
+ AmoebaDeployTools::Logger.instance.level = level
105
+ end
106
+
107
+ def setup_cocaine
108
+ if options[:dry]
109
+ Cocaine::CommandLine.runner = Cocaine::CommandLine::FakeRunner.new
110
+ else
111
+ Cocaine::CommandLine.runner = AmoebaDeployTools::NoiseyCocaineRunner.new
112
+ end
113
+
114
+ Cocaine::CommandLine.logger = AmoebaDeployTools::Logger.instance
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,27 @@
1
+ module AmoebaDeployTools
2
+ class App < Command
3
+ desc 'deploy', 'Deploy the application (using capistrano)'
4
+ def deploy
5
+ cap :deploy
6
+ end
7
+
8
+ desc 'capfile', 'Generate Capfile for application'
9
+ def capfile
10
+ app = node.application
11
+ sudo(node.name, "cat ~#{app.name}/shared/config/Capfile")
12
+ end
13
+
14
+ desc 'exec CMD', "executes CMD on remote server in app's env"
15
+ def exec
16
+ end
17
+
18
+ desc 'ssh', 'SSHs to the application node, as the application user'
19
+ def ssh
20
+ end
21
+
22
+ no_commands do
23
+ def cap(cmd)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,45 @@
1
+ module AmoebaDeployTools
2
+ module Concerns
3
+ module Hooks
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ ### Class methods
10
+ def before_hooks
11
+ @before_hooks ||= []
12
+ end
13
+
14
+ def after_hooks
15
+ @after_hooks ||= []
16
+ end
17
+
18
+ def before(&blk)
19
+ @before_hooks ||= []
20
+ @before_hooks << blk
21
+ end
22
+
23
+ def after(&blk)
24
+ @after_hooks ||= []
25
+ @after_hooks << blk
26
+ end
27
+ end
28
+
29
+ #### Instance methods
30
+
31
+ def invoke_command(command, *args)
32
+ # Ignore hooks on help commands
33
+ if command.name == 'help'
34
+ return super
35
+ end
36
+
37
+ self.class.before_hooks.each {|h| instance_eval &h }
38
+ retVal = super
39
+ self.class.after_hooks.each {|h| instance_eval &h }
40
+ return retVal
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,109 @@
1
+ module AmoebaDeployTools
2
+ module Concerns
3
+ module SSH
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ ### Class methods
10
+ end
11
+
12
+ ### Instance methods
13
+
14
+ # Outputs SSH options for connecting to this node (provide a map of deploy key to command
15
+ # line arg name).
16
+ def node_host_args(flag_map)
17
+ say_fatal 'ERROR: Missing deployment info for node.' unless deployment && deployment.host
18
+
19
+ host_arg = deployment.host
20
+ host_arg = "#{deployment.user}@#{host_arg}" if deployment.user
21
+
22
+ # Iterate through all the specified flags and check if they're defined in the deployment
23
+ # config, appending them to the output if they are.
24
+ flag_map.each do |field, argument_name|
25
+ host_arg << " #{argument_name} #{deployment[field]}" if deployment[field]
26
+ end
27
+
28
+ host_arg
29
+ end
30
+
31
+ # Run knife solo command on server
32
+ def knife_solo(cmd, options={})
33
+ say_fatal 'ERROR: Node must have a name defined' unless node.name
34
+
35
+ # Ensure json is a hashie
36
+ json = options.delete(:json) || Hashie::Mash.new
37
+
38
+ # If a block is specified, it means we have json in it, so let's resolve it
39
+ yield(json) if block_given?
40
+
41
+ # Ensure JSON for the node is set. It is either provided by the calling party, or we use the
42
+ # node file's definitions.
43
+ if json.empty?
44
+ json.deep_merge!(node)
45
+ end
46
+
47
+ exec = "bundle exec knife solo #{cmd.to_s} "
48
+
49
+ if options.delete(:ssh)
50
+ exec << node_host_args(port: '--ssh-port',
51
+ config: '--ssh-config-file',
52
+ ident: '--identity-file') << ' '
53
+ exec << "--no-host-key-verify --node-name #{node.name}"
54
+ end
55
+
56
+ if options.delete(:include_private_key)
57
+ private_key = node.private_key || 'default'
58
+ private_key = config.private_keys_[private_key]
59
+
60
+ # Stick the private key into a our custom JSON (to be appended to the node)
61
+ json.private_key_raw = private_key if private_key
62
+ end
63
+
64
+ opts = {}
65
+ opts[:runner] = AmoebaDeployTools::InteractiveCocaineRunner.new if options.delete(:interactive)
66
+
67
+ # Now go through all the options specified and append them to args
68
+ args = ''
69
+ options.each do |argument, value|
70
+ args << " --#{argument} #{value}"
71
+ end
72
+
73
+ inside_kitchen do
74
+ # JSON will be written to a temp file and used in place of the node JSON file
75
+ with_tmpfile(JSON.dump(json), name: ['node', '.json']) do |file_name|
76
+ knife_solo_cmd = Cocaine::CommandLine.new(exec, "#{args} #{file_name}", opts)
77
+ knife_solo_cmd.run
78
+ end
79
+ end
80
+ end
81
+
82
+ def ssh_run(cmd, options)
83
+ options = {
84
+ silent: false,
85
+ interactive: false
86
+ }.merge!(options)
87
+
88
+ opts = {}
89
+ opts[:runner] = Cocaine::CommandLine::BackticksRunner.new if options[:silent]
90
+ opts[:runner] = AmoebaDeployTools::InteractiveCocaineRunner.new if options[:interactive]
91
+
92
+ ssh_cmd = node_host_args(port: '-p', ident: '-i')
93
+
94
+ [ 'Compression=yes',
95
+ 'DSAAuthentication=yes',
96
+ 'LogLevel=FATAL',
97
+ 'StrictHostKeyChecking=no',
98
+ 'UserKnownHostsFile=/dev/null'
99
+ ].each do |opt|
100
+ ssh_cmd << " -o #{opt}"
101
+ end
102
+
103
+ ssh_cmd << " '#{cmd}'" if cmd && !cmd.empty?
104
+
105
+ Cocaine::CommandLine.new('ssh', ssh_cmd, opts).run
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,22 @@
1
+ module AmoebaDeployTools
2
+ class Key < Command
3
+
4
+ desc 'create', 'Create a private_key (used to encrypt secret data_bags like SSL certs)'
5
+ def create(name=nil)
6
+ validate_chef_id!(name)
7
+
8
+ key = Cocaine::CommandLine.new('openssl', "rand -base64 512 | tr -d '\\r\\n'",
9
+ runner: Cocaine::CommandLine::BackticksRunner.new).run
10
+ config.private_keys![name] = key
11
+
12
+ logger.debug "Saving key to `.amoeba.yml` config"
13
+
14
+ if config.new_file?
15
+ say_fatal "Cannot create new key, no .amoeba.yml file found! Please run `amoeba init`"
16
+ end
17
+
18
+ config.save
19
+ end
20
+
21
+ end
22
+ end