gusteau 0.4.7 → 0.4.8

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -9,7 +9,7 @@ Gusteau
9
9
  Introduction
10
10
  ------------
11
11
 
12
- Gusteau is here to make servers provisioning simple and enjoyable. It provides an efficient interface to Chef Solo as well as some nice features:
12
+ Gusteau is here to make servers provisioning simple and enjoyable. It's a good choice if you are running a small infrastructure or a VPS and want to use Chef. It provides an efficient interface to Chef Solo as well as some nice features:
13
13
 
14
14
  * Uses YAML for readable server configuration definitions
15
15
  * Uses a single SSH connection to stream compressed files and commands
@@ -29,7 +29,7 @@ gem install gusteau
29
29
 
30
30
  A typical Gusteau node configuration looks like this:
31
31
 
32
- ```YAML
32
+ ```
33
33
  json:
34
34
  mysql:
35
35
  server_root_password: ASahiweqwqe2
@@ -39,8 +39,7 @@ json:
39
39
  - linguini
40
40
 
41
41
  roles:
42
- - platform
43
- - rails
42
+ - base
44
43
 
45
44
  recipes:
46
45
  - mysql::server
@@ -49,7 +48,7 @@ recipes:
49
48
  server:
50
49
  host: 33.33.33.20
51
50
  platform: ubuntu
52
- password: vagrant
51
+ password: omgsecret
53
52
  ```
54
53
 
55
54
  Gusteau only needs a node definition to run, but you'll need a few cookbooks to actually cook something :)
@@ -68,7 +67,7 @@ Provisioning a server
68
67
  The following command will run all roles and recipes from node's YAML file.
69
68
 
70
69
  ```
71
- gusteau node-name provision
70
+ gusteau example provision
72
71
  ```
73
72
 
74
73
  Use the `--bootstrap` or `-b` flag to bootstrap chef-solo (for the first time run).
@@ -78,7 +77,7 @@ Running recipes
78
77
  You may choose to run a few recipes instead of full provisioning.
79
78
 
80
79
  ```
81
- gusteau node-name run redis::server ntp unicorn
80
+ gusteau example run redis::server ntp unicorn
82
81
  ```
83
82
 
84
83
  SSH
@@ -86,8 +85,9 @@ SSH
86
85
  Gusteau provides a useful shortcut that you may use to ssh into a node. If you haven't got passwordless authentication set up, Gusteau will use `user` and `password` values from the node configuration.
87
86
 
88
87
  ```
89
- gusteau ssh node-name
88
+ gusteau ssh example
90
89
  ```
90
+
91
91
  Please note that `expect` utility must be installed for `gusteau ssh` to work.
92
92
 
93
93
  If you prefer calling ssh directly, you will find the `gusteau ssh_config` subcommand useful:
@@ -98,9 +98,9 @@ gusteau ssh_config >> ~/.ssh/config
98
98
 
99
99
  Using with Vagrant
100
100
  ------------------
101
- Gusteau comes with partial Vagrant integration. It enables you to move node-specific Vagrant configuration away from the Vagrantfile into node yml files, e.g.
101
+ Gusteau can save you from writing some Vagrantfile boilerplate code. It also enables you to move node-specific Vagrant configuration away from the Vagrantfile into node yml files.
102
102
 
103
- ```YAML
103
+ ```
104
104
  ...
105
105
  vagrant:
106
106
  IP: 192.168.100.20
@@ -109,25 +109,28 @@ vagrant:
109
109
  box_url: 'https://opscode-vm.s3.amazonaws.com/vagrant/opscode_ubuntu-12.04_provisionerless.box'
110
110
  ```
111
111
 
112
- This way you can tidy up your Vagrantfile:
113
-
114
- ```ruby
115
- Vagrant.require_plugin 'gusteau'
112
+ The following bit will configure Vagrant for all Gusteau nodes which have `vagrant` section defined.
116
113
 
114
+ ```
117
115
  Vagrant.configure('2') do |config|
118
- defaults = { :box_url => 'http://www.something.com/different.box' } # optional
119
- Gusteau::Vagrant.define_nodes config, defaults
116
+ Gusteau::Vagrant.detect(config) do |setup|
117
+ setup.prefix = 'loco'
118
+ setup.defaults.box_url = 'http://example.com/vm/opscode_centos-6.4.box'
119
+ setup.provision = false
120
+ end
120
121
  end
