caterer 0.0.1 → 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.
Files changed (59) hide show
  1. data/.gitignore +1 -0
  2. data/Berksfile +3 -0
  3. data/Berksfile.lock +0 -0
  4. data/README.md +138 -2
  5. data/Vagrantfile +22 -0
  6. data/bin/cater +9 -0
  7. data/caterer.gemspec +5 -0
  8. data/cookbooks/users/recipes/default.rb +18 -0
  9. data/example/Caterfile +28 -10
  10. data/example/bootstrap.sh +3 -0
  11. data/knife.rb +1 -0
  12. data/lib/caterer/action/base.rb +10 -0
  13. data/lib/caterer/action/config/validate/image.rb +23 -0
  14. data/lib/caterer/action/config/validate/provisioner.rb +29 -0
  15. data/lib/caterer/action/config/validate.rb +10 -0
  16. data/lib/caterer/action/config.rb +7 -0
  17. data/lib/caterer/action/provisioner/base.rb +16 -0
  18. data/lib/caterer/action/provisioner/bootstrap.rb +14 -0
  19. data/lib/caterer/action/provisioner/cleanup.rb +14 -0
  20. data/lib/caterer/action/provisioner/prepare.rb +14 -0
  21. data/lib/caterer/action/provisioner/provision.rb +14 -0
  22. data/lib/caterer/action/provisioner.rb +11 -0
  23. data/lib/caterer/action/server/validate/ssh.rb +22 -0
  24. data/lib/caterer/action/server/validate.rb +9 -0
  25. data/lib/caterer/action/server.rb +7 -0
  26. data/lib/caterer/action.rb +9 -0
  27. data/lib/caterer/actions.rb +48 -0
  28. data/lib/caterer/command/base.rb +100 -0
  29. data/lib/caterer/command/bootstrap.rb +28 -0
  30. data/lib/caterer/command/provision.rb +24 -0
  31. data/lib/caterer/command/reboot.rb +22 -0
  32. data/lib/caterer/command/test.rb +9 -2
  33. data/lib/caterer/command/up.rb +28 -0
  34. data/lib/caterer/command.rb +6 -1
  35. data/lib/caterer/commands.rb +6 -1
  36. data/lib/caterer/communication/rsync.rb +14 -0
  37. data/lib/caterer/communication/ssh.rb +185 -0
  38. data/lib/caterer/communication.rb +6 -0
  39. data/lib/caterer/config/base.rb +24 -11
  40. data/lib/caterer/config/group.rb +24 -0
  41. data/lib/caterer/config/image.rb +20 -0
  42. data/lib/caterer/config/member.rb +14 -0
  43. data/lib/caterer/config/provision/chef_solo.rb +36 -12
  44. data/lib/caterer/config/provision.rb +5 -3
  45. data/lib/caterer/config.rb +5 -1
  46. data/lib/caterer/environment.rb +38 -12
  47. data/lib/caterer/provisioner/base.rb +19 -0
  48. data/lib/caterer/provisioner/chef_solo.rb +150 -0
  49. data/lib/caterer/provisioner.rb +6 -0
  50. data/lib/caterer/server.rb +102 -0
  51. data/lib/caterer/util/ansi_escape_code_remover.rb +34 -0
  52. data/lib/caterer/util/retryable.rb +25 -0
  53. data/lib/caterer/util.rb +6 -0
  54. data/lib/caterer/version.rb +1 -1
  55. data/lib/caterer.rb +15 -5
  56. data/lib/templates/provisioner/chef_solo/bootstrap.sh +87 -0
  57. data/lib/templates/provisioner/chef_solo/solo.erb +3 -0
  58. metadata +124 -3
  59. data/lib/caterer/config/role.rb +0 -21
data/.gitignore CHANGED
@@ -2,6 +2,7 @@
2
2
  *.rbc
3
3
  .bundle
4
4
  .config
5
+ .vagrant
5
6
  .yardoc
