gusteau 0.4.7 → 0.4.8

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