121
122
  ```
122
123
 
123
- Please note that this feature only works with Vagrant ~> 1.2 and needs gusteau to be installed as a Vagrant plugin:
124
+ * The `prefix` option lets you prepend your VirtualBox VMs names, e.g. `loco-nodename`.
125
+ * The `defaults` one lets you provide default values for `cpus`, `memory`, `box_url`.
126
+ * If you'd like to use Vagrant's own automatic `chef_solo` provisioner, set `provision` to `true`. In this scenario, `gusteau provision` will be just calling `vagrant provision`.
127
+
128
+ Please note that the add-on only works with Vagrant ~> 1.2 and needs gusteau to be installed as a Vagrant plugin:
124
129
 
125
130
  ```
126
131
  vagrant plugin install gusteau
127
132
  ```
128
133
 
129
- Gusteau doesn't automatically provision your Vagrant nodes.
130
-
131
134
  Notes
132
135
  -----
133
136
 
@@ -20,6 +20,7 @@ Gem::Specification.new do |gem|
20
20
  gem.add_dependency 'optitron'
21
21
  gem.add_dependency 'inform'
22
22
  gem.add_dependency 'json'
23
+ gem.add_dependency 'hashie'
23
24
  gem.add_dependency 'net-ssh', '>= 2.2.2'
24
25
  gem.add_dependency 'archive-tar-minitar', '>= 0.5.2'
25
26
 
@@ -2,24 +2,6 @@ require 'inform'
2
2
 
3
3
  module Gusteau
4
4
  module Log
5
- private
6
-
7
- def timestamp
8
- Time.now.strftime('%Y-%m-%dT%H:%M:%S')
9
- end
10
-
11
- def indent
12
- @level = (@level || 0) + 1
13
- end
14
-
15
- def unindent
16
- @level = (@level || 0) - 1
17
- end
18
-
19
- def prompt
20
- "[#{timestamp}] GUSTEAU: #{' ' * (@level || 0)}"
21
- end
22
-
23
5
  def log(msg, opts={})
24
6
  info "%{prompt}#{msg}", opts.merge(:prompt => prompt)
25
7
  if block_given?
@@ -41,5 +23,23 @@ module Gusteau
41
23
  def info(str, opts={})
42
24
  Inform.info str, opts
43
25
  end
26
+
27
+ private
28
+
29
+ def timestamp
30
+ Time.now.strftime('%Y-%m-%dT%H:%M:%S')
31
+ end
32
+
33
+ def indent
34
+ @level = (@level || 0) + 1
35
+ end
36
+
37
+ def unindent
38
+ @level = (@level || 0) - 1
39
+ end
40
+
41
+ def prompt
42
+ "[#{timestamp}] GUSTEAU: #{' ' * (@level || 0)}"
43
+ end
44
44
  end
45
45
  end
@@ -6,7 +6,7 @@ module Gusteau
6
6
  class Node
7
7
  include Gusteau::ERB
8
8
 
9
- attr_reader :server
9
+ attr_reader :name, :config, :server
10
10
 
11
11
  def initialize(path)
12
12
  raise "Node YAML file #{path} not found" unless path && File.exists?(path)
@@ -14,21 +14,26 @@ module Gusteau
14
14
  @name = File.basename(path).gsub('.yml','')
15
15
  @config = read_erb_yaml(path)
16
16
 
17
+ @server = Server.new(@config['server']) if @config['server']
17
18
  @dna_path = '/tmp/dna.json'
18
-
19
- @server = Server.new(@config['server'])
20
19
  end
21
20
 
22
21
  def provision(opts = {})
23
- @server.chef.run opts, dna(true)
22
+ wrap_vagrant :provision do
23
+ server.chef.run opts, dna(true)
24
+ end
24
25
  end
25
26
 
26
27
  def run(opts = {}, *recipes)
27
- @server.chef.run opts, dna(false, recipes.flatten)
28
+ wrap_vagrant :run do
29
+ server.chef.run opts, dna(false, recipes.flatten)
30
+ end
28
31
  end
29
32
 
30
33
  def ssh
31
- @server.ssh
34
+ wrap_vagrant :ssh do
35
+ server.ssh
36
+ end
32
37
  end
33
38
 
34
39
  private
@@ -56,5 +61,15 @@ module Gusteau
56
61
  recipes.map { |r| "recipe[#{r}]" }
57
62
  end
58
63
  end
64
+
65
+ def wrap_vagrant(method)
66
+ if server
67
+ yield
68
+ elsif @config['vagrant']
69
+ Vagrant.send(method, @name)
70
+ else
71
+ Kernel.abort "Neither 'server' nor 'vagrant' defined for #{@name}. Please provide one."
72
+ end
73
+ end
59
74
  end
60
75
  end
@@ -1,59 +1,111 @@
1
+ require 'hashie'
2
+ require 'gusteau/log'
3
+
4
+ class Hash; include Hashie::Extensions::SymbolizeKeys; end
5
+
1
6
  module Gusteau
2
7
  module Vagrant
3
8
  extend self
9
+ extend Gusteau::Log
10
+
11
+ def detect(config)
12
+ options = Hashie::Mash.new
13
+ options.defaults = Hashie::Mash.new
4
14
 
5
- def complete_defaults(d)
6
- d[:ip] ||= '33.33.33.99'
7
- d[:memory] ||= 1024
8
- d[:cpus] ||= 1
9
- d
15
+ yield options if block_given?
16
+ define_nodes(config, options.to_hash.symbolize_keys)
10
17
  end
11
18
 
12
- def vagrant_config(config_path, defaults, label)
13
- if config = YAML::load_file(config_path)['vagrant']
14
- name = File.basename(config_path, '.yml')
15
- label = label.nil? ? name : "#{label}-#{name}"
19
+ def define_nodes(config, options, prefix = nil)
20
+ if prefix
21
+ options[:prefix] = prefix
22
+ info "The 'prefix' param is deprecated. Please refer to the new Gusteau::Vagrant syntax."
23
+ end
16
24
 
17
- box_url = config.fetch 'box_url' do
18
- raise "Box url can't be determined for #{name}" unless defaults[:box_url]
19
- defaults[:box_url]
25
+ Dir.glob("#{options[:dir] || './nodes'}/**/*.yml").sort.each do |path|
26
+ node = ::Gusteau::Node.new(path)
27
+ if node.config['vagrant']
28
+ define_vm config, node, options
20
29
  end
30
+ end
31
+ end
21
32
 
22
- defaults = complete_defaults(defaults)
23
- {
24
- :name => name,
25
- :label => label,
26
- :box_url => box_url,
27
- :ip => config.fetch('IP', defaults[:ip]),
28
- :cpus => config.fetch('cpus', defaults[:cpus]),
29
- :memory => config.fetch('memory', defaults[:memory])
30
- }
33
+ def vm_config(node, options)
34
+ defaults = options[:defaults] || {}
35
+ config = node.config['vagrant']
36
+ label = options[:prefix] ? "#{options[:prefix]}-#{node.name}" : node.name
37
+
38
+ config = {} if config == true
39
+
40
+ box_url = config.fetch 'box_url' do
41
+ unless defaults[:box_url]
42
+ raise "Box url can't be determined for #{node.name}"
43
+ end
44
+ defaults[:box_url]
31
45
  end
46
+
47
+ {
48
+ :name => node.name,
49
+ :label => label,
50
+ :box_url => box_url,
51
+ :ip => config['IP'] || defaults[:ip],
52
+ :cpus => config['cpus'] || defaults[:cpus] || 1,
53
+ :memory => config['memory'] || defaults[:memory] || 1024
54
+ }
32
55
  end
33
56
 
34
- def define_vm(c, config)
35
- c.vm.define config[:name] do |instance|
36
- instance.vm.box = config[:name]
37
- instance.vm.box_url = config[:box_url]
57
+ def define_vm(config, node, options)
58
+ vm_config = vm_config(node, options)
59
+
60
+ config.vm.define vm_config[:name] do |instance|
61
+ instance.vm.box = vm_config[:name]
62
+ instance.vm.box_url = vm_config[:box_url]
38
63
 
39
64
  instance.vm.provider :virtualbox do |vb|
40
65
  vb.customize ['modifyvm', :id,
41
- '--memory', config[:memory],
42
- '--name', config[:label],
43
- '--cpus', config[:cpus]
66
+ '--memory', vm_config[:memory],
67
+ '--name', vm_config[:label],
68
+ '--cpus', vm_config[:cpus],
69
+ '--natdnsproxy1', 'on'
44
70
  ]
45
71
  end
46
- instance.vm.network :private_network, :ip => config[:ip]
72
+
73
+ if vm_config[:ip]
74
+ instance.vm.network :private_network, :ip => vm_config[:ip]
75
+ end
76
+
77
+ define_provisioner(instance, node) if options[:provision]
47
78
  end
48
79
  end
49
80
 
50
- def define_nodes(c, defaults = {}, label = nil)
51
- Dir.glob("./nodes/**/*.yml").sort.each do |path|
52
- if config = vagrant_config(path, defaults, label)
53
- define_vm c, config
54
- end
81
+ def define_provisioner(instance, node)
82
+ instance.vm.provision 'chef_solo' do |chef|
83
+ chef.data_bags_path = 'data_bags'
84
+ chef.cookbooks_path = ['cookbooks', 'site-cookbooks']
85
+ chef.json = node.config['json'] || {}
86
+
87
+ (node.config['recipes'] || []).each { |r| chef.add_recipe(r) }
88
+ (node.config['roles'] || []).each { |r| chef.add_role(r) }
55
89
  end
56
90
  end
57
91
 
92
+ def ssh(nodename)
93
+ vagrant_cmd("ssh #{nodename}")
94
+ end
95
+
96
+ def provision(nodename)
97
+ vagrant_cmd("provision #{nodename}")
98
+ end
99
+
100
+ def vagrant_cmd(cmd)
101
+ info "Running 'vagrant #{cmd}'"
102
+ Kernel.system("vagrant #{cmd}")
103
+ Kernel.exit $?.exitstatus
104
+ end
105
+
106
+ def run(nodename)
107
+ Kernel.abort "'run' is not supported for vagrant-only nodes. Please configure 'server' for #{nodename} node"
108
+ end
58
109
  end
59
110
  end
111
+
@@ -1,3 +1,3 @@
1
1
  module Gusteau
2
- VERSION = "0.4.7"
2
+ VERSION = "0.4.8"
3
3
  end
@@ -38,4 +38,65 @@ describe Gusteau::Node do
38
38
  end
39
39
  end
40
40
  end
41
+
42
+ describe "#vagrant_wrap" do
43
+ let(:node) { Gusteau::Node.new('spec/nodes/development.yml') }
44
+
45
+ describe "#ssh" do
46
+ context "server is defined" do
47
+ it "should call server.ssh" do
48
+ node.server.expects(:ssh)
49
+ node.ssh
50
+ end
51
+ end
52
+
53
+ context "vagrant is defined, server is not" do
54
+ before do
55
+ node.stubs(:server).returns(nil)
56
+ end
57
+
58
+ it "should call 'vagrant ssh'" do
59
+ Kernel.expects(:system).with("vagrant ssh development")
60
+ Kernel.expects(:exit)
61
+ node.ssh
62
+ end
63
+ end
64
+
65
+ context "neither server nor vagrant are defined" do
66
+ before do
67
+ node.stubs(:server).returns(nil)
68
+ node.config['vagrant'] = nil
69
+ end
70
+
71
+ it "should exit with an error" do
72
+ Kernel.expects(:abort)
73
+ node.ssh
74
+ end
75
+ end
76
+ end
77
+
78
+ describe "#run" do
79
+ context "server is defined" do
80
+ it "should call server.chef.run" do
81
+ chef = mock()
82
+ node.server.expects(:chef).returns(chef)
83
+ chef.expects(:run)
84
+
85
+ node.run('test')
86
+ end
87
+ end
88
+
89
+ context "vagrant is defined, server is not" do
90
+ before do
91
+ node.stubs(:server).returns(nil)
92
+ end
93
+
94
+ it "should exit with an error" do
95
+ Kernel.expects(:abort).with("'run' is not supported for vagrant-only nodes. Please configure 'server' for development node")
96
+ node.run('test')
97
+ end
98
+ end
99
+
100
+ end
101
+ end
41
102
  end
@@ -1,73 +1,91 @@
1
1
  require './spec/spec_helper.rb'
2
2
 
3
3
  describe Gusteau::Vagrant do
4
- let(:defaults) do
5
- {
6
- :box_url => 'https://opscode.s3.amazonaws.com/centos-6.4.box',
7
- :cpus => 64
8
- }
9
- end
10
- let(:label) { 'hyper' }
11
-
12
- describe "#vagrant_config" do
13
- subject { Gusteau::Vagrant.vagrant_config(config_path, defaults, label) }
14
4
 
15
- context "non-vagrant node" do
16
- let(:config_path) { './spec/nodes/staging.yml' }
5
+ describe "#detect" do
6
+ let(:config) { mock() }
7
+ let(:vm) { mock() }
8
+ let(:instance) { mock() }
9
+ let(:subvm) { stub_everything('subvm') }
17
10
 
18
- it "should return nil" do
19
- subject.must_equal nil
11
+ before do
12
+ def vm.define(name)
13
+ yield instance
20
14
  end
15
+
16
+ config.expects(:vm).at_least_once.returns(vm)
17
+ vm.expects(:instance).at_least_once.returns(instance)
18
+ instance.expects(:vm).at_least_once.returns(subvm)
21
19
  end
22
20
 
23
- context "vagrant node" do
24
- let(:config_path) { './spec/nodes/development.yml' }
25
-
26
- let(:expected_label) { 'hyper-development' }
27
- let(:expected_memory) { 1024 }
28
-
29
- let(:expected_config) do
30
- {
31
- :name => 'development',
32
- :label => expected_label,
33
- :box_url => 'https://opscode.s3.amazonaws.com/centos-6.4.box',
34
- :ip => '192.168.100.21',
35
- :cpus => 4,
36
- :memory => expected_memory
37
- }
21
+ it "should define vm instances with correct settings" do
22
+ subvm.expects('box='.to_sym).with('development')
23
+ subvm.expects('box_url='.to_sym).with("http://a.com/b.box")
24
+ subvm.expects(:network).with(:private_network, { :ip => '192.168.100.21' })
25
+ subvm.expects(:provision).never
26
+
27
+ Gusteau::Vagrant.detect(config) do |setup|
28
+ setup.dir = './spec/nodes'
29
+ setup.defaults.box_url = "http://a.com/b.box"
38
30
  end
31
+ end
39
32
 
40
- def config_expectation
41
- subject.must_equal(expected_config)
33
+ it "should define provisioner" do
34
+ subvm.expects(:provision).with('chef_solo')
35
+
36
+ Gusteau::Vagrant.detect(config) do |setup|
37
+ setup.dir = './spec/nodes'
38
+ setup.defaults.box_url = "http://a.com/b.box"
39
+ setup.provision = true
42
40
  end
41
+ end
42
+ end
43
43
 
44
- specify { config_expectation }
44
+ describe "#vm_config" do
45
+ subject { Gusteau::Vagrant.vm_config(node, options) }
45
46
 
46
- context "label not specified" do
47
- let(:label) { nil }
48
- let(:expected_label) { 'development' }
47
+ let(:defaults) do
48
+ {
49
+ :box_url => 'https://opscode.s3.amazonaws.com/centos-6.4.box',
50
+ :cpus => 64,
51
+ :memory => 4096,
52
+ }
53
+ end
54
+ let(:prefix) { 'hyper' }
55
+ let(:options) { { :defaults => defaults, :prefix => prefix } }
49
56
 
50
- specify { config_expectation }
51
- end
57
+ let(:node) { ::Gusteau::Node.new('./spec/nodes/development.yml') }
58
+
59
+ let(:expected_label) { 'hyper-development' }
60
+ let(:expected_config) do
61
+ {
62
+ :name => 'development',
63
+ :label => expected_label,
64
+ :box_url => 'https://opscode.s3.amazonaws.com/centos-6.4.box',
65
+ :ip => '192.168.100.21',
66
+ :cpus => 4,
67
+ :memory => 4096
68
+ }
69
+ end
52
70
 
53
- context "different memory defaults" do
54
- let(:defaults) do
55
- {
56
- :memory => 4096,
57
- :box_url => 'https://opscode.s3.amazonaws.com/centos-6.4.box',
58
- }
59
- end
60
- let(:expected_memory) { 4096 }
71
+ it "should merge in defaults" do
72
+ subject.must_equal(expected_config)
73
+ end
74
+
75
+ context "prefix not specified" do
76
+ let(:prefix) { nil }
77
+ let(:expected_label) { 'development' }
61
78
 
62
- specify { config_expectation }
79
+ it "should omit the prefix" do
80
+ subject.must_equal(expected_config)
63
81
  end
82
+ end
64
83
 
65
- context "box_url not specified" do
66
- let(:defaults) { {} }
84
+ context "box_url not specified" do
85
+ let(:defaults) { {} }
67
86
 
68
- it "should raise an exception" do
69
- proc { subject }.must_raise RuntimeError
70
- end
87
+ it "should raise an exception" do
88
+ proc { subject }.must_raise RuntimeError
71
89
  end
72
90
  end
73
91
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gusteau
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.7
4
+ version: 0.4.8
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-06-21 00:00:00.000000000 Z
13
+ date: 2013-06-24 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: optitron
@@ -60,6 +60,22 @@ dependencies:
60
60
  - - ! '>='
61
61
  - !ruby/object:Gem::Version
62
62
  version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: hashie
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
63
79
  - !ruby/object:Gem::Dependency
64
80
  name: net-ssh
65
81
  requirement: !ruby/object:Gem::Requirement