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 +4 -4
- data/.gitignore +34 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +83 -0
- data/Rakefile +1 -0
- data/amoeba-deploy-tools.gemspec +36 -0
- data/lib/amoeba_deploy_tools/capistrano/common.rb +5 -0
- data/lib/amoeba_deploy_tools/capistrano/recipes.rb +14 -0
- data/lib/amoeba_deploy_tools/capistrano.rb +11 -0
- data/lib/amoeba_deploy_tools/command.rb +105 -0
- data/lib/amoeba_deploy_tools/commands/amoeba.rb +118 -0
- data/lib/amoeba_deploy_tools/commands/app.rb +27 -0
- data/lib/amoeba_deploy_tools/commands/concerns/hooks.rb +45 -0
- data/lib/amoeba_deploy_tools/commands/concerns/ssh.rb +109 -0
- data/lib/amoeba_deploy_tools/commands/key.rb +22 -0
- data/lib/amoeba_deploy_tools/commands/node.rb +119 -0
- data/lib/amoeba_deploy_tools/commands/ssl.rb +45 -0
- data/lib/amoeba_deploy_tools/config.rb +133 -0
- data/lib/amoeba_deploy_tools/data_bag.rb +30 -0
- data/lib/amoeba_deploy_tools/helpers.rb +51 -0
- data/lib/amoeba_deploy_tools/interactive_cocaine_runner.rb +27 -0
- data/lib/amoeba_deploy_tools/logger.rb +67 -0
- data/lib/amoeba_deploy_tools/noisey_cocaine_runner.rb +42 -0
- data/lib/amoeba_deploy_tools/version.rb +3 -0
- data/spec/amoeba_deploy_tools/command_spec.rb +4 -0
- data/spec/amoeba_deploy_tools/commands_spec.rb +91 -0
- data/spec/amoeba_deploy_tools/config_spec.rb +48 -0
- data/spec/amoeba_deploy_tools/helpers_spec.rb +18 -0
- data/spec/spec_helper.rb +21 -0
- metadata +37 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34b12c26a76d623203a1e6ec7b139d49c3296f9f
|
4
|
+
data.tar.gz: 9e27589f78df53ab436c5282a9e4660eca3acf9f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/Gemfile
ADDED
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,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
|