6
7
  Gemfile.lock
7
8
  InstalledFiles
data/Berksfile ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ site :opscode
data/Berksfile.lock ADDED
File without changes
data/README.md CHANGED
@@ -1,6 +1,18 @@
1
1
  # Caterer
2
2
 
3
- TODO: Write a gem description
3
+ Heavily inspired by vagrant, caterer is a remote server configuration tool that caters to your servers using a collaborative, fast, and iterative push model. Caterer supports chef-solo by default.
4
+
5
+ ## Why?
6
+
7
+ Caterer is designed as a workflow-first utility around 3 guiding principles:
8
+
9
+ 1. Configuration as code.
10
+ 2. Push provisioning.
11
+ 3. Modular provisioner.
12
+
13
+ Caterer does not replace chef or puppet, but leverages them to provide a faster iterative approach to live infrastructures. If you're familiar with the vagrant workflow, imagine that your 'boxes' are remote servers and you'll feel right at home.
14
+
15
+ The goal of caterer is that an entire infrastructure definition lives within a git repo. An infrastructure developer can open the Caterfile and read the infrastructure like a map. All provisioning recipes or definitions will live within this repo, and infrastructure developers can interate repidly to provide a hot infrastructure.
4
16
 
5
17
  ## Installation
6
18
 
@@ -18,7 +30,131 @@ Or install it yourself as:
18
30
 
19
31
  ## Usage
20
32
 
21
- TODO: Write usage instructions here
33
+ ### Caterfile
34
+
35
+ Caterer loads a Caterfile from the current directory. A Caterfile defines your infrastructure in a centralized, familiar ruby dsl. A Caterfile essentially creates a library of images that can be aggregated and applied to live servers.
36
+
37
+ ### Images
38
+
39
+ An image is a configuration construct that defines the end state of the machine after that image has been applied. An image requires a provisioner.
40
+
41
+ ### Provisioner
42
+
43
+ A provisioner is the tool that will provision a server to meet the end requirements of an image. Currently only chef-solo is supported.
44
+
45
+ ### Member
46
+
47
+ A member is an optional configuration for servers. Storing live server credentials in a version controlled repo is not suitable for many infrastructures, but is there for your convenience if you want it.
48
+
49
+ ### Group
50
+
51
+ A group is a simply way of grouping images or members to provide agrregate funcionality in a simple, concise way.
52
+
53
+ ## Example
54
+
55
+ ### Caterfile
56
+
57
+ ```ruby
58
+ Caterer.configure do |config|
59
+
60
+ config.image :basic do |image|
61
+ image.provision :chef_solo do |chef|
62
+ chef.cookbooks_path = ['cookbooks'] # default
63
+ chef.add_recipe 'ruby'
64
+ end
65
+ end
66
+
67
+ config.image :rails do |image|
68
+ image.provision :chef_solo do |chef|
69
+ chef.bootstrap_script = 'script/bootstrap'
70
+ chef.cookbooks_path = ['cookbooks'] # default
71
+ chef.add_recipe 'ruby'
72
+ chef.add_recipe 'mysql::server'
73
+ chef.add_recipe 'mysql::client'
74
+ chef.json = {
75
+ "ruby" => {
76
+ "gems" => ['mysql2']
77
+ }
78
+ }
79
+ end
80
+ end
81
+
82
+ config.member :m1 do |member|
83
+ member.images = [:basic]
84
+ member.host = "192.168.1.101"
85
+ member.password = 'samIam'
86
+ end
87
+
88
+ config.group :oven do |group|
89
+
90
+ group.images = [:basic, :rails]
91
+ group.user = 'root' # optional
92
+ group.password = 'password' # optional
93
+
94
+ # optional member configuration
95
+ group.member :oven1 do |m|
96
+ m.host = "192.168.1.100"
97
+ end
98
+
99
+ group.member :oven2 do |m|
100
+ m.host = "192.168.1.101"
101
+ m.password = 'samIam'
102
+ end
103
+
104
+ end
105
+
106
+ end
107
+ ```
108
+
109
+ ### Command Line
110
+
111
+ Provision a server with a basic image (image defined in Caterfile)
112
+
113
+ ```bash
114
+ cater provision -i basic HOST
115
+ ```
116
+
117
+ Provision a server with multiple images (images defined in Caterfile)
118
+
119
+ ```bash
120
+ cater provision -i basic,rails HOST
121
+ ```
122
+
123
+ Provision a server with a group (group defined in Caterfile)
124
+
125
+ ```bash
126
+ cater provision -g oven HOST
127
+ ```
128
+
129
+ Provision a server from a member defined in Caterfile
130
+
131
+ ```bash
132
+ cater provision m1
133
+ ```
134
+
135
+ Provision a server from a group member
136
+
137
+ ```bash
138
+ cater provision oven::oven1
139
+ ```
140
+
141
+ Provision multiple servers from members defined in Caterfile
142
+
143
+ ```bash
144
+ cater provision m1,oven::oven1,oven::oven2
145
+ ```
146
+
147
+ Bootstrap a server
148
+
149
+ ```bash
150
+ cater bootstrap -i basic HOST
151
+ ```
152
+
153
+ Boostrap and provision a server
154
+
155
+ ```bash
156
+ cater up -i basic HOST
157
+ ```
22
158
 
