caterer 0.0.1 → 0.1.0

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