gusteau 0.4.8 → 1.0.0.dev

Sign up to get free protection for your applications and to get access to all the features.
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