23
159
  ## Contributing
24
160
 
data/Vagrantfile ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # require 'berkshelf/vagrant'
4
+ require 'vagrant-vbguest' unless defined? VagrantVbguest::Config
5
+
6
+ Vagrant::Config.run do |config|
7
+
8
+ config.berkshelf.config_path = './knife.rb'
9
+
10
+ config.vm.box = "precise64"
11
+ config.vm.box_url = "http://files.vagrantup.com/precise64.box"
12
+ config.vm.customize ["modifyvm", :id, "--cpus", 1, "--memory", 512]
13
+ config.vm.network :hostonly, "33.33.33.10"
14
+ config.vm.share_folder("v-root", "/vagrant", ".")
15
+ config.vbguest.auto_update = true
16
+
17
+ config.vm.provision :chef_solo do |chef|
18
+ chef.cookbooks_path = ['cookbooks']
19
+ chef.add_recipe 'users'
20
+ end
21
+
22
+ end
data/bin/cater CHANGED
@@ -28,6 +28,15 @@ else
28
28
  opts[:ui_class] = Vli::UI::Colored
29
29
  end
30
30
 
31
+ # strip out a custom config file and set it into the env opts
32
+ if ARGV.include? "-c"
33
+ index = ARGV.index '-c'
34
+ value = ARGV[index + 1]
35
+ opts[:custom_config] = value
36
+ ARGV.delete('-c')
37
+ ARGV.delete(value)
38
+ end
39
+
31
40
  env = Caterer::Environment.new(opts)
32
41
 
33
42
  env.load!
data/caterer.gemspec CHANGED
@@ -15,6 +15,11 @@ Gem::Specification.new do |gem|
15
15
  gem.add_dependency 'log4r'
16
16
  gem.add_dependency 'activesupport'
17
17
  gem.add_dependency 'vli'
18
+ gem.add_dependency 'net-ssh'
19
+ gem.add_dependency 'net-scp'
20
+ gem.add_dependency 'tilt'
21
+ gem.add_dependency 'oj'
22
+ gem.add_dependency 'multi_json', '>= 1.3'
18
23
 
19
24
  gem.files = `git ls-files`.split($/)
