gusteau 0.4.8 → 1.0.0.dev

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.
Files changed (49) hide show
  1. data/.travis.yml +2 -0
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +31 -30
  4. data/bin/gusteau +14 -13
  5. data/gusteau.gemspec +4 -1
  6. data/lib/gusteau.rb +1 -0
  7. data/lib/gusteau/bureau.rb +32 -20
  8. data/lib/gusteau/chef.rb +2 -2
  9. data/lib/gusteau/config.rb +39 -0
  10. data/lib/gusteau/node.rb +11 -43
  11. data/lib/gusteau/ssh_config.rb +8 -7
  12. data/lib/gusteau/vagrant.rb +7 -32
  13. data/lib/gusteau/version.rb +1 -1
  14. data/spec/config/gusteau.yml +60 -0
  15. data/spec/config/remi.yml +29 -0
  16. data/spec/lib/gusteau/bureau_spec.rb +52 -0
  17. data/spec/lib/gusteau/compressed_tar_stream_spec.rb +31 -0
  18. data/spec/lib/gusteau/config_spec.rb +31 -0
  19. data/spec/lib/gusteau/log_spec.rb +34 -0
  20. data/spec/lib/gusteau/node_spec.rb +40 -85
  21. data/spec/lib/gusteau/server_spec.rb +12 -0
  22. data/spec/lib/gusteau/ssh_config_spec.rb +16 -10
  23. data/spec/lib/gusteau/ssh_spec.rb +110 -0
  24. data/spec/lib/gusteau/vagrant_spec.rb +46 -17
  25. data/spec/spec_helper.rb +11 -0
  26. data/template/.gusteau.yml.erb +21 -0
  27. data/template/.kitchen.yml +20 -0
  28. data/template/Berksfile +3 -3
  29. data/template/Gemfile +6 -1
  30. data/template/README.md.erb +70 -0
  31. data/template/Vagrantfile +12 -4
  32. data/template/init.sh +27 -0
  33. data/template/site-cookbooks/cowsay/metadata.rb +10 -0
  34. data/template/site-cookbooks/cowsay/recipes/default.rb +1 -3
  35. data/template/site-cookbooks/platform/metadata.rb +14 -0
  36. data/template/site-cookbooks/platform/recipes/default.rb +3 -0
  37. data/template/test/integration/data_bags/users/remi.json +7 -0
  38. data/template/test/integration/default/serverspec/localhost/cowsay_spec.rb +5 -0
  39. data/template/test/integration/default/serverspec/localhost/platform_spec.rb +25 -0
  40. data/template/test/integration/default/serverspec/spec_helper.rb +9 -0
  41. metadata +81 -17
  42. data/bootstrap/centos.sh +0 -17
  43. data/bootstrap/redhat.sh +0 -17
  44. data/bootstrap/ubuntu.sh +0 -17
  45. data/spec/nodes/development.yml +0 -17
  46. data/spec/nodes/production.yml +0 -18
  47. data/spec/nodes/staging.yml +0 -12
  48. data/template/nodes/example.yml.erb +0 -19
  49. data/template/roles/platform.rb +0 -8
@@ -5,3 +5,5 @@ rvm:
5
5
  - rbx
6
6
  - jruby
7
7
  - 2.0.0
8
+ env:
9
+ - COVERAGE=coveralls
@@ -0,0 +1,4 @@
1
+ ## 0.5.0
2
+
3
+ * Use omibus installation if platform is unspecified
4
+ * Use the unified `.gusteau.yml` configuration file for all nodes and environments
data/README.md CHANGED
@@ -4,12 +4,13 @@ Gusteau
4
4
  *"Anyone can cook."*
5
5
 
