joyent-provisioner 0.1.0

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.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .idea/
7
+ .pairs
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ script: "bundle exec rspec"
5
+ notifications:
6
+ email: false
7
+
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in provisioner.gemspec
4
+ gemspec
5
+
data/Guardfile ADDED
@@ -0,0 +1,8 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| puts m.inspect ; "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 TODO: Write your name
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,130 @@
1
+ # Joyent Cloud Provisioner
2
+
3
+ [![Build status](https://secure.travis-ci.org/wanelo/joyent-provisioner.png)](http://travis-ci.org/wanelo/joyent-provisioner)
4
+
5
+ This gem provides a ruby class and a command line tool to simplify provisioning of clusters of hosts in the
6
+ cloud environment, based on rules defined in a convenient yml file. It specifically supports Joyent Cloud,
7
+ but can be easily extended to support other clouds.
8
+
9
+ The idea behind it is that working software is better than any documentation. But defining your templates
10
+ in the YML file you not only documenting how to build various types of hosts in your infrastructure,
11
+ you are also then able to use this configuration/documentation to actually provision or bootstrap these hosts
12
+ with ease.
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ gem 'joyent-provisioner'
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it into your ruby environment as follows:
25
+
26
+ $ gem install joyent-provisioner
27
+
28
+ ## Configuration
29
+
30
+ Provisioner uses a YAML file to define templates of various types of hosts in your application, and
31
+ link them with a particular type of Joyent flavors, packages and networks. Please see 'knife joyent' for
32
+ definition of these terms.
33
+
34
+ Below YAML file defines a single template 'memcached-sessions' within the 'test' environment. It
35
+ links the template with a UUID of an appropriate Joyent image, flavor, distribution, and list of
36
+ networks. It also specifies Chef run_list to configure these hosts, as well as the host sequence
37
+ which is used to provision many hosts in a cluster.
38
+
39
+ Host sequence can be overridden by command line option --number, which can pass a single number, a ruby
40
+ range, or a ruby array as a string.
41
+
42
+ ```yaml
43
+ global:
44
+ environment: test
45
+ host_suffix: test
46
+ log_dir: ./tmp
47
+ ssh_user: ops
48
+
49
+ templates:
50
+ memcached-sessions:
51
+ image: 9ec5c0c-a941-11e2-a7dc-57a6b041988f
52
+ flavor: "g3-highmemory-17.125-smartos"
53
+ distro: smartos-base64
54
+ networks: "42325ea0-eb62-44c1-8eb6-0af3e2f83abc,c8cde927-6277-49ca-82a3-741e8b23b02f"
55
+ run_list: "role[joyent]"
56
+ host_sequence: 1..2
57
+ host_prefix: memcached-sessions
58
+ ```
59
+
60
+ After provisioning all hosts in the memcached-sessions template, you should end up with two hosts, like so:
61
+
62
+ * memcached-sessions001.test
63
+ * memcached-sessions002.test
64
+
65
+ The intention is that each environment, such as production or staging, will have it's own YAML file.
66
+
67
+ ## Usage
68
+
69
+ ```bash
70
+ Usage:
71
+ [bundle exec] provisioner command ...
72
+
73
+ Where the command is one of the following:
74
+ provision, bootstrap
75
+ ```
76
+
77
+ ### Provisioning Hosts
78
+
79
+ ```bash
80
+ Usage: provisioner provision --config <path-to-config>.yml [options]
81
+ -c, --config CONFIG_FILE Path to the config file (YML) (required)
82
+ -g, --debug Log status to STDOUT
83
+ --dry-run Dry runs and prints all commands without executing them
84
+ -n, --number NUMBER Ruby range or a number for the host, ie 3 or 1..3 or [2,4,6]
85
+ -t, --template TEMPLATE Template name (required)
86
+ -h, --help Show this message
87
+ ```
88
+
89
+ To provision a specific host
90
+
91
+ ```bash
92
+ provisioner provision -n 3 -t redis-cluster -c production.yml
93
+ ```
94
+
95
+ To provision all hosts defined by host_sequence in a template
96
+
97
+ ```bash
98
+ provisioner provision -t redis-cluster -c production.yml
99
+ ```
100
+
101
+ Only show the commands that should be run without actually running them:
102
+
103
+ ```bash
104
+ provisioner provision -t redis-cluster -c production.yml --dry-run
105
+ ```
106
+
107
+ ### Bootstrapping Hosts
108
+
109
+ Bootstrapping hosts skips the provisioning step, and assumes that the host is already provisioned.
110
+ It runs 'knife joyent server list' in order to determine host's IP address, and then proceeds
111
+ to bootrap the host when the IP is found.
112
+
113
+ ```bash
114
+ Usage: provisioner bootstrap --config <path-to-config>.yml [options]
115
+ -c, --config CONFIG_FILE Path to the config file (YML) (required)
116
+ -g, --debug Log status to STDOUT
117
+ --dry-run Dry runs and prints all commands without executing them
118
+ -n, --number NUMBER Ruby range or a number for the host, ie 3 or 1..3 or [2,4,6]
119
+ -t, --template TEMPLATE Template name (required)
120
+ -h, --help Show this message
121
+ ```
122
+
123
+ ## Contributing
124
+
125
+ 1. Fork it
126
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
127
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
128
+ 4. Push to the branch (`git push origin my-new-feature`)
129
+ 5. Create new Pull Request
130
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/provisioner ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3
+ require 'rubygems'
4
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
5
+ require 'joyent-provisioner'
6
+
7
+ Provisioner::CLI.new.run
8
+
@@ -0,0 +1,16 @@
1
+ global:
2
+ environment: test
3
+ host_suffix: test
4
+ log_dir: ./tmp
5
+ ssh_user: ops
6
+
7
+ templates:
8
+ memcached-sessions:
9
+ image: 9ec5c0c-a941-11e2-a7dc-57a6b041988f
10
+ flavor: "g3-highmemory-17.125-smartos"
11
+ distro: smartos-base64
12
+ networks: "42325ea0-eb62-44c1-8eb6-0af3e2f83abc,c8cde927-6277-49ca-82a3-741e8b23b02f"
13
+ run_list: "role[joyent]"
14
+ host_sequence: 1..2
15
+ host_prefix: memcached-sessions
16
+
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'provisioner/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "joyent-provisioner"
8
+ spec.version = Provisioner::VERSION
9
+ spec.authors = ["Konstantin Gredeskoul", "Blake Irvin", "Richard Millan"]
10
+ spec.email = ["kig@wanelo.com", "bixu@wanelo.com", "richard@wanelo.com"]
11
+ spec.description = %q{Wrapper gem around provisioning clusters of servers on Joyent cloud}
12
+ spec.summary = %q{Wrapper gem around provisioning clusters of servers on Joyent cloud}
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 'mixlib-cli'
22
+ spec.add_dependency 'chef'
23
+ spec.add_dependency 'knife-joyent'
24
+ spec.add_dependency 'colorize'
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.3'
27
+ spec.add_development_dependency 'rake'
28
+ spec.add_development_dependency 'guard'
29
+ spec.add_development_dependency 'guard-rspec'
30
+ spec.add_development_dependency 'rspec'
31
+ spec.add_development_dependency 'pry-debugger'
32
+ end
@@ -0,0 +1,12 @@
1
+ module Provisioner
2
+ end
3
+
4
+ require 'colorize'
5
+
6
+ require_relative 'provisioner/usage'
7
+ require_relative 'provisioner/logger'
8
+ require_relative 'provisioner/helper'
9
+ require_relative 'provisioner/configuration'
10
+ require_relative 'provisioner/host_ip'
11
+ require_relative 'provisioner/cli'
12
+ require_relative 'provisioner/command/base'
@@ -0,0 +1,107 @@
1
+ require 'mixlib/cli'
2
+
3
+ module Provisioner
4
+ class CLI
5
+ include Mixlib::CLI
6
+
7
+ class << self
8
+ attr_reader :commands
9
+
10
+ # Returns a command class by it's name
11
+ def command name
12
+ @commands[name]
13
+ end
14
+
15
+ # Returns array of registered command names
16
+ def supported_commands
17
+ commands.keys
18
+ end
19
+
20
+ def inherited subclass
21
+ @commands ||= {}
22
+ @commands[subclass.name.split("::").last.downcase] = subclass
23
+
24
+ subclass.instance_eval do
25
+ option :config_file,
26
+ short: '-c CONFIG_FILE',
27
+ long: '--config CONFIG_FILE',
28
+ description: 'Path to the config file (YML)',
29
+ required: true
30
+
31
+ option :debug,
32
+ short: '-g',
33
+ long: '--debug',
34
+ description: 'Log status to STDOUT',
35
+ boolean: true,
36
+ required: false,
37
+ default: false
38
+
39
+ option :template,
40
+ short: '-t TEMPLATE',
41
+ long: '--template TEMPLATE',
42
+ description: 'Template name',
43
+ required: true
44
+
45
+ option :number,
46
+ short: '-n NUMBER',
47
+ long: '--number NUMBER',
48
+ description: 'Ruby range or a number for the host, ie 3 or 1..3 or [2,4,6]',
49
+ required: false
50
+
51
+ option :dry,
52
+ long: '--dry-run',
53
+ description: 'Dry runs and prints all commands without executing them',
54
+ boolean: true,
55
+ required: false
56
+
57
+ option :help,
58
+ short: '-h',
59
+ long: '--help',
60
+ description: 'Show this message',
61
+ on: :tail,
62
+ boolean: true,
63
+ show_options: true,
64
+ exit: 0
65
+ end
66
+ end
67
+ end
68
+
69
+ attr_reader :args
70
+
71
+ def run args = ARGV
72
+ @args = args
73
+ validate!
74
+ command_name = args.shift
75
+ command_class = Provisioner::CLI.command(command_name)
76
+ Provisioner::Exit.with_message("Command '#{command_name}' not found.") if command_class.nil?
77
+ command_class.new.run(args)
78
+ end
79
+
80
+ protected
81
+
82
+ def template_configuration
83
+ Provisioner::Configuration.from_path(config[:config_file])
84
+ end
85
+
86
+ private
87
+
88
+ def validate!
89
+ Provisioner::Exit.with_message('No command given') if args.empty?
90
+ @command = args.first
91
+ end
92
+
93
+ def generate_config argv = []
94
+ parse_options argv
95
+ config[:configuration] = Provisioner::Configuration.from_path(config[:config_file])
96
+ end
97
+
98
+ def enable_logger
99
+ STDOUT.sync = true
100
+ Provisioner::Logger.enable
101
+ end
102
+
103
+ end
104
+ end
105
+
106
+ require 'provisioner/cli/provision'
107
+ require 'provisioner/cli/bootstrap'
@@ -0,0 +1,25 @@
1
+ require 'mixlib/cli'
2
+ require 'pry'
3
+
4
+ class Provisioner::CLI::Bootstrap < Provisioner::CLI
5
+
6
+ banner 'Usage: provisioner bootstrap --config <path-to-config>.yml [options] '
7
+
8
+ def run(argv = ARGV)
9
+ parse_options argv
10
+ enable_logger if config[:debug]
11
+
12
+ if config[:dry]
13
+ provisioner_command.shell_commands.each do |command|
14
+ puts command.colorize(:green)
15
+ end
16
+ else
17
+ provisioner_command.run
18
+ end
19
+ end
20
+
21
+ def provisioner_command
22
+ Provisioner::Command::Bootstrap.new(template_configuration.for_template(config[:template]), config[:number])
23
+ end
24
+
25
+ end
@@ -0,0 +1,25 @@
1
+ require 'mixlib/cli'
2
+ require 'pry'
3
+
4
+ class Provisioner::CLI::Provision < Provisioner::CLI
5
+
6
+ banner 'Usage: provisioner provision --config <path-to-config>.yml [options] '
7
+
8
+ def run(argv = ARGV)
9
+ parse_options argv
10
+ enable_logger if config[:debug]
11
+
12
+ if config[:dry]
13
+ provisioner_command.shell_commands.each do |command|
14
+ puts command.colorize(:green)
15
+ end
16
+ else
17
+ provisioner_command.run
18
+ end
19
+ end
20
+
21
+ def provisioner_command
22
+ Provisioner::Command::Provision.new(template_configuration.for_template(config[:template]), config[:number])
23
+ end
24
+
25
+ end
@@ -0,0 +1,60 @@
1
+ module Provisioner
2
+ module Command
3
+ class Base
4
+ attr_accessor :image, :flavor, :distro, :networks, :run_list,
5
+ :host_sequence, :host_prefix, :environment, :host_suffix,
6
+ :host_presuffix, :log_dir, :host_number, :ssh_user
7
+
8
+ def initialize(template_configuration, host_number=nil)
9
+ @host_number = host_number
10
+ template_configuration.each_pair do |key, value|
11
+ self.send("#{key}=", value)
12
+ end
13
+ raise "Log path is required" unless @log_dir
14
+ Dir.mkdir(log_dir) unless Dir.exists?(log_dir)
15
+ end
16
+
17
+ def run
18
+ shell_commands.each do |command|
19
+ shellout command
20
+ sleep 0.5
21
+ end
22
+ end
23
+
24
+ def shell_commands_for host_number
25
+ raise 'Abstract method, implement in subclasses'
26
+ end
27
+
28
+ def shell_commands
29
+ host_numbers.map do |i|
30
+ shell_commands_for(i.to_i)
31
+ end.flatten
32
+ end
33
+
34
+ protected
35
+
36
+ def host_numbers
37
+ @host_numbers ||= begin
38
+ return [host_number] if host_number
39
+ eval "(#{host_sequence}).to_a"
40
+ end
41
+ end
42
+
43
+ def host_name(number)
44
+ host_name = sprintf('%s%03d', host_prefix, number)
45
+ host_name += ".#{host_presuffix}" if host_presuffix
46
+ host_name += ".#{host_suffix}" if host_suffix
47
+ host_name
48
+ end
49
+
50
+ def shellout(command)
51
+ puts "Running provision command:"
52
+ puts command.colorize(:green)
53
+ system(command)
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ require_relative 'provision'
60
+ require_relative 'bootstrap'
@@ -0,0 +1,45 @@
1
+ module Provisioner
2
+ module Command
3
+ class Bootstrap < Base
4
+
5
+ private
6
+
7
+ def shell_commands_for(number)
8
+ host = HostCommand.new(host_name(number), self)
9
+
10
+ [host.reset_command, host.bootstrap_command]
11
+ end
12
+
13
+ class HostCommand
14
+ attr_accessor :name, :context
15
+ def initialize(name, context)
16
+ @name = name
17
+ @context = context
18
+ end
19
+
20
+ def ip_for_host
21
+ Provisioner::HostIP.ip_for name
22
+ end
23
+
24
+ def reset_command
25
+ "ssh #{ip_for_host} -l #{context.ssh_user} 'sudo rm -rf /etc/chef'"
26
+ end
27
+
28
+ def bootstrap_command
29
+ bootstrap_command = [
30
+ 'knife bootstrap',
31
+ ip_for_host,
32
+ "--distro #{context.distro}",
33
+ "--environment #{context.environment}",
34
+ "--node-name #{name}",
35
+ ]
36
+
37
+ bootstrap_command << "--run-list #{context.run_list}" if context.run_list
38
+ bootstrap_command << "--ssh-user #{context.ssh_user}"
39
+ bootstrap_command << "2>&1 > #{context.log_dir}/#{name}_provision.log &"
40
+ bootstrap_command.join(' ')
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ module Provisioner
2
+ module Command
3
+ class Provision < Base
4
+
5
+ private
6
+
7
+ def shell_commands_for(number)
8
+ host_name = host_name(number)
9
+ command = [
10
+ "knife joyent server create",
11
+ "--image #{image}",
12
+ "--flavor #{flavor}",
13
+ "--distro #{distro}",
14
+ "--networks #{networks}",
15
+ "--environment #{environment}",
16
+ "--node-name #{host_name}",
17
+ ].join(' ')
18
+
19
+ log_path = "#{log_dir}/#{host_name}_provision.log"
20
+ command << " --run-list #{run_list}" if run_list
21
+ command << " 2>&1 > #{log_path} &"
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,36 @@
1
+ require 'yaml'
2
+ require 'ostruct'
3
+
4
+ module Provisioner
5
+ class Configuration < OpenStruct
6
+
7
+ class << self
8
+ def from_path(path)
9
+ self.new(YAML.load_file(path))
10
+ end
11
+ end
12
+
13
+ def for_template(name)
14
+ validate_template(name)
15
+
16
+ templates[name].merge(global)
17
+ end
18
+
19
+ def all
20
+ templates.map { |k, v| {k => v.merge(global)} }
21
+ end
22
+
23
+ private
24
+
25
+ def validate_template(name)
26
+ error_message = "Can't find configuration for template '#{name}'\nAvailable templates: #{template_names}\n\n"
27
+ Provisioner::Exit.with_message(error_message) unless templates[name]
28
+ end
29
+
30
+ def template_names
31
+ return '' unless templates.keys
32
+ templates.keys.join(', ')
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,6 @@
1
+ module Provisioner
2
+ module Helper
3
+ end
4
+ end
5
+
6
+ require 'provisioner/helper/exit'
@@ -0,0 +1,9 @@
1
+ module Provisioner
2
+ class Exit
3
+ def self.with_message(msg)
4
+ $stderr.puts "Error: #{msg}"
5
+ $stderr.puts Provisioner::USAGE
6
+ exit 1
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ require 'yaml'
2
+
3
+ module Provisioner
4
+ class HostIP
5
+ def self.ip_for(host)
6
+ Provisioner::Exit.with_message("Unable to find IP for host: #{host}\nMake sure 'knife joyent server list' runs successfully\n\n") unless ips[host]
7
+ ips[host]
8
+ end
9
+
10
+ private
11
+
12
+ def self.ips
13
+ @ips = {}
14
+ server_list.each do |server|
15
+ hostname, ip = server.split(' ')
16
+ @ips[hostname] = ip
17
+ end
18
+ @ips
19
+ end
20
+
21
+ def self.server_list
22
+ @server_list ||= `knife joyent server list | awk '{print $2 " " $6}' | tail +2`.split("\n")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,47 @@
1
+ module Provisioner
2
+ module Logger
3
+
4
+ class << self
5
+ def enable
6
+ class << self
7
+ self.send(:define_method, :log, proc { |msg| _log(msg) })
8
+ self.send(:define_method, :logging, proc { |msg, &block| _logging(msg, &block) })
9
+ end
10
+ end
11
+
12
+ def disable
13
+ class << self
14
+ self.send(:define_method, :log, proc { |msg|})
15
+ self.send(:define_method, :logging, proc { |msg, &block| block.call })
16
+ end
17
+ end
18
+
19
+ def log(msg)
20
+ end
21
+
22
+ def logging(msg, &block)
23
+ block.call
24
+ end
25
+
26
+ private
27
+
28
+ def _log(msg)
29
+ puts "#{Time.now}: #{sprintf("%-20s", Thread.current[:name])} - #{msg}"
30
+ end
31
+
32
+ def _logging(message, &block)
33
+ start = Time.now
34
+ returned_from_block = yield
35
+ elapsed_time = Time.now - start
36
+ if returned_from_block.is_a?(String) && returned_from_block != ""
37
+ message += " - #{returned_from_block}"
38
+ end
39
+ log "(#{"%9.2f" % (1000 * elapsed_time)}ms) #{message}"
40
+ returned_from_block
41
+ rescue Exception => e
42
+ elapsed_time = Time.now - start
43
+ log "(#{"%9.2f" % (1000 * elapsed_time)}ms) error: #{e.message} for #{message} "
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'cli'
2
+ module Provisioner
3
+ USAGE = <<-EOF
4
+
5
+ Usage:
6
+ [bundle exec] provisioner command ...
7
+
8
+ Where the command is one of the following:
9
+ #{Provisioner::CLI.supported_commands.join(', ')}
10
+ EOF
11
+ end
@@ -0,0 +1,3 @@
1
+ module Provisioner
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,16 @@
1
+ global:
2
+ environment: test
3
+ host_suffix: test
4
+ log_dir: ./tmp
5
+ ssh_user: ops
6
+
7
+ templates:
8
+ memcached-sessions:
9
+ image: 9ec5c0c-a941-11e2-a7dc-57a6b041988f
10
+ flavor: "g3-highmemory-17.125-smartos"
11
+ distro: smartos-base64
12
+ networks: "42325ea0-eb62-44c1-8eb6-0af3e2f83abc,c8cde927-6277-49ca-82a3-741e8b23b02f"
13
+ run_list: "role[joyent]"
14
+ host_sequence: 1..2
15
+ host_prefix: memcached-sessions
16
+
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'provision bootstrap cli' do
4
+ let(:provision_path) { 'bin' }
5
+
6
+ def execute(command)
7
+ `#{provision_path}/#{command} --dry-run`.strip
8
+ end
9
+
10
+ describe 'host number is provided' do
11
+ end
12
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'provision cli' do
4
+ let(:provision_path) { 'bin' }
5
+
6
+ def execute(command)
7
+ `#{provision_path}/#{command} --dry-run`.strip
8
+ end
9
+
10
+ describe 'host number is provided' do
11
+ let(:expected_command) {'knife joyent server create --image 9ec5c0c-a941-11e2-a7dc-57a6b041988f --flavor g3-highmemory-17.125-smartos --distro smartos-base64 --networks 42325ea0-eb62-44c1-8eb6-0af3e2f83abc,c8cde927-6277-49ca-82a3-741e8b23b02f --environment test --node-name memcached-sessions545.test --run-list role[joyent] 2>&1 > ./tmp/memcached-sessions545.test_provision.log &'}
12
+ it 'reads the configuration and generates command' do
13
+ result = execute('provisioner provision --number 545 --config conf/configuration.yml.example --template memcached-sessions')
14
+ expect(result).to eq(expected_command)
15
+ end
16
+
17
+ it 'accepts short options' do
18
+ result = execute('provisioner provision -n 545 -c conf/configuration.yml.example -t memcached-sessions')
19
+ expect(result).to eq(expected_command)
20
+ end
21
+ end
22
+
23
+ describe 'host number is not provided' do
24
+ it 'returns a command per host' do
25
+ result = execute('provisioner provision --config conf/configuration.yml.example --template memcached-sessions')
26
+ commands = result.split("\n")
27
+ expect(commands.size).to eq(2)
28
+ expect(commands[0]).to include('memcached-sessions001.test')
29
+ expect(commands[1]).to include('memcached-sessions002.test')
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe Provisioner::Command::Bootstrap do
4
+ describe '#shell_commands' do
5
+ context 'host is specified' do
6
+
7
+ let(:subject) { Provisioner::Command::Bootstrap.new(template_configuration, '1') }
8
+
9
+ let(:template_configuration) { {
10
+ image: '9ec5c0c-a941-11e2-a7dc-57a6b041988f',
11
+ flavor: 'g3-highmemory-17.125-smartos',
12
+ distro: 'smartos-base64',
13
+ networks: '42325ea0-eb62-44c1-8eb6-0af3e2f83abc,c8cde927-6277-49ca-82a3-741e8b23b02f',
14
+ host_sequence: '1..2',
15
+ host_suffix: 'test',
16
+ host_presuffix: 'c1',
17
+ host_prefix: 'memcached-sessions',
18
+ environment: 'test',
19
+ log_dir: './tmp',
20
+ run_list: 'role[joyent]',
21
+ ssh_user: 'ops'
22
+ } }
23
+
24
+ let(:expected_bootstrap_command) { [
25
+ 'knife bootstrap 1.2.3.4',
26
+ '--distro smartos-base64',
27
+ '--environment test',
28
+ '--node-name memcached-sessions001.c1.test',
29
+ '--run-list role[joyent]',
30
+ '--ssh-user ops',
31
+ '2>&1 > ./tmp/memcached-sessions001.c1.test_provision.log &'
32
+ ].join(' ') }
33
+
34
+ it 'returns command string' do
35
+ Provisioner::Command::Bootstrap::HostCommand.any_instance.stub(:ip_for_host) { '1.2.3.4' }
36
+ shell_commands = subject.shell_commands
37
+
38
+ expect(shell_commands[0]).to eq("ssh 1.2.3.4 -l ops 'sudo rm -rf /etc/chef'")
39
+ expect(shell_commands[1]).to eq(expected_bootstrap_command)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ describe Provisioner::Command::Provision do
4
+ let(:configuration) { Provisioner::Configuration.from_path('spec/fixtures/test.yml') }
5
+
6
+ describe '#shell_commands' do
7
+
8
+ context 'host is specified' do
9
+
10
+ let(:subject) { Provisioner::Command::Provision.new(template_configuration, '1') }
11
+ let(:expected_command) { [
12
+ 'knife joyent server create',
13
+ '--image 9ec5c0c-a941-11e2-a7dc-57a6b041988f',
14
+ '--flavor g3-highmemory-17.125-smartos',
15
+ '--distro smartos-base64',
16
+ '--networks 42325ea0-eb62-44c1-8eb6-0af3e2f83abc,c8cde927-6277-49ca-82a3-741e8b23b02f',
17
+ '--environment test',
18
+ '--node-name memcached-sessions001.test',
19
+ '--run-list role[joyent]',
20
+ '2>&1 > ./tmp/memcached-sessions001.test_provision.log &'
21
+ ].join(' ') }
22
+
23
+ context 'passing options directly' do
24
+ let(:template_configuration) { {
25
+ image: '9ec5c0c-a941-11e2-a7dc-57a6b041988f',
26
+ flavor: 'g3-highmemory-17.125-smartos',
27
+ distro: 'smartos-base64',
28
+ networks: '42325ea0-eb62-44c1-8eb6-0af3e2f83abc,c8cde927-6277-49ca-82a3-741e8b23b02f',
29
+ host_sequence: '1..2',
30
+ host_suffix: 'test',
31
+ host_prefix: 'memcached-sessions',
32
+ environment: 'test',
33
+ log_dir: './tmp',
34
+ run_list: 'role[joyent]'
35
+ } }
36
+ it 'returns expected command string' do
37
+ expect(subject.shell_commands).to eq([expected_command])
38
+ end
39
+ end
40
+
41
+ context 'using the template from yaml configuration' do
42
+ let(:template_configuration) { configuration.for_template('memcached-sessions') }
43
+
44
+ it 'returns expected command string' do
45
+ expect(subject.shell_commands).to eq([expected_command])
46
+ end
47
+ end
48
+ end
49
+
50
+ context 'host number is not specified' do
51
+ let(:subject) { Provisioner::Command::Provision.new(template_configuration) }
52
+ let(:template_configuration) { configuration.for_template('memcached-sessions') }
53
+
54
+ it 'returns an array of commands based on the configuration' do
55
+ commands = subject.shell_commands
56
+ expect(commands).to be_an(Array)
57
+ expect(commands.size).to be(2)
58
+ expect(commands[0]).to include('--node-name memcached-sessions001.test')
59
+ expect(commands[1]).to include('--node-name memcached-sessions002.test')
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Provisioner::Configuration do
4
+
5
+ let(:config) { Provisioner::Configuration.from_path('spec/fixtures/test.yml') }
6
+
7
+ describe '#for_template' do
8
+ let(:template_configuration) { config.for_template('memcached-sessions') }
9
+ it 'returns a configuration for a template' do
10
+ expect(template_configuration['image']).to eq('9ec5c0c-a941-11e2-a7dc-57a6b041988f')
11
+ end
12
+
13
+ it 'includes global configuration' do
14
+ expect(template_configuration['environment']).to eq('test')
15
+ expect(template_configuration['host_suffix']).to eq('test')
16
+ expect(template_configuration['log_dir']).to eq('./tmp')
17
+ expect(template_configuration['ssh_user']).to eq('ops')
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe Provisioner::HostIP do
4
+
5
+ before do
6
+ Provisioner::HostIP.stub(:server_list){ ['app001.stage 1.1.1.1', 'app002.stage 1.1.1.2']}
7
+ end
8
+
9
+
10
+ describe '.ip_for' do
11
+ it 'returns the IP address given a hostname' do
12
+ expect(Provisioner::HostIP.ip_for('app001.stage')).to eq('1.1.1.1')
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+
8
+ require 'joyent-provisioner'
9
+
10
+ RSpec.configure do |config|
11
+ config.treat_symbols_as_metadata_keys_with_true_values = true
12
+ config.run_all_when_everything_filtered = true
13
+ config.filter_run :focus
14
+
15
+ # Run specs in random order to surface order dependencies. If you find an
16
+ # order dependency and want to debug it, you can fix the order by providing
17
+ # the seed, which is printed after each run.
18
+ # --seed 1234
19
+ config.order = 'random'
20
+ end
metadata ADDED
@@ -0,0 +1,258 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: joyent-provisioner
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Konstantin Gredeskoul
9
+ - Blake Irvin
10
+ - Richard Millan
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2013-08-15 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: mixlib-cli
18
+ requirement: !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ! '>='
22
+ - !ruby/object:Gem::Version
23
+ version: '0'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ none: false
28
+ requirements:
29
+ - - ! '>='
30
+ - !ruby/object:Gem::Version
31
+ version: '0'
32
+ - !ruby/object:Gem::Dependency
33
+ name: chef
34
+ requirement: !ruby/object:Gem::Requirement
35
+ none: false
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ - !ruby/object:Gem::Dependency
49
+ name: knife-joyent
50
+ requirement: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ - !ruby/object:Gem::Dependency
65
+ name: colorize
66
+ requirement: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ type: :runtime
73
+ prerelease: false
74
+ version_requirements: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ - !ruby/object:Gem::Dependency
81
+ name: bundler
82
+ requirement: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ version: '1.3'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ~>
94
+ - !ruby/object:Gem::Version
95
+ version: '1.3'
96
+ - !ruby/object:Gem::Dependency
97
+ name: rake
98
+ requirement: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: guard
114
+ requirement: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ! '>='
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ type: :development
121
+ prerelease: false
122
+ version_requirements: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ! '>='
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ - !ruby/object:Gem::Dependency
129
+ name: guard-rspec
130
+ requirement: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ! '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ type: :development
137
+ prerelease: false
138
+ version_requirements: !ruby/object:Gem::Requirement
139
+ none: false
140
+ requirements:
141
+ - - ! '>='
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ - !ruby/object:Gem::Dependency
145
+ name: rspec
146
+ requirement: !ruby/object:Gem::Requirement
147
+ none: false
148
+ requirements:
149
+ - - ! '>='
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ none: false
156
+ requirements:
157
+ - - ! '>='
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ - !ruby/object:Gem::Dependency
161
+ name: pry-debugger
162
+ requirement: !ruby/object:Gem::Requirement
163
+ none: false
164
+ requirements:
165
+ - - ! '>='
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ type: :development
169
+ prerelease: false
170
+ version_requirements: !ruby/object:Gem::Requirement
171
+ none: false
172
+ requirements:
173
+ - - ! '>='
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ description: Wrapper gem around provisioning clusters of servers on Joyent cloud
177
+ email:
178
+ - kig@wanelo.com
179
+ - bixu@wanelo.com
180
+ - richard@wanelo.com
181
+ executables:
182
+ - provisioner
183
+ extensions: []
184
+ extra_rdoc_files: []
185
+ files:
186
+ - .gitignore
187
+ - .rspec
188
+ - .travis.yml
189
+ - Gemfile
190
+ - Guardfile
191
+ - LICENSE.txt
192
+ - README.md
193
+ - Rakefile
194
+ - bin/provisioner
195
+ - conf/configuration.yml.example
196
+ - joyent-provisioner.gemspec
197
+ - lib/joyent-provisioner.rb
198
+ - lib/provisioner/cli.rb
199
+ - lib/provisioner/cli/bootstrap.rb
200
+ - lib/provisioner/cli/provision.rb
201
+ - lib/provisioner/command/base.rb
202
+ - lib/provisioner/command/bootstrap.rb
203
+ - lib/provisioner/command/provision.rb
204
+ - lib/provisioner/configuration.rb
205
+ - lib/provisioner/helper.rb
206
+ - lib/provisioner/helper/exit.rb
207
+ - lib/provisioner/host_ip.rb
208
+ - lib/provisioner/logger.rb
209
+ - lib/provisioner/usage.rb
210
+ - lib/provisioner/version.rb
211
+ - spec/fixtures/test.yml
212
+ - spec/provisioner/cli/bootstrap_spec.rb
213
+ - spec/provisioner/cli/host_spec.rb
214
+ - spec/provisioner/command/bootstrap_spec.rb
215
+ - spec/provisioner/command/provision_spec.rb
216
+ - spec/provisioner/configuration_spec.rb
217
+ - spec/provisioner/host_ip_spec.rb
218
+ - spec/spec_helper.rb
219
+ homepage: ''
220
+ licenses:
221
+ - MIT
222
+ post_install_message:
223
+ rdoc_options: []
224
+ require_paths:
225
+ - lib
226
+ required_ruby_version: !ruby/object:Gem::Requirement
227
+ none: false
228
+ requirements:
229
+ - - ! '>='
230
+ - !ruby/object:Gem::Version
231
+ version: '0'
232
+ segments:
233
+ - 0
234
+ hash: 3329166401206121639
235
+ required_rubygems_version: !ruby/object:Gem::Requirement
236
+ none: false
237
+ requirements:
238
+ - - ! '>='
239
+ - !ruby/object:Gem::Version
240
+ version: '0'
241
+ segments:
242
+ - 0
243
+ hash: 3329166401206121639
244
+ requirements: []
245
+ rubyforge_project:
246
+ rubygems_version: 1.8.24
247
+ signing_key:
248
+ specification_version: 3
249
+ summary: Wrapper gem around provisioning clusters of servers on Joyent cloud
250
+ test_files:
251
+ - spec/fixtures/test.yml
252
+ - spec/provisioner/cli/bootstrap_spec.rb
253
+ - spec/provisioner/cli/host_spec.rb
254
+ - spec/provisioner/command/bootstrap_spec.rb
255
+ - spec/provisioner/command/provision_spec.rb
256
+ - spec/provisioner/configuration_spec.rb
257
+ - spec/provisioner/host_ip_spec.rb
258
+ - spec/spec_helper.rb