20
25
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -0,0 +1,18 @@
1
+
2
+ package "build-essential" do
3
+ action :nothing
4
+ end.run_action(:install)
5
+
6
+ package 'whois'
7
+ chef_gem 'ruby-shadow'
8
+
9
+ [['bert', 'password'], ['ernie', 'password'], ['tylerflint', 'password']].each do |u, pass|
10
+ user u do
11
+ supports :manage_home => true
12
+ home "/home/#{u}"
13
+ gid "admin"
14
+ shell "/bin/bash"
15
+ password `echo #{pass} | mkpasswd -s -m sha-512`.chomp
16
+ end
17
+ end
18
+
data/example/Caterfile CHANGED
@@ -1,25 +1,43 @@
1
1
  Caterer.configure do |config|
2
2
 
3
- config.role :default do |node|
3
+ config.image :basic do |image|
4
+ image.provision :chef_solo do |chef|
5
+ chef.cookbooks_path = ['cookbooks'] # default
6
+ chef.add_recipe 'ruby'
7
+ end
8
+ end
4
9
 
5
- node.provision :chef_solo do |chef|
10
+ config.image :rails do |image|
11
+ image.provision :chef_solo do |chef|
12
+ chef.bootstrap_script = 'script/bootstrap'
13
+ chef.cookbooks_path = ['cookbooks'] # default
6
14
  chef.add_recipe 'ruby'
7
15
  chef.add_recipe 'mysql::server'
8
16
  chef.add_recipe 'mysql::client'
9
17
  chef.json = {
10
18
  "ruby" => {
11
19
  "gems" => ['args_parser', 'mysql2']
12
- },
13
- "mysql" => {
14
- "server_root_password" => "root",
15
- "bind_address" => '127.0.0.1',
16
- "client" => {
17
- "packages" => ["mysql-client", "libmysqlclient-dev"]
18
- }
19
20
  }
20
21
  }
21
22
  end
23
+ end
24
+
25
+ config.group :oven do |group|
26
+
27
+ group.images = [:basic, :rails]
28
+ group.user = 'root'
29
+ group.password = 'password'
30
+
31
+ group.member :oven1 do |m|
32
+ m.host = "192.168.1.100"
33
+ end
34
+
35
+ group.member :oven2 do |m|
36
+ m.host = "192.168.1.101"
37
+ m.password = 'samIam'
38
+ end
22
39
 
23
40
  end
24
41
 