6
6
  [![Build Status](https://www.travis-ci.org/locomote/gusteau.png?branch=master)](https://www.travis-ci.org/locomote/gusteau)
7
+ [![Coverage Status](https://coveralls.io/repos/locomote/gusteau/badge.png)](https://coveralls.io/r/locomote/gusteau)
7
8
  [![Dependency Status](https://gemnasium.com/locomote/gusteau.png)](https://gemnasium.com/locomote/gusteau)
8
9
 
9
10
  Introduction
10
11
  ------------
11
12
 
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
+ Gusteau is an easy to use configuration manager for Chef Solo and Vagrant. It provides an efficient interface to Chef Solo as well as some nice features:
13
14
 
14
15
  * Uses YAML for readable server configuration definitions
15
16
  * Uses a single SSH connection to stream compressed files and commands
@@ -24,60 +25,60 @@ Gettings started
24
25
  Gusteau is a Ruby gem:
25
26
 
26
27
  ```
27
- gem install gusteau
28
+ gem install gusteau --pre
28
29
  ```
29
30
 
30
- A typical Gusteau node configuration looks like this:
31
+ A typical Gusteau configuration looks like this:
31
32
 
32
33
  ```
33
- json:
34
- mysql:
35
- server_root_password: ASahiweqwqe2
36
- rvm:
37
- default_ruby: 1.9.3-p362
38
- users:
39
- - linguini
34
+ environments:
35
+ development:
36
+ attributes:
37
+ mysql:
38
+ server_root_password: ASahiweqwqe2
39
+ rvm:
40
+ default_ruby: 1.9.3-p362
41
+ users:
42
+ - linguini
40
43
 
41
- roles:
42
- - base
44
+ run_list:
45
+ - role[base]
46
+ - recipe[mysql::server]
47
+ - recipe[iptables]
43
48
 
44
- recipes:
45
- - mysql::server
46
- - iptables
47
-
48
- server:
49
- host: 33.33.33.20
50
- platform: ubuntu
51
- password: omgsecret
49
+ nodes:
50
+ playground:
51
+ host: 33.33.33.20
52
+ password: omgsecret
52
53
  ```
53
54
 
54
- Gusteau only needs a node definition to run, but you'll need a few cookbooks to actually cook something :)
55
+ Gusteau only needs a single node definition to run, but you'll need a few cookbooks to actually cook something :)
55
56
  The following command generates an example configuration to get you started:
56
57
 
57
58
  ```
58
59
  gusteau init project-name
59
60
  ```
60
61
 
61
- Next, `cd project-name` and see `nodes/example.yml`.
62
+ Next, `cd project-name` and see `.gusteau.yml`.
62
63
 
63
64
 
64
- Provisioning a server
65
+ Converging a server
65
66
  ----------
66
67
 
67
68
  The following command will run all roles and recipes from node's YAML file.
68
69
 
69
70
  ```
70
- gusteau example provision
71
+ gusteau converge development-playground
71
72
  ```
72
73
 
73
74
  Use the `--bootstrap` or `-b` flag to bootstrap chef-solo (for the first time run).
74
75
 
75
- Running recipes
76
+ Applying individual recipes
76
77
  -----------
77
- You may choose to run a few recipes instead of full provisioning.
78
+ You may choose to run a custom run_list instead of the full convergence.
78
79
 
79
80
  ```
80
- gusteau example run redis::server ntp unicorn
81
+ gusteau apply development-playground "role[base],recipe[oh-my-zsh]"
81
82
  ```
82
83
 
83
84
  SSH
@@ -85,7 +86,7 @@ SSH
85
86
  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.
86
87
 
87
88
  ```
88
- gusteau ssh example
89
+ gusteau ssh development-playground
89
90
  ```
90
91
 
91
92
  Please note that `expect` utility must be installed for `gusteau ssh` to work.
@@ -123,7 +124,7 @@ end
123
124
 
124
125
  * The `prefix` option lets you prepend your VirtualBox VMs names, e.g. `loco-nodename`.
125
126
  * 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
+ * If you'd like to use Vagrant's own automatic `chef_solo` provisioner, set `provision` to `true`.
127
128
 
128
129
  Please note that the add-on only works with Vagrant ~> 1.2 and needs gusteau to be installed as a Vagrant plugin:
129
130
 
@@ -135,5 +136,5 @@ Notes
135
136
  -----
136
137
 
137
138
  * Feel free to contribute a [bootstrap script](https://github.com/locomote/gusteau/tree/master/bootstrap) for your platform.
138
- * Gusteau uploads both `./cookbooks` and `./site-cookbooks` so that you can use [librarian-chef](https://github.com/applicationsonline/librarian) to include third party cookbooks.
139
+ * Gusteau uploads `./cookbooks` and `./site-cookbooks` from the current working directory.
139
140
 
@@ -13,14 +13,14 @@ class Gusteau::CLI < Optitron::CLI
13
13
  class_opt 'log_level', 'Set the log level', :in => %w{debug info warn error fatal}
14
14
  class_opt 'why-run', 'Enable whyrun mode', :short_name => 'W'
15
15
 
16
- desc 'Fully provision a node'
17
- def provision(node_name)
18
- node(node_name).provision(params)
16
+ desc 'Fully converge a node'
17
+ def converge(node_name)
18
+ node(node_name).converge(params)
19
19
  end
20
20
 
21
- desc 'Run recipe(s)'
22
- def run(node_name, *recipe)
23
- node(node_name).run(params, recipe)
21
+ desc 'Apply a run_list'
22
+ def apply(node_name, run_list)
23
+ node(node_name).apply(params, run_list.split(","))
24
24
  end
25
25
 
26
26
  desc 'SSH into a node'
@@ -30,25 +30,26 @@ class Gusteau::CLI < Optitron::CLI
30
30
 
31
31
  desc 'Generate an SSH config'
32
32
  def ssh_config
33
- puts Gusteau::SSHConfig.new
33
+ puts Gusteau::SSHConfig.new(nodes)
34
34
  end
35
35
 
36
36
  desc 'Generate an example project (a bureau)'
37
37
  def init(bureau_name)
38
- Gusteau::Bureau.new(bureau_name)
38
+ Gusteau::Bureau.new(bureau_name).generate!
39
39
  end
40
40
 
41
41
  private
42
42
 
43
43
  def node(node_name)
44
- unless node_path = Dir.glob("./nodes/**/#{node_name}.yml")[0]
45
- abort "Node '#{node_name}' is unknown. Known nodes are #{nodes_list.join(', ')}."
44
+ unless node = nodes[node_name]
45
+ abort "Node '#{node_name}' is unknown. Known nodes are #{nodes.keys.join(', ')}."
46
+ else
47
+ node
46
48
  end
47
- Gusteau::Node.new(node_path)
48
49
  end
49
50
 
50
- def nodes_list
51
- Dir.glob("./nodes/**/*.yml").map { |f| File.basename(f, '.*') }
51
+ def nodes
52
+ @nodes ||= Gusteau::Config.nodes(".gusteau.yml")
52
53
  end
53
54
 
54
55
  end
@@ -8,7 +8,7 @@ Gem::Specification.new do |gem|
8
8
  gem.version = Gusteau::VERSION
9
9
  gem.authors = ["Vasily Mikhaylichenko", "Chris"]
10
10
  gem.email = ["vasily@locomote.com", "chris@locomote.com"]
11
- gem.description = %q{A fine Chef Solo wrapper}
11
+ gem.description = %q{Chef Solo wrapper and configuration manager}
12
12
  gem.summary = %q{Making servers provisioning enjoyable since 2013.}
13
13
  gem.homepage = "http://gusteau.gs"
14
14
 
@@ -21,9 +21,12 @@ Gem::Specification.new do |gem|
21
21
  gem.add_dependency 'inform'
22
22
  gem.add_dependency 'json'
23
23
  gem.add_dependency 'hashie'
24
+ gem.add_dependency 'hash-deep-merge'
24
25
  gem.add_dependency 'net-ssh', '>= 2.2.2'
25
26
  gem.add_dependency 'archive-tar-minitar', '>= 0.5.2'
26
27
 
27
28
  gem.add_development_dependency 'minitest'
28
29
  gem.add_development_dependency 'mocha'
30
+ gem.add_development_dependency 'simplecov'
31
+ gem.add_development_dependency 'coveralls'
29
32
  end
@@ -2,6 +2,7 @@ $LOAD_PATH << File.expand_path(File.dirname(__FILE__))
2
2
 
3
3
  module Gusteau
4
4
  require 'gusteau/node'
5
+ require 'gusteau/config'
5
6
  require 'gusteau/bureau'
6
7
  require 'gusteau/ssh_config'
7
8
  require 'gusteau/vagrant'
@@ -8,41 +8,53 @@ module Gusteau
8
8
  include Gusteau::ERB
9
9
 
10
10
  def initialize(name)
11
- template_path = File.expand_path('../../../template', __FILE__)
11
+ @name = name
12
+ @template_path = File.expand_path('../../../template', __FILE__)
12
13
 
13
14
  @login = Etc.getlogin
14
15
  @ssh_key = File.read(File.expand_path '~/.ssh/id_rsa.pub').chomp rescue 'Your SSH key'
15
16
 
16
17
  abort "Directory #{name} already exists" if Dir.exists?(name)
18
+ end
17
19
 
18
- FileUtils.cp_r(template_path, name)
20
+ def generate!(init = true)
21
+ FileUtils.cp_r(@template_path, @name)
22
+ yaml_template '.gusteau.yml'
23
+ text_template 'README.md'
24
+ json_template "data_bags/users/#{@login}.json", "data_bags/users/user.json.erb"
25
+ Dir.chdir(name) { system "bash ./init.sh ; rm ./init.sh" } if(init)
26
+ end
19
27
 
20
- File.open(File.join(name, 'nodes', 'example.yml'), 'w+') do |f|
21
- read_erb_yaml(File.join(template_path, 'nodes', 'example.yml.erb')).tap do |node|
22
- f.write node.to_yaml
23
- f.close
24
- end
28
+ private
25
29
 
26
- FileUtils.rm(File.join(name, 'nodes', 'example.yml.erb'))
30
+ def yaml_template(file)
31
+ replace_template file do |f|
32
+ read_erb_yaml("#{@template_path}/#{file}.erb").tap { |c| f.write(c.to_yaml) }
27
33
  end
34
+ end
28
35
 
29
- File.open(File.join(name, 'data_bags', 'users', "#{@login}.json"), 'w+') do |f|
30
- read_erb_json(File.join(template_path, 'data_bags', 'users', 'user.json.erb')).tap do |user|
31
- f.write JSON::pretty_generate user
32
- f.close
33
- end
36
+ def json_template(file, src)
37
+ replace_template file, src do |f|
38
+ read_erb_json("#{@template_path}/#{src}").tap { |c| f.write JSON::pretty_generate(c) }
39
+ end
40
+ end
34
41
 
35
- FileUtils.rm(File.join(name, 'data_bags', 'users', 'user.json.erb'))
42
+ def text_template(file)
43
+ replace_template file do |f|
44
+ read_erb("#{@template_path}/#{file}.erb").tap { |t| f.write t }
36
45
  end
46
+ end
37
47
 
38
- puts "Created bureau '#{name}'"
39
- Dir.chdir(name) do
40
- puts 'Installing gem dependencies'
41
- system 'bundle'
48
+ def replace_template(file, src = nil)
49
+ dest = "#{@name}/#{file}"
50
+ src = "#{@name}/#{src}" if(src)
42
51
 
43
- puts 'Installing cookbooks'
44
- system 'bundle exec berks install --path ./cookbooks'
52
+ File.open(dest, 'w+') do |f|
53
+ yield f
54
+ f.close
55
+ FileUtils.rm(src || "#{dest}.erb")
45
56
  end
46
57
  end
58
+
47
59
  end
48
60
  end
@@ -1,8 +1,8 @@
1
1
  module Gusteau
2
2
  class Chef
3
- def initialize(server, platform, dest_dir = '/etc/chef')
3
+ def initialize(server, platform = nil, dest_dir = '/etc/chef')
4
4
  @server = server
5
- @platform = platform
5
+ @platform = platform || 'omnibus'
6
6
  @dest_dir = dest_dir
7
7
  end
8
8
 
@@ -0,0 +1,39 @@
1
+ require 'gusteau/erb'
2
+ require 'hash_deep_merge'
3
+
4
+ module Gusteau
5
+ module Config
6
+ extend self
7
+ extend Gusteau::ERB
8
+
9
+ def nodes(config_path)
10
+ if File.exists?(config_path)
11
+ env_config = read_erb_yaml(config_path)['environments']
12
+ env_config.inject({}) do |nodes, (env_name, env_hash)|
13
+ if env_hash['nodes']
14
+ env_hash['nodes'].each_pair do |node_name, node_hash|
15
+ node_name = "#{env_name}-#{node_name}"
16
+ nodes[node_name] = build_node(node_name, env_hash, node_hash)
17
+ end
18
+ end
19
+ nodes
20
+ end
21
+ else
22
+ abort ".gusteau.yml not found"
23
+ end
24
+ end
25
+
26
+ #
27
+ # Node attributes get deep-merged with the environment ones
28
+ # Node run_list overrides the environment one
29
+ #
30
+ def build_node(node_name, env_hash, node_hash)
31
+ config = {
32
+ 'server' => node_hash,
33
+ 'attributes' => env_hash['attributes'].deep_merge(node_hash['attributes'] || {}),
34
+ 'run_list' => node_hash['run_list'] || env_hash['run_list']
35
+ }
36
+ Gusteau::Node.new(node_name, config)
37
+ end
38
+ end
39
+ end
@@ -1,75 +1,43 @@
1
1
  require 'gusteau/server'
2
2
  require 'gusteau/chef'
3
- require 'gusteau/erb'
4
3
 
5
4
  module Gusteau
6
5
  class Node
7
- include Gusteau::ERB
8
-
9
6
  attr_reader :name, :config, :server
10
7
 
11
- def initialize(path)
12
- raise "Node YAML file #{path} not found" unless path && File.exists?(path)
13
-
14
- @name = File.basename(path).gsub('.yml','')
15
- @config = read_erb_yaml(path)
8
+ def initialize(name, config)
9
+ @name = name
10
+ @config = config
16
11
 
17
12
  @server = Server.new(@config['server']) if @config['server']
18
13
  @dna_path = '/tmp/dna.json'
19
14
  end
20
15
 
21
- def provision(opts = {})
22
- wrap_vagrant :provision do
23
- server.chef.run opts, dna(true)
24
- end
16
+ def converge(opts = {})
17
+ server.chef.run opts, dna
25
18
  end
26
19
 
27
- def run(opts = {}, *recipes)
28
- wrap_vagrant :run do
29
- server.chef.run opts, dna(false, recipes.flatten)
30
- end
20
+ def apply(opts = {}, run_list)
21
+ server.chef.run opts, dna(run_list)
31
22
  end
32
23
 
33
24
  def ssh
34
- wrap_vagrant :ssh do
35
- server.ssh
36
- end
25
+ server.ssh
37
26
  end
38
27
 
39
28
  private
40
29
 
41
- def dna(include_all, recipes = [])
30
+ def dna(run_list = nil)
42
31
  node_dna = {
43
32
  :path => @dna_path,
44
33
  :hash => {
45
34
  :instance_role => @name,
46
- :run_list => run_list(include_all, recipes)
47
- }.merge(@config['json'])
35
+ :run_list => run_list || @config['run_list']
36
+ }.merge(@config['attributes'] || {})
48
37
  }
49
38
 
50
39
  File.open(node_dna[:path], 'w+') { |f| f.puts node_dna[:hash].to_json }
51
40
  node_dna
52
41
  end
53
-
54
- def run_list(include_all, recipes)
55
- if include_all
56
- list = []
57
- list += @config['roles'].map { |r| "role[#{r}]" } if @config['roles']
58
- list += @config['recipes'].map { |r| "recipe[#{r}]" } if @config['recipes']
59
- list
60
- else
61
- recipes.map { |r| "recipe[#{r}]" }
62
- end
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
74
42
  end
75
43
  end
@@ -3,13 +3,14 @@ require 'gusteau/server'
3
3
 
4
4
  module Gusteau
5
5
  class SSHConfig
6
- def initialize(root_dir = ".")
7
- @config = Dir.glob("#{root_dir}/nodes/**/*.yml").sort.map do |n|
8
- name = File.basename(n, '.*')
9
- config = YAML::load_file(n)['server']
6
+ def initialize(nodes)
7
+ @config = []
10
8
 
11
- section name, Gusteau::Server.new(config)
12
- end.join("\n")
9
+ nodes.each_pair do |name, node|
10
+ if server = node.server
11
+ @config << section(name, server)
12
+ end
13
+ end
13
14
  end
14
15
 
15
16
  def section(name, server)
@@ -25,7 +26,7 @@ Host #{name}
25
26
  <<-eos
26
27
  # BEGIN GUSTEAU NODES
27
28
 
28
- #{@config}
29
+ #{@config.join("\n")}
29
30
  # END GUSTEAU NODES
30
31
  eos
31
32
  end