cluster_chef 3.0.5
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/.gitignore +51 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +63 -0
- data/Gemfile +18 -0
- data/LICENSE +201 -0
- data/README.md +332 -0
- data/Rakefile +92 -0
- data/TODO.md +8 -0
- data/VERSION +1 -0
- data/chefignore +41 -0
- data/cluster_chef.gemspec +115 -0
- data/clusters/website_demo.rb +65 -0
- data/config/client.rb +59 -0
- data/lib/cluster_chef/chef_layer.rb +297 -0
- data/lib/cluster_chef/cloud.rb +409 -0
- data/lib/cluster_chef/cluster.rb +118 -0
- data/lib/cluster_chef/compute.rb +144 -0
- data/lib/cluster_chef/cookbook_munger/README.md.erb +47 -0
- data/lib/cluster_chef/cookbook_munger/licenses.yaml +16 -0
- data/lib/cluster_chef/cookbook_munger/metadata.rb.erb +23 -0
- data/lib/cluster_chef/cookbook_munger.rb +588 -0
- data/lib/cluster_chef/deprecated.rb +33 -0
- data/lib/cluster_chef/discovery.rb +158 -0
- data/lib/cluster_chef/dsl_object.rb +123 -0
- data/lib/cluster_chef/facet.rb +144 -0
- data/lib/cluster_chef/fog_layer.rb +134 -0
- data/lib/cluster_chef/private_key.rb +110 -0
- data/lib/cluster_chef/role_implications.rb +49 -0
- data/lib/cluster_chef/security_group.rb +103 -0
- data/lib/cluster_chef/server.rb +265 -0
- data/lib/cluster_chef/server_slice.rb +259 -0
- data/lib/cluster_chef/volume.rb +93 -0
- data/lib/cluster_chef.rb +137 -0
- data/notes/aws_console_screenshot.jpg +0 -0
- data/rspec.watchr +29 -0
- data/spec/cluster_chef/cluster_spec.rb +13 -0
- data/spec/cluster_chef/facet_spec.rb +70 -0
- data/spec/cluster_chef/server_slice_spec.rb +19 -0
- data/spec/cluster_chef/server_spec.rb +112 -0
- data/spec/cluster_chef_spec.rb +193 -0
- data/spec/spec_helper/dummy_chef.rb +25 -0
- data/spec/spec_helper.rb +50 -0
- data/spec/test_config.rb +20 -0
- data/tasks/chef_config.rb +38 -0
- data/tasks/jeweler_use_alt_branch.rb +47 -0
- metadata +227 -0
data/Rakefile
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
#
|
2
|
+
# Rakefile for Cluster Chef Knife plugins
|
3
|
+
#
|
4
|
+
# Author:: Adam Jacob (<adam@opscode.com>)
|
5
|
+
# Copyright:: Copyright (c) 2008 Opscode, Inc.
|
6
|
+
# License:: Apache License, Version 2.0
|
7
|
+
#
|
8
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
9
|
+
# you may not use this file except in compliance with the License.
|
10
|
+
# You may obtain a copy of the License at
|
11
|
+
#
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
13
|
+
#
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
17
|
+
# See the License for the specific language governing permissions and
|
18
|
+
# limitations under the License.
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'rubygems' unless defined?(Gem)
|
22
|
+
require 'bundler'
|
23
|
+
begin
|
24
|
+
Bundler.setup(:default, :development)
|
25
|
+
rescue Bundler::BundlerError => e
|
26
|
+
$stderr.puts e.message
|
27
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
28
|
+
exit e.status_code
|
29
|
+
end
|
30
|
+
require 'json'
|
31
|
+
require 'jeweler'
|
32
|
+
require 'rspec/core/rake_task'
|
33
|
+
require 'yard'
|
34
|
+
|
35
|
+
# Load constants from rake config file.
|
36
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks', '*.rb')].sort.each{|f| p f ; require f }
|
37
|
+
|
38
|
+
$jeweler_push_from_branch = 'version_3'
|
39
|
+
|
40
|
+
# ---------------------------------------------------------------------------
|
41
|
+
#
|
42
|
+
# Jeweler -- release cluster_chef as a gem
|
43
|
+
#
|
44
|
+
Jeweler::Tasks.new do |gem|
|
45
|
+
gem.name = ENV['CLUSTER_CHEF_NAME'] || "cluster_chef-knife"
|
46
|
+
gem.homepage = "http://infochimps.com/labs"
|
47
|
+
gem.license = NEW_COOKBOOK_LICENSE.to_s
|
48
|
+
gem.summary = %Q{cluster_chef allows you to orchestrate not just systems but clusters of machines. It includes a powerful layer on top of knife and a collection of cloud cookbooks.}
|
49
|
+
gem.description = %Q{cluster_chef allows you to orchestrate not just systems but clusters of machines. It includes a powerful layer on top of knife and a collection of cloud cookbooks.}
|
50
|
+
gem.email = SSL_EMAIL_ADDRESS
|
51
|
+
gem.authors = ["Infochimps"]
|
52
|
+
|
53
|
+
ignores = File.readlines(".gitignore").grep(/^[^#]\S+/).map{|s| s.chomp }
|
54
|
+
dotfiles = [".gemtest", ".gitignore", ".rspec", ".yardopts"]
|
55
|
+
gem.files = dotfiles + Dir["**/*"].
|
56
|
+
reject{|f| f =~ %r{^cookbooks/} }.
|
57
|
+
reject{|f| File.directory?(f) }.
|
58
|
+
reject{|f| ignores.any?{|i| File.fnmatch(i, f) || File.fnmatch(i+'/*', f) || File.fnmatch(i+'/**/*', f) } }
|
59
|
+
gem.test_files = gem.files.grep(/^spec\//)
|
60
|
+
gem.require_paths = ['lib']
|
61
|
+
|
62
|
+
if gem.name == 'cluster_chef'
|
63
|
+
gem.files.reject!{|f| f =~ %r{^(cluster_chef-knife.gemspec|lib/chef/knife/)} }
|
64
|
+
else
|
65
|
+
gem.files.reject!{|f| f =~ %r{^(cluster_chef.gemspec|lib/cluster_chef)} }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
Jeweler::RubygemsDotOrgTasks.new
|
69
|
+
|
70
|
+
# ---------------------------------------------------------------------------
|
71
|
+
#
|
72
|
+
# RSpec -- testing
|
73
|
+
#
|
74
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
75
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
76
|
+
end
|
77
|
+
|
78
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
79
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
80
|
+
spec.rcov = true
|
81
|
+
spec.rcov_opts = %w[ --exclude .rvm --no-comments --text-summary]
|
82
|
+
end
|
83
|
+
|
84
|
+
# ---------------------------------------------------------------------------
|
85
|
+
#
|
86
|
+
# Yard -- documentation
|
87
|
+
#
|
88
|
+
YARD::Rake::YardocTask.new
|
89
|
+
|
90
|
+
# ---------------------------------------------------------------------------
|
91
|
+
|
92
|
+
task :default => :spec
|
data/TODO.md
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
### From Nathan
|
2
|
+
- syntactic sugar for ```server(0).fullname('blah')```
|
3
|
+
|
4
|
+
### Knife commands
|
5
|
+
|
6
|
+
* knife cluster kick fails if service isn't running
|
7
|
+
* make clear directions for installing `cluster_chef` and its initial use.
|
8
|
+
* knife cluster launch should fail differently if you give it a facet that doesn't exist
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.0.5
|
data/chefignore
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Put files/directories that should be ignored in this file.
|
2
|
+
# Lines that start with '# ' are comments.
|
3
|
+
|
4
|
+
## OS
|
5
|
+
.DS_Store
|
6
|
+
Icon?
|
7
|
+
nohup.out
|
8
|
+
|
9
|
+
## EDITORS
|
10
|
+
\#*
|
11
|
+
.#*
|
12
|
+
*~
|
13
|
+
*.sw[a-z]
|
14
|
+
*.bak
|
15
|
+
REVISION
|
16
|
+
TAGS*
|
17
|
+
tmtags
|
18
|
+
*_flymake.*
|
19
|
+
*_flymake
|
20
|
+
*.tmproj
|
21
|
+
.project
|
22
|
+
.settings
|
23
|
+
mkmf.log
|
24
|
+
|
25
|
+
## COMPILED
|
26
|
+
a.out
|
27
|
+
*.o
|
28
|
+
*.pyc
|
29
|
+
*.so
|
30
|
+
|
31
|
+
## OTHER SCM
|
32
|
+
*/.bzr/*
|
33
|
+
*/.hg/*
|
34
|
+
*/.svn/*
|
35
|
+
|
36
|
+
## Don't send rspecs up in cookbook
|
37
|
+
.watchr
|
38
|
+
.rspec
|
39
|
+
spec/*
|
40
|
+
spec/fixtures/*
|
41
|
+
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "cluster_chef"
|
8
|
+
s.version = "3.0.5"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Infochimps"]
|
12
|
+
s.date = "2011-12-11"
|
13
|
+
s.description = "cluster_chef allows you to orchestrate not just systems but clusters of machines. It includes a powerful layer on top of knife and a collection of cloud cookbooks."
|
14
|
+
s.email = "coders@infochimps.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
".rspec",
|
22
|
+
"CHANGELOG.md",
|
23
|
+
"Gemfile",
|
24
|
+
"LICENSE",
|
25
|
+
"README.md",
|
26
|
+
"Rakefile",
|
27
|
+
"TODO.md",
|
28
|
+
"VERSION",
|
29
|
+
"chefignore",
|
30
|
+
"cluster_chef.gemspec",
|
31
|
+
"clusters/website_demo.rb",
|
32
|
+
"config/client.rb",
|
33
|
+
"lib/cluster_chef.rb",
|
34
|
+
"lib/cluster_chef/chef_layer.rb",
|
35
|
+
"lib/cluster_chef/cloud.rb",
|
36
|
+
"lib/cluster_chef/cluster.rb",
|
37
|
+
"lib/cluster_chef/compute.rb",
|
38
|
+
"lib/cluster_chef/cookbook_munger.rb",
|
39
|
+
"lib/cluster_chef/cookbook_munger/README.md.erb",
|
40
|
+
"lib/cluster_chef/cookbook_munger/licenses.yaml",
|
41
|
+
"lib/cluster_chef/cookbook_munger/metadata.rb.erb",
|
42
|
+
"lib/cluster_chef/deprecated.rb",
|
43
|
+
"lib/cluster_chef/discovery.rb",
|
44
|
+
"lib/cluster_chef/dsl_object.rb",
|
45
|
+
"lib/cluster_chef/facet.rb",
|
46
|
+
"lib/cluster_chef/fog_layer.rb",
|
47
|
+
"lib/cluster_chef/private_key.rb",
|
48
|
+
"lib/cluster_chef/role_implications.rb",
|
49
|
+
"lib/cluster_chef/security_group.rb",
|
50
|
+
"lib/cluster_chef/server.rb",
|
51
|
+
"lib/cluster_chef/server_slice.rb",
|
52
|
+
"lib/cluster_chef/volume.rb",
|
53
|
+
"notes/aws_console_screenshot.jpg",
|
54
|
+
"rspec.watchr",
|
55
|
+
"spec/cluster_chef/cluster_spec.rb",
|
56
|
+
"spec/cluster_chef/facet_spec.rb",
|
57
|
+
"spec/cluster_chef/server_slice_spec.rb",
|
58
|
+
"spec/cluster_chef/server_spec.rb",
|
59
|
+
"spec/cluster_chef_spec.rb",
|
60
|
+
"spec/spec_helper.rb",
|
61
|
+
"spec/spec_helper/dummy_chef.rb",
|
62
|
+
"spec/test_config.rb",
|
63
|
+
"tasks/chef_config.rb",
|
64
|
+
"tasks/jeweler_use_alt_branch.rb"
|
65
|
+
]
|
66
|
+
s.homepage = "http://infochimps.com/labs"
|
67
|
+
s.licenses = ["apachev2"]
|
68
|
+
s.require_paths = ["lib"]
|
69
|
+
s.rubygems_version = "1.8.11"
|
70
|
+
s.summary = "cluster_chef allows you to orchestrate not just systems but clusters of machines. It includes a powerful layer on top of knife and a collection of cloud cookbooks."
|
71
|
+
s.test_files = ["spec/cluster_chef/cluster_spec.rb", "spec/cluster_chef/facet_spec.rb", "spec/cluster_chef/server_slice_spec.rb", "spec/cluster_chef/server_spec.rb", "spec/cluster_chef_spec.rb", "spec/spec_helper/dummy_chef.rb", "spec/spec_helper.rb", "spec/test_config.rb"]
|
72
|
+
|
73
|
+
if s.respond_to? :specification_version then
|
74
|
+
s.specification_version = 3
|
75
|
+
|
76
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
77
|
+
s.add_runtime_dependency(%q<chef>, ["~> 0.10.4"])
|
78
|
+
s.add_runtime_dependency(%q<fog>, ["~> 1.1.1"])
|
79
|
+
s.add_runtime_dependency(%q<formatador>, ["~> 0.2.1"])
|
80
|
+
s.add_runtime_dependency(%q<gorillib>, ["~> 0.1.7"])
|
81
|
+
s.add_development_dependency(%q<bundler>, ["~> 1"])
|
82
|
+
s.add_development_dependency(%q<yard>, ["~> 0.6.7"])
|
83
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
|
84
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.7.0"])
|
85
|
+
s.add_development_dependency(%q<configliere>, ["~> 0.4.8"])
|
86
|
+
s.add_development_dependency(%q<spork>, ["~> 0.9.0.rc5"])
|
87
|
+
s.add_development_dependency(%q<watchr>, ["~> 0.7"])
|
88
|
+
else
|
89
|
+
s.add_dependency(%q<chef>, ["~> 0.10.4"])
|
90
|
+
s.add_dependency(%q<fog>, ["~> 1.1.1"])
|
91
|
+
s.add_dependency(%q<formatador>, ["~> 0.2.1"])
|
92
|
+
s.add_dependency(%q<gorillib>, ["~> 0.1.7"])
|
93
|
+
s.add_dependency(%q<bundler>, ["~> 1"])
|
94
|
+
s.add_dependency(%q<yard>, ["~> 0.6.7"])
|
95
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
96
|
+
s.add_dependency(%q<rspec>, ["~> 2.7.0"])
|
97
|
+
s.add_dependency(%q<configliere>, ["~> 0.4.8"])
|
98
|
+
s.add_dependency(%q<spork>, ["~> 0.9.0.rc5"])
|
99
|
+
s.add_dependency(%q<watchr>, ["~> 0.7"])
|
100
|
+
end
|
101
|
+
else
|
102
|
+
s.add_dependency(%q<chef>, ["~> 0.10.4"])
|
103
|
+
s.add_dependency(%q<fog>, ["~> 1.1.1"])
|
104
|
+
s.add_dependency(%q<formatador>, ["~> 0.2.1"])
|
105
|
+
s.add_dependency(%q<gorillib>, ["~> 0.1.7"])
|
106
|
+
s.add_dependency(%q<bundler>, ["~> 1"])
|
107
|
+
s.add_dependency(%q<yard>, ["~> 0.6.7"])
|
108
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
109
|
+
s.add_dependency(%q<rspec>, ["~> 2.7.0"])
|
110
|
+
s.add_dependency(%q<configliere>, ["~> 0.4.8"])
|
111
|
+
s.add_dependency(%q<spork>, ["~> 0.9.0.rc5"])
|
112
|
+
s.add_dependency(%q<watchr>, ["~> 0.7"])
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
ClusterChef.cluster 'webserver_demo' do
|
2
|
+
cloud :ec2 do
|
3
|
+
defaults
|
4
|
+
availability_zones ['us-east-1d']
|
5
|
+
flavor 't1.micro' # change to something larger for serious use
|
6
|
+
backing 'ebs'
|
7
|
+
image_name 'natty'
|
8
|
+
bootstrap_distro 'ubuntu10.04-cluster_chef'
|
9
|
+
chef_client_script 'client.rb'
|
10
|
+
mount_ephemerals(:tags => { :scratch_dirs => true })
|
11
|
+
end
|
12
|
+
|
13
|
+
role "nfs_client"
|
14
|
+
recipe "package_set"
|
15
|
+
|
16
|
+
facet :webnode do
|
17
|
+
instances 6
|
18
|
+
role "nginx"
|
19
|
+
role "redis_client"
|
20
|
+
role "mysql_client"
|
21
|
+
role "elasticsearch_client"
|
22
|
+
role "awesome_website"
|
23
|
+
role "web_server" # this triggers opening appropriate ports
|
24
|
+
# Rotate nodes among availability zones
|
25
|
+
azs = ['us-east-1d', 'us-east-1b', 'us-east-1c']
|
26
|
+
(0...instances).each do |idx|
|
27
|
+
server(idx).cloud.availability_zones [azs[ idx % azs.length ]]
|
28
|
+
end
|
29
|
+
# Rote nodes among A/B testing groups
|
30
|
+
(0..instances).each do |idx|
|
31
|
+
server(idx).chef_node.normal[:split_testing] = ( (idx % 2 == 0) ? 'A' : 'B' )
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
facet :dbnode do
|
36
|
+
instances 2
|
37
|
+
role "mysql_server"
|
38
|
+
role "redis_client"
|
39
|
+
# burly master, wussier slaves
|
40
|
+
cloud.flavor "m1.large"
|
41
|
+
server(0) do
|
42
|
+
cloud.flavor "c1.xlarge"
|
43
|
+
end
|
44
|
+
|
45
|
+
volume(:data) do
|
46
|
+
size 50
|
47
|
+
keep true
|
48
|
+
device '/dev/sdi'
|
49
|
+
mount_point '/data/db'
|
50
|
+
mount_options 'defaults,nouuid,noatime'
|
51
|
+
fstype 'xfs'
|
52
|
+
snapshot_id 'snap-d9c1edb1'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
facet :esnode do
|
57
|
+
instances 1
|
58
|
+
role "nginx"
|
59
|
+
role "redis_server"
|
60
|
+
role "elasticsearch_data_esnode"
|
61
|
+
role "elasticsearch_http_esnode"
|
62
|
+
#
|
63
|
+
cloud.flavor "m1.large"
|
64
|
+
end
|
65
|
+
end
|
data/config/client.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require "ohai"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
#
|
5
|
+
# Load configuration
|
6
|
+
#
|
7
|
+
|
8
|
+
def merge_safely hsh
|
9
|
+
hsh.merge!( yield ) rescue Mash.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_file_if_empty(filename, str)
|
13
|
+
unless File.exists?(filename)
|
14
|
+
puts "Populating #{filename}" ;
|
15
|
+
File.open(filename, "w", 0600){|f| f.puts(str) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def present?(config, key)
|
20
|
+
not config[key].to_s.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
# Start with a set of defaults
|
24
|
+
chef_config = Mash.new
|
25
|
+
|
26
|
+
# Extract client configuration from EC2 user-data
|
27
|
+
OHAI_INFO = Ohai::System.new
|
28
|
+
OHAI_INFO.all_plugins
|
29
|
+
merge_safely(chef_config){ JSON.parse(OHAI_INFO[:ec2][:userdata]) }
|
30
|
+
|
31
|
+
#
|
32
|
+
# Configure chef run
|
33
|
+
#
|
34
|
+
|
35
|
+
log_level :info
|
36
|
+
log_location STDOUT
|
37
|
+
node_name chef_config["node_name"] if chef_config["node_name"]
|
38
|
+
chef_server_url chef_config["chef_server"] if chef_config["chef_server"]
|
39
|
+
validation_client_name chef_config["validation_client_name"] if chef_config["validation_client_name"]
|
40
|
+
validation_key "/etc/chef/validation.pem"
|
41
|
+
client_key "/etc/chef/client.pem"
|
42
|
+
node_attrs_file "/etc/chef/first-boot.json"
|
43
|
+
|
44
|
+
# If the client file is missing, write the validation key out so chef-client can register
|
45
|
+
unless File.exists?(client_key)
|
46
|
+
if present?(chef_config, "client_key") then create_file_if_empty(client_key, chef_config["client_key"])
|
47
|
+
elsif present?(chef_config, "validation_key") then create_file_if_empty(validation_key, chef_config["validation_key"])
|
48
|
+
else warn "Yikes -- I have no client key or validation key!!"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
reduced_chef_config = chef_config.reject{|k,v| k.to_s =~ /(_key|run_list)$/ }
|
53
|
+
unless File.exists?(node_attrs_file)
|
54
|
+
create_file_if_empty(node_attrs_file, JSON.pretty_generate(reduced_chef_config))
|
55
|
+
end
|
56
|
+
json_attribs node_attrs_file
|
57
|
+
|
58
|
+
Chef::Log.debug(JSON.generate(chef_config))
|
59
|
+
Chef::Log.info("=> chef client #{node_name} on #{chef_server_url} in cluster +#{chef_config["cluster_name"]}+")
|
@@ -0,0 +1,297 @@
|
|
1
|
+
#
|
2
|
+
# OK so things get a little fishy here, and it's all Opscode's fault ;-)
|
3
|
+
#
|
4
|
+
# There's currently no API for setting ACLs. However, if the *client the
|
5
|
+
# node will run as* is the *client that creates the node*, it is granted the
|
6
|
+
# correct permissions.
|
7
|
+
#
|
8
|
+
# * client exists, node exists: don't need to do anything. We trust that permissions are correct.
|
9
|
+
# * client absent, node exists: client created, node is fine. We trust that permissions are correct.
|
10
|
+
# * client absent, node absent: client created, so have key; client creates node, so it has write permissions.
|
11
|
+
# * client exists, node absent: FAIL.
|
12
|
+
#
|
13
|
+
# The current implementation persists the client keys locally to your
|
14
|
+
# Chef::Config[:client_key_dir]. This is insecure and unmanageable; and the
|
15
|
+
# node will shortly re-register the key, making it invalide anyway.
|
16
|
+
#
|
17
|
+
# If the client's private_key is empty/wrong and the node is absent, it will
|
18
|
+
# cause an error. in that case, you can:
|
19
|
+
#
|
20
|
+
# * create the node yourself in the management console, and
|
21
|
+
# grant access to its eponymous client; OR
|
22
|
+
# * nuke the client key from orbit (it's the only way to be sure) and re-run,
|
23
|
+
# taking all responsibility for the catastrophic results of an errant nuke; OR
|
24
|
+
# * wait for opscode to open API access for ACLs.
|
25
|
+
#
|
26
|
+
#
|
27
|
+
|
28
|
+
module ClusterChef
|
29
|
+
module DryRunnable
|
30
|
+
# Run given block unless in dry_run mode (ClusterChef.chef_config[:dry_run]
|
31
|
+
# is true)
|
32
|
+
def unless_dry_run
|
33
|
+
if ClusterChef.chef_config[:dry_run]
|
34
|
+
ui.info(" ... but not really (#{ui.color("dry run", :bold, :yellow)} for server #{name})")
|
35
|
+
else
|
36
|
+
yield
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
ComputeBuilder.class_eval do
|
42
|
+
def new_chef_role(role_name, cluster, facet=nil)
|
43
|
+
chef_role = Chef::Role.new
|
44
|
+
chef_role.name role_name
|
45
|
+
chef_role.description "ClusterChef generated role for #{[cluster_name, facet_name].compact.join('-')}" unless chef_role.description
|
46
|
+
chef_role.instance_eval{ @cluster = cluster; @facet = facet; }
|
47
|
+
@chef_roles << chef_role
|
48
|
+
chef_role
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
ServerSlice.class_eval do
|
53
|
+
include DryRunnable
|
54
|
+
def sync_roles
|
55
|
+
step(" syncing cluster and facet roles")
|
56
|
+
unless_dry_run do
|
57
|
+
chef_roles.each(&:save)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# ClusterChef::Server methods that handle chef actions
|
64
|
+
#
|
65
|
+
Server.class_eval do
|
66
|
+
include DryRunnable
|
67
|
+
|
68
|
+
# The chef client, if it already exists in the server.
|
69
|
+
# Use the 'ensure' method to create/update it.
|
70
|
+
def chef_client
|
71
|
+
return @chef_client unless @chef_client.nil?
|
72
|
+
@chef_client = cluster.find_client(fullname) || false
|
73
|
+
end
|
74
|
+
|
75
|
+
# The chef node, if it already exists in the server.
|
76
|
+
# Use the 'ensure' method to create/update it.
|
77
|
+
def chef_node
|
78
|
+
return @chef_node unless @chef_node.nil?
|
79
|
+
@chef_node = cluster.find_node(fullname) || false
|
80
|
+
end
|
81
|
+
|
82
|
+
# true if chef client is created and discovered
|
83
|
+
def chef_client?
|
84
|
+
chef_client.present?
|
85
|
+
end
|
86
|
+
|
87
|
+
# true if chef node is created and discovered
|
88
|
+
def chef_node?
|
89
|
+
chef_node.present?
|
90
|
+
end
|
91
|
+
|
92
|
+
def delete_chef
|
93
|
+
if chef_node then
|
94
|
+
step(" deleting chef node", :red)
|
95
|
+
unless_dry_run do
|
96
|
+
chef_node.destroy
|
97
|
+
end
|
98
|
+
@chef_node = nil
|
99
|
+
end
|
100
|
+
if chef_client
|
101
|
+
step(" deleting chef client", :red)
|
102
|
+
unless_dry_run do
|
103
|
+
chef_client.destroy
|
104
|
+
end
|
105
|
+
@chef_client = nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# creates or updates the chef node.
|
110
|
+
#
|
111
|
+
# FIXME: !! this currently doesn't do the right thing for modifications to the
|
112
|
+
# chef node. !!
|
113
|
+
#
|
114
|
+
# See notes at top of file for why all this jiggery-fuckery
|
115
|
+
#
|
116
|
+
# * client exists, node exists: assume client can update, weep later when
|
117
|
+
# the initial chef run fails. Not much we can do here -- holler at opscode.
|
118
|
+
# * client exists, node absent: see if client can create, fail otherwise
|
119
|
+
# * client absent, node absent: see if client can create both, fail otherwise
|
120
|
+
# * client absent, node exists: fail (we can't get permissions)
|
121
|
+
def sync_chef_node
|
122
|
+
step(" syncing chef node using the server's key")
|
123
|
+
# force-fetch the node so that we have its full attributes (the discovery
|
124
|
+
# does not pull all of it back)
|
125
|
+
@chef_node = handle_chef_response('404'){ Chef::Node.load( fullname ) }
|
126
|
+
# sets @chef_client if it exists
|
127
|
+
chef_client
|
128
|
+
#
|
129
|
+
case
|
130
|
+
when @chef_client && @chef_node then _update_chef_node # this will fail later if the chef client is in a bad state but whaddayagonnado
|
131
|
+
when @chef_client && (! @chef_node) then _create_chef_node
|
132
|
+
when (! @chef_client) && (! @chef_node) then # create both
|
133
|
+
ensure_chef_client
|
134
|
+
_create_chef_node
|
135
|
+
when (! @chef_client) && @chef_node
|
136
|
+
raise("The #{fullname} node exists, but its client does not.\nDue to limitations in the Opscode API, if we create a client, it will lack write permissions to the node. Small sadness now avoids much sadness later\nYou must either create a client manually, fix its permissions in the Chef console, and drop its client key where we can find it; or (if you are aware of the consequences) do \nknife node delete #{fullname}")
|
137
|
+
end
|
138
|
+
@chef_node
|
139
|
+
end
|
140
|
+
|
141
|
+
def client_key
|
142
|
+
@client_key ||= ClusterChef::ChefClientKey.new("client-#{fullname}", chef_client) do |body|
|
143
|
+
chef_client.private_key(body) if chef_client.present? && body.present?
|
144
|
+
cloud.user_data(:client_key => body)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def chef_client_script_content
|
149
|
+
return @chef_client_script_content if @chef_client_script_content
|
150
|
+
return unless cloud.chef_client_script
|
151
|
+
script_filename = File.expand_path("../../config/#{cloud.chef_client_script}", File.dirname(__FILE__))
|
152
|
+
@chef_client_script_content = safely{ File.read(script_filename) }
|
153
|
+
end
|
154
|
+
|
155
|
+
protected
|
156
|
+
|
157
|
+
# Create the chef client on the server. Do not call this directly -- go
|
158
|
+
# through sync_chef_node.
|
159
|
+
#
|
160
|
+
# this is done as the eponymous client, ensuring that the client does in
|
161
|
+
# fact have permissions on the node
|
162
|
+
#
|
163
|
+
# preconditions: @chef_node is set
|
164
|
+
def _create_chef_node(&block)
|
165
|
+
step(" creating chef node", :green)
|
166
|
+
@chef_node = Chef::Node.new
|
167
|
+
@chef_node.name(fullname)
|
168
|
+
set_chef_node_attributes
|
169
|
+
set_chef_node_environment
|
170
|
+
sync_volume_attributes
|
171
|
+
unless_dry_run do
|
172
|
+
chef_api_server_as_client.post_rest('nodes', @chef_node)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Update the chef client on the server. Do not call this directly -- go
|
177
|
+
# through create_or_update_chef_node.
|
178
|
+
#
|
179
|
+
# this is done as the eponymous client, ensuring that the client does in
|
180
|
+
# fact have permissions on the node.
|
181
|
+
#
|
182
|
+
# preconditions: @chef_node is set
|
183
|
+
def _update_chef_node
|
184
|
+
step(" updating chef node", :blue)
|
185
|
+
set_chef_node_attributes
|
186
|
+
set_chef_node_environment
|
187
|
+
sync_volume_attributes
|
188
|
+
unless_dry_run do
|
189
|
+
chef_api_server_as_admin.put_rest("nodes/#{@chef_node.name}", @chef_node)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
def sync_volume_attributes
|
195
|
+
composite_volumes.each do |vol_name, vol|
|
196
|
+
chef_node.normal[:volumes] ||= Mash.new
|
197
|
+
chef_node.normal[:volumes][vol_name] = vol.to_mash.compact
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def set_chef_node_attributes
|
202
|
+
step(" setting node runlist and essential attributes")
|
203
|
+
@chef_node.run_list = Chef::RunList.new(*@settings[:run_list])
|
204
|
+
@chef_node.override[:cluster_name] = cluster_name
|
205
|
+
@chef_node.override[:facet_name] = facet_name
|
206
|
+
@chef_node.override[:facet_index] = facet_index
|
207
|
+
end
|
208
|
+
|
209
|
+
def set_chef_node_environment
|
210
|
+
@chef_node.chef_environment(environment.to_s)
|
211
|
+
end
|
212
|
+
|
213
|
+
#
|
214
|
+
# Don't call this directly -- only through ensure_chef_node_and_client
|
215
|
+
#
|
216
|
+
def ensure_chef_client
|
217
|
+
step(" ensuring chef client exists")
|
218
|
+
return @chef_client if chef_client
|
219
|
+
step( " creating chef client", :green)
|
220
|
+
@chef_client = Chef::ApiClient.new
|
221
|
+
@chef_client.name(fullname)
|
222
|
+
@chef_client.admin(false)
|
223
|
+
#
|
224
|
+
# ApiClient#create sends extra params that fail -- we'll do it ourselves
|
225
|
+
# purposefully *not* catching the 'but it already exists' error: if it
|
226
|
+
# didn't show up in the discovery process, we're in an inconsistent state
|
227
|
+
unless_dry_run do
|
228
|
+
response = chef_api_server_as_admin.post_rest("clients", { 'name' => fullname, 'admin' => false, 'private_key' => true })
|
229
|
+
client_key.body = response['private_key']
|
230
|
+
end
|
231
|
+
client_key.save
|
232
|
+
@chef_client
|
233
|
+
end
|
234
|
+
|
235
|
+
def chef_api_server_as_client
|
236
|
+
return @chef_api_server_as_client if @chef_api_server_as_client
|
237
|
+
unless File.exists?(client_key.filename)
|
238
|
+
raise("Cannot create chef node #{fullname} -- client #{@chef_client} exists but no client key found in #{client_key.filename}.")
|
239
|
+
end
|
240
|
+
@chef_api_server_as_client = Chef::REST.new(Chef::Config[:chef_server_url], fullname, client_key.filename)
|
241
|
+
end
|
242
|
+
|
243
|
+
def chef_api_server_as_admin
|
244
|
+
@chef_api_server_as_admin ||= Chef::REST.new(Chef::Config[:chef_server_url])
|
245
|
+
end
|
246
|
+
|
247
|
+
# Execute the given chef call, but don't explode if the given http status
|
248
|
+
# code comes back
|
249
|
+
#
|
250
|
+
# @return chef object, or false if the server returned a recoverable response
|
251
|
+
def handle_chef_response(recoverable_responses, &block)
|
252
|
+
begin
|
253
|
+
block.call
|
254
|
+
rescue Net::HTTPServerException => e
|
255
|
+
raise unless Array(recoverable_responses).include?(e.response.code)
|
256
|
+
Chef::Log.debug("Swallowing a #{e.response.code} response in #{self.fullname}: #{e}")
|
257
|
+
return false
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
#
|
262
|
+
# The below *was* present but was pulled from the API by opscode for some reason (2011/10/20)
|
263
|
+
#
|
264
|
+
|
265
|
+
# # The client is required to have these permissions on its eponymous node
|
266
|
+
# REQUIRED_PERMISSIONS = %w[read create update]
|
267
|
+
#
|
268
|
+
# #
|
269
|
+
# # Verify that the client has required _acl's on the node.
|
270
|
+
# #
|
271
|
+
# # We don't raise an error, just a very noisy warning.
|
272
|
+
# #
|
273
|
+
# def check_node_permissions
|
274
|
+
# step(" ensuring chef node permissions are correct")
|
275
|
+
# chef_server_rest = Chef::REST.new(Chef::Config[:chef_server_url])
|
276
|
+
# handle_chef_response('404') do
|
277
|
+
# perms = chef_server_rest.get_rest("nodes/#{fullname}/_acl")
|
278
|
+
# perms_valid = {}
|
279
|
+
# REQUIRED_PERMISSIONS.each{|perm| perms_valid[perm] = perms[perm] && perms[perm]['actors'].include?(fullname) }
|
280
|
+
# Chef::Log.debug("Checking permissions: #{perms_valid.inspect} -- #{ perms_valid.values.all? ? 'correct' : 'BADNESS' }")
|
281
|
+
# unless perms_valid.values.all?
|
282
|
+
# ui.info(" ************************ ")
|
283
|
+
# ui.info(" ")
|
284
|
+
# ui.info(" INCONSISTENT PERMISSIONS for node #{fullname}:")
|
285
|
+
# ui.info(" The client[#{fullname}] should have permissions for #{REQUIRED_PERMISSIONS.join(', ')}")
|
286
|
+
# ui.info(" Instead, they are #{perms_valid.inspect}")
|
287
|
+
# ui.info(" You should create the node #{fullname} as client[#{fullname}], not as yourself.")
|
288
|
+
# ui.info(" ")
|
289
|
+
# ui.info(" Please adjust the permissions on the Opscode console, at")
|
290
|
+
# ui.info(" https://manage.opscode.com/nodes/#{fullname}/_acl")
|
291
|
+
# ui.info(" ")
|
292
|
+
# ui.info(" ************************ ")
|
293
|
+
# end
|
294
|
+
# end
|
295
|
+
# end
|
296
|
+
end
|
297
|
+
end
|