25
- end
42
+ end
43
+
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+
3
+ echo "hello bootstrap!"
data/knife.rb ADDED
@@ -0,0 +1 @@
1
+ #whatev
@@ -0,0 +1,10 @@
1
+ module Caterer
2
+ module Action
3
+ class Base
4
+ def initialize(app, env)
5
+ @app = app
6
+ @env = env
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,23 @@
1
+ module Caterer
2
+ module Action
3
+ module Config
4
+ module Validate
5
+ class Image < Base
6
+
7
+ def call(env)
8
+ # check to ensure the image exists
9
+ image = env[:config].images[env[:image]]
10
+
11
+ if not image
12
+ env[:ui].error "image ':#{env[:image]}' is not defined"
13
+ return
14
+ end
15
+
16
+ @app.call(env)
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ module Caterer
2
+ module Action
3
+ module Config
4
+ module Validate
5
+ class Provisioner < Base
6
+
7
+ def call(env)
8
+ provisioner = env[:config].images[env[:image]].provisioner
9
+
10
+ if not provisioner
11
+ env[:ui].error "provisioner for image ':#{env[:image]}' is not defined"
12
+ return
13
+ end
14
+
15
+ if errors = provisioner.errors
16
+ errors.each do |key, val|
17
+ env[:ui].error "image :#{env[:image]} provisioner error -> #{key} #{val}"
18
+ end
19
+ return
20
+ end
21
+
22
+ @app.call(env)
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,10 @@
1
+ module Caterer
2
+ module Action
3
+ module Config
4
+ module Validate
5
+ autoload :Provisioner, 'caterer/action/config/validate/provisioner'
6
+ autoload :Image, 'caterer/action/config/validate/image'
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ module Caterer
2
+ module Action
3
+ module Config
4
+ autoload :Validate, 'caterer/action/config/validate'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ require 'active_support/inflector'
2
+
3
+ module Caterer
4
+ module Action
5
+ module Provisioner
6
+ class Base < Action::Base
7
+
8
+ def provisioner(env)
9
+ config = env[:config].images[env[:image]].provisioner
10
+ "Caterer::Provisioner::#{config.name.to_s.classify}".constantize.new(env[:server], config)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ module Caterer
2
+ module Action
3
+ module Provisioner
4
+ class Bootstrap < Base
5
+
6
+ def call(env)
7
+ provisioner(env).bootstrap(env[:script])
8
+ @app.call(env)
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Caterer
2
+ module Action
3
+ module Provisioner
4
+ class Cleanup < Base
5
+
6
+ def call(env)
7
+ provisioner(env).cleanup
8
+ @app.call(env)
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Caterer
2
+ module Action
3
+ module Provisioner
4
+ class Prepare < Base
5
+
6
+ def call(env)
7
+ provisioner(env).prepare
8
+ @app.call(env)
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Caterer
2
+ module Action
3
+ module Provisioner
4
+ class Provision < Base
5
+
6
+ def call(env)
7
+ provisioner(env).provision
8
+ @app.call(env)
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ module Caterer
2
+ module Action
3
+ module Provisioner
4
+ autoload :Base, 'caterer/action/provisioner/base'
5
+ autoload :Bootstrap, 'caterer/action/provisioner/bootstrap'
6
+ autoload :Cleanup, 'caterer/action/provisioner/cleanup'
7
+ autoload :Prepare, 'caterer/action/provisioner/prepare'
8
+ autoload :Provision, 'caterer/action/provisioner/provision'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ module Caterer
2
+ module Action
3
+ module Server
4
+ module Validate
5
+ class SSH < Base
6
+
7
+ def call(env)
8
+ ready = env[:server].ssh.ready?
9
+
10
+ if not ready
11
+ env[:ui].error "unable to ssh into #{env[:server].host}"
12
+ return
13
+ end
14
+
15
+ @app.call(env)
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,9 @@
1
+ module Caterer
2
+ module Action
3
+ module Server
4
+ module Validate
5
+ autoload :SSH, 'caterer/action/server/validate/ssh'
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Caterer
2
+ module Action
3
+ module Server
4
+ autoload :Validate, 'caterer/action/server/validate'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module Caterer
2
+ module Action
3
+ autoload :Base, 'caterer/action/base'
4
+ autoload :Config, 'caterer/action/config'
5
+ autoload :Runner, 'caterer/action/runner'
6
+ autoload :Provisioner, 'caterer/action/provisioner'
7
+ autoload :Server, 'caterer/action/server'
8
+ end
9
+ end
@@ -0,0 +1,48 @@
1
+ # actions
2
+ Caterer.actions.register(:validate) do
3
+ Vli::Action::Builder.new do
4
+ use Caterer::Action::Config::Validate::Image
5
+ use Caterer::Action::Config::Validate::Provisioner
6
+ use Caterer::Action::Server::Validate::SSH
7
+ end
8
+ end
9
+
10
+ Caterer.actions.register(:prepare) do
11
+ Vli::Action::Builder.new do
12
+ use Caterer::Action::Provisioner::Prepare
13
+ end
14
+ end
15
+
16
+ Caterer.actions.register(:cleanup) do
17
+ Vli::Action::Builder.new do
18
+ use Caterer::Action::Provisioner::Cleanup
19
+ end
20
+ end
21
+
22
+ Caterer.actions.register(:bootstrap) do
23
+ Vli::Action::Builder.new do
24
+ use Caterer.actions.get(:validate)
25
+ use Caterer.actions.get(:prepare)
26
+ use Caterer::Action::Provisioner::Bootstrap
27
+ use Caterer.actions.get(:cleanup)
28
+ end
29
+ end
30
+
31
+ Caterer.actions.register(:provision) do
32
+ Vli::Action::Builder.new do
33
+ use Caterer.actions.get(:validate)
34
+ use Caterer.actions.get(:prepare)
35
+ use Caterer::Action::Provisioner::Provision
36
+ use Caterer.actions.get(:cleanup)
37
+ end
38
+ end
39
+
40
+ Caterer.actions.register(:up) do
41
+ Vli::Action::Builder.new do
42
+ use Caterer.actions.get(:validate)
43
+ use Caterer.actions.get(:prepare)
44
+ use Caterer::Action::Provisioner::Bootstrap
45
+ use Caterer::Action::Provisioner::Provision
46
+ use Caterer.actions.get(:cleanup)
47
+ end
48
+ end
@@ -0,0 +1,100 @@
1
+ module Caterer
2
+ module Command
3
+ class Base < Vli::Command::Base
4
+
5
+ def parse_options(opts=nil, options={}, force_argv=true)
6
+ opts ||= OptionParser.new
7
+ opts.separator ""
8
+ opts.on("-c CONFIG", 'assumes Caterfile in current directory')
9
+ opts.separator ""
10
+ opts.on("-u USER", "--user USER", 'assumes current username') do |u|
11
+ options[:user] = u
12
+ end
13
+ opts.on('-p PASSWORD', '--password PASSWORD', 'assumes key') do |p|
14
+ options[:pass] = p
15
+ end
16
+ opts.on('-P PORT', '--port PORT', 'assumes 22') do |p|
17
+ options[:port] = p
18
+ end
19
+ opts.on('-i IMAGE', '--image IMAGE', 'corresponds to a image in Caterfile') do |i|
20
+ options[:image] = i
21
+ end
22
+ opts.on('-g GROUP', '--group GROUP', 'corresponds to a group in Caterfile') do |g|
23
+ options[:group] = g
24
+ end
25
+ opts.separator ""
26
+
27
+ begin
28
+ argv = super(opts)
29
+ raise if force_argv and (not argv or argv.length == 0)
30
+ argv
31
+ rescue
32
+ safe_puts(opts.help)
33
+ nil
34
+ end
35
+
36
+ end
37
+
38
+ def with_target_servers(argv, options={})
39
+ target_servers(argv, options).each do |server|
40
+ yield server if block_given?
41
+ end
42
+ end
43
+
44
+ def target_servers(argv, options={})
45
+ @servers ||= begin
46
+ servers = []
47
+
48
+ argv.first.split(",").each do |host|
49
+
50
+ if group = @env.config.groups[host.to_sym]
51
+ group.members.each do |member|
52
+ servers << init_server(group, member, options)
53
+ end
54
+ else
55
+
56
+ if not host.match /::/
57
+ host = "default::#{host}"
58
+ end
59
+
60
+ g, m = host.split "::"
61
+ group = nil
62
+ member = nil
63
+
64
+ if group = @env.config.groups[g.to_sym]
65
+ member = group.members[m.to_sym]
66
+ end
67
+
68
+ servers << init_server(group, member, options.merge(:host => m))
69
+ end
70
+ end
71
+
72
+ servers
73
+ end
74
+ end
75
+
76
+ def init_server(group=nil, member=nil, options={})
77
+
78
+ group ||= Config::Group.new
79
+ member ||= Config::Member.new
80
+
81
+ opts = {}
82
+ opts[:alias] = member.name
83
+ opts[:user] = options[:user] || member.user || group.user
84
+ opts[:pass] = options[:pass] || member.password || group.password
85
+ opts[:host] = member.host || options[:host]
86
+ opts[:port] = options[:port] || member.port
87
+ opts[:images] = image_list(options) || member.images || group.images
88
+
89
+ Server.new(@env, opts)
90
+ end
91
+
92
+ def image_list(options={})
93
+ if images = options[:image]
94
+ images.split(',').map(&:to_sym)
95
+ end
96
+ end
97
+
98
+ end
99
+ end
100
+ end