clc-cheffish 0.8.clc
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/README.md +4 -0
- data/Rakefile +23 -0
- data/lib/chef/provider/chef_acl.rb +434 -0
- data/lib/chef/provider/chef_client.rb +48 -0
- data/lib/chef/provider/chef_container.rb +50 -0
- data/lib/chef/provider/chef_data_bag.rb +50 -0
- data/lib/chef/provider/chef_data_bag_item.rb +273 -0
- data/lib/chef/provider/chef_environment.rb +78 -0
- data/lib/chef/provider/chef_group.rb +78 -0
- data/lib/chef/provider/chef_mirror.rb +138 -0
- data/lib/chef/provider/chef_node.rb +82 -0
- data/lib/chef/provider/chef_organization.rb +150 -0
- data/lib/chef/provider/chef_resolved_cookbooks.rb +41 -0
- data/lib/chef/provider/chef_role.rb +79 -0
- data/lib/chef/provider/chef_user.rb +53 -0
- data/lib/chef/provider/private_key.rb +219 -0
- data/lib/chef/provider/public_key.rb +82 -0
- data/lib/chef/resource/chef_acl.rb +65 -0
- data/lib/chef/resource/chef_client.rb +44 -0
- data/lib/chef/resource/chef_container.rb +18 -0
- data/lib/chef/resource/chef_data_bag.rb +18 -0
- data/lib/chef/resource/chef_data_bag_item.rb +114 -0
- data/lib/chef/resource/chef_environment.rb +71 -0
- data/lib/chef/resource/chef_group.rb +49 -0
- data/lib/chef/resource/chef_mirror.rb +47 -0
- data/lib/chef/resource/chef_node.rb +18 -0
- data/lib/chef/resource/chef_organization.rb +64 -0
- data/lib/chef/resource/chef_resolved_cookbooks.rb +31 -0
- data/lib/chef/resource/chef_role.rb +104 -0
- data/lib/chef/resource/chef_user.rb +51 -0
- data/lib/chef/resource/private_key.rb +44 -0
- data/lib/chef/resource/public_key.rb +21 -0
- data/lib/cheffish.rb +222 -0
- data/lib/cheffish/actor_provider_base.rb +131 -0
- data/lib/cheffish/basic_chef_client.rb +115 -0
- data/lib/cheffish/chef_provider_base.rb +231 -0
- data/lib/cheffish/chef_run_data.rb +19 -0
- data/lib/cheffish/chef_run_listener.rb +28 -0
- data/lib/cheffish/key_formatter.rb +109 -0
- data/lib/cheffish/merged_config.rb +94 -0
- data/lib/cheffish/recipe_dsl.rb +147 -0
- data/lib/cheffish/server_api.rb +52 -0
- data/lib/cheffish/version.rb +3 -0
- data/lib/cheffish/with_pattern.rb +21 -0
- data/spec/functional/fingerprint_spec.rb +64 -0
- data/spec/functional/merged_config_spec.rb +20 -0
- data/spec/integration/chef_acl_spec.rb +914 -0
- data/spec/integration/chef_client_spec.rb +110 -0
- data/spec/integration/chef_container_spec.rb +34 -0
- data/spec/integration/chef_group_spec.rb +324 -0
- data/spec/integration/chef_mirror_spec.rb +244 -0
- data/spec/integration/chef_node_spec.rb +211 -0
- data/spec/integration/chef_organization_spec.rb +244 -0
- data/spec/integration/chef_user_spec.rb +90 -0
- data/spec/integration/private_key_spec.rb +446 -0
- data/spec/integration/recipe_dsl_spec.rb +29 -0
- data/spec/support/key_support.rb +29 -0
- data/spec/support/repository_support.rb +103 -0
- data/spec/support/spec_support.rb +176 -0
- data/spec/unit/get_private_key_spec.rb +93 -0
- metadata +162 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'chef/config'
|
2
|
+
require 'cheffish/with_pattern'
|
3
|
+
|
4
|
+
module Cheffish
|
5
|
+
class ChefRunData
|
6
|
+
def initialize(config)
|
7
|
+
@local_servers = []
|
8
|
+
@current_chef_server = Cheffish.default_chef_server(config)
|
9
|
+
end
|
10
|
+
|
11
|
+
extend Cheffish::WithPattern
|
12
|
+
with :data_bag
|
13
|
+
with :environment
|
14
|
+
with :data_bag_item_encryption
|
15
|
+
with :chef_server
|
16
|
+
|
17
|
+
attr_reader :local_servers
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'chef/event_dispatch/base'
|
2
|
+
|
3
|
+
module Cheffish
|
4
|
+
class ChefRunListener < Chef::EventDispatch::Base
|
5
|
+
def initialize(run_context)
|
6
|
+
@run_context = run_context
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :run_context
|
10
|
+
|
11
|
+
def run_complete(node)
|
12
|
+
disconnect
|
13
|
+
end
|
14
|
+
|
15
|
+
def run_failed(exception)
|
16
|
+
disconnect
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def disconnect
|
22
|
+
# Stop the servers
|
23
|
+
run_context.cheffish.local_servers.each do |server|
|
24
|
+
server.stop
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'net/ssh'
|
3
|
+
require 'etc'
|
4
|
+
require 'socket'
|
5
|
+
require 'digest/md5'
|
6
|
+
require 'base64'
|
7
|
+
|
8
|
+
module Cheffish
|
9
|
+
class KeyFormatter
|
10
|
+
# Returns nil or key, format
|
11
|
+
def self.decode(str, pass_phrase=nil, filename='')
|
12
|
+
key_format = {}
|
13
|
+
key_format[:format] = format_of(str)
|
14
|
+
|
15
|
+
case key_format[:format]
|
16
|
+
when :openssh
|
17
|
+
key = decode_openssh_key(str, filename)
|
18
|
+
else
|
19
|
+
begin
|
20
|
+
key = OpenSSL::PKey.read(str) { pass_phrase }
|
21
|
+
rescue
|
22
|
+
return nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
key_format[:type] = type_of(key)
|
27
|
+
key_format[:size] = size_of(key)
|
28
|
+
key_format[:pass_phrase] = pass_phrase if pass_phrase
|
29
|
+
# TODO cipher, exponent
|
30
|
+
|
31
|
+
[key, key_format]
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.encode(key, key_format)
|
35
|
+
format = key_format[:format] || :pem
|
36
|
+
case format
|
37
|
+
when :openssh
|
38
|
+
encode_openssh_key(key)
|
39
|
+
when :pem
|
40
|
+
if key_format[:pass_phrase]
|
41
|
+
cipher = key_format[:cipher] || 'DES-EDE3-CBC'
|
42
|
+
key.to_pem(OpenSSL::Cipher.new(cipher), key_format[:pass_phrase])
|
43
|
+
else
|
44
|
+
key.to_pem
|
45
|
+
end
|
46
|
+
when :der
|
47
|
+
key.to_der
|
48
|
+
when :fingerprint, :pkcs1md5fingerprint
|
49
|
+
hexes = Digest::MD5.hexdigest(key.to_der)
|
50
|
+
# Put : between every pair of hexes
|
51
|
+
hexes.scan(/../).join(':')
|
52
|
+
when :rfc4716md5fingerprint
|
53
|
+
type, base64_data, etc = encode_openssh_key(key).split
|
54
|
+
data = Base64.decode64(base64_data)
|
55
|
+
hexes = Digest::MD5.hexdigest(data)
|
56
|
+
hexes.scan(/../).join(':')
|
57
|
+
when :pkcs8sha1fingerprint
|
58
|
+
if RUBY_VERSION.to_f >= 2.0
|
59
|
+
raise "PKCS8 SHA1 not supported in Ruby #{RUBY_VERSION}"
|
60
|
+
end
|
61
|
+
require 'openssl_pkcs8'
|
62
|
+
pkcs8_pem = key.to_pem_pkcs8
|
63
|
+
pkcs8_base64 = pkcs8_pem.split("\n").reject { |l| l =~ /^-----/ }
|
64
|
+
pkcs8_data = Base64.decode64(pkcs8_base64.join)
|
65
|
+
hexes = Digest::SHA1.hexdigest(pkcs8_data)
|
66
|
+
hexes.scan(/../).join(':')
|
67
|
+
else
|
68
|
+
raise "Unrecognized key format #{format}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def self.encode_openssh_key(key)
|
75
|
+
# TODO there really isn't a method somewhere in net/ssh or openssl that does this??
|
76
|
+
type = key.ssh_type
|
77
|
+
data = [ key.to_blob ].pack('m0')
|
78
|
+
"#{type} #{data} #{Etc.getlogin}@#{Socket.gethostname}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.decode_openssh_key(str, filename='')
|
82
|
+
Net::SSH::KeyFactory.load_data_public_key(str, filename)
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.format_of(key_contents)
|
86
|
+
if key_contents.start_with?('-----BEGIN ')
|
87
|
+
:pem
|
88
|
+
elsif key_contents.start_with?('ssh-rsa ') || key_contents.start_with?('ssh-dss ')
|
89
|
+
:openssh
|
90
|
+
else
|
91
|
+
:der
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.type_of(key)
|
96
|
+
case key.class
|
97
|
+
when OpenSSL::PKey::RSA
|
98
|
+
:rsa
|
99
|
+
when OpenSSL::PKey::DSA
|
100
|
+
:dsa
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.size_of(key)
|
105
|
+
# TODO DSA -- this is RSA only
|
106
|
+
key.n.num_bytes * 8
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Cheffish
|
2
|
+
class MergedConfig
|
3
|
+
def initialize(*configs)
|
4
|
+
@configs = configs
|
5
|
+
@merge_arrays = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
attr_reader :configs
|
11
|
+
def merge_arrays(*symbols)
|
12
|
+
if symbols.size > 0
|
13
|
+
symbols.each do |symbol|
|
14
|
+
@merge_arrays[symbol] = true
|
15
|
+
end
|
16
|
+
else
|
17
|
+
@merge_arrays
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](name)
|
22
|
+
if @merge_arrays[name]
|
23
|
+
configs.select { |c| !c[name].nil? }.collect_concat { |c| c[name] }
|
24
|
+
else
|
25
|
+
result_configs = []
|
26
|
+
configs.each do |config|
|
27
|
+
value = config[name]
|
28
|
+
if !value.nil?
|
29
|
+
if value.respond_to?(:keys)
|
30
|
+
result_configs << value
|
31
|
+
elsif result_configs.size > 0
|
32
|
+
return result_configs[0]
|
33
|
+
else
|
34
|
+
return value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
if result_configs.size > 1
|
39
|
+
MergedConfig.new(*result_configs)
|
40
|
+
elsif result_configs.size == 1
|
41
|
+
result_configs[0]
|
42
|
+
else
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def method_missing(name, *args)
|
49
|
+
if args.count > 0
|
50
|
+
raise NoMethodError, "Unexpected method #{name} for MergedConfig with arguments #{args}"
|
51
|
+
else
|
52
|
+
self[name]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def key?(name)
|
57
|
+
configs.any? { |config| config.has_key?(name) }
|
58
|
+
end
|
59
|
+
|
60
|
+
alias_method :has_key?, :key?
|
61
|
+
|
62
|
+
def keys
|
63
|
+
configs.map { |c| c.keys }.flatten(1).uniq
|
64
|
+
end
|
65
|
+
|
66
|
+
def values
|
67
|
+
keys.map { |key| self[key] }
|
68
|
+
end
|
69
|
+
|
70
|
+
def each_pair(&block)
|
71
|
+
each(&block)
|
72
|
+
end
|
73
|
+
|
74
|
+
def each
|
75
|
+
keys.each do |key|
|
76
|
+
if block_given?
|
77
|
+
yield key, self[key]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_hash
|
83
|
+
result = {}
|
84
|
+
each_pair do |key, value|
|
85
|
+
result[key] = value
|
86
|
+
end
|
87
|
+
result
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_s
|
91
|
+
to_hash.to_s
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'cheffish'
|
2
|
+
|
3
|
+
require 'chef/version'
|
4
|
+
require 'chef_zero/server'
|
5
|
+
require 'chef/chef_fs/chef_fs_data_store'
|
6
|
+
require 'chef/chef_fs/config'
|
7
|
+
require 'cheffish/chef_run_data'
|
8
|
+
require 'cheffish/chef_run_listener'
|
9
|
+
require 'chef/client'
|
10
|
+
require 'chef/config'
|
11
|
+
require 'chef_zero/version'
|
12
|
+
require 'cheffish/merged_config'
|
13
|
+
require 'chef/resource/chef_acl'
|
14
|
+
require 'chef/resource/chef_client'
|
15
|
+
require 'chef/resource/chef_container'
|
16
|
+
require 'chef/resource/chef_data_bag'
|
17
|
+
require 'chef/resource/chef_data_bag_item'
|
18
|
+
require 'chef/resource/chef_environment'
|
19
|
+
require 'chef/resource/chef_group'
|
20
|
+
require 'chef/resource/chef_mirror'
|
21
|
+
require 'chef/resource/chef_node'
|
22
|
+
require 'chef/resource/chef_organization'
|
23
|
+
require 'chef/resource/chef_role'
|
24
|
+
require 'chef/resource/chef_user'
|
25
|
+
require 'chef/resource/private_key'
|
26
|
+
require 'chef/resource/public_key'
|
27
|
+
require 'chef/provider/chef_acl'
|
28
|
+
require 'chef/provider/chef_client'
|
29
|
+
require 'chef/provider/chef_container'
|
30
|
+
require 'chef/provider/chef_data_bag'
|
31
|
+
require 'chef/provider/chef_data_bag_item'
|
32
|
+
require 'chef/provider/chef_environment'
|
33
|
+
require 'chef/provider/chef_group'
|
34
|
+
require 'chef/provider/chef_mirror'
|
35
|
+
require 'chef/provider/chef_node'
|
36
|
+
require 'chef/provider/chef_organization'
|
37
|
+
require 'chef/provider/chef_role'
|
38
|
+
require 'chef/provider/chef_user'
|
39
|
+
require 'chef/provider/private_key'
|
40
|
+
require 'chef/provider/public_key'
|
41
|
+
|
42
|
+
|
43
|
+
class Chef
|
44
|
+
module DSL
|
45
|
+
module Recipe
|
46
|
+
def with_chef_data_bag(name)
|
47
|
+
run_context.cheffish.with_data_bag(name, &block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def with_chef_environment(name, &block)
|
51
|
+
run_context.cheffish.with_environment(name, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def with_chef_data_bag_item_encryption(encryption_options, &block)
|
55
|
+
run_context.cheffish.with_data_bag_item_encryption(encryption_options, &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def with_chef_server(server_url, options = {}, &block)
|
59
|
+
run_context.cheffish.with_chef_server({ :chef_server_url => server_url, :options => options }, &block)
|
60
|
+
end
|
61
|
+
|
62
|
+
def with_chef_local_server(options, &block)
|
63
|
+
options[:host] ||= '127.0.0.1'
|
64
|
+
options[:log_level] ||= Chef::Log.level
|
65
|
+
options[:port] ||= ChefZero::VERSION.to_f >= 2.2 ? 8901.upto(9900) : 8901
|
66
|
+
|
67
|
+
# Create the data store chef-zero will use
|
68
|
+
options[:data_store] ||= begin
|
69
|
+
if !options[:chef_repo_path]
|
70
|
+
raise "chef_repo_path must be specified to with_chef_local_server"
|
71
|
+
end
|
72
|
+
|
73
|
+
# Ensure all paths are given
|
74
|
+
%w(acl client cookbook container data_bag environment group node role).each do |type|
|
75
|
+
options["#{type}_path"] ||= begin
|
76
|
+
if options[:chef_repo_path].kind_of?(String)
|
77
|
+
Chef::Util::PathHelper.join(options[:chef_repo_path], "#{type}s")
|
78
|
+
else
|
79
|
+
options[:chef_repo_path].map { |path| Chef::Util::PathHelper.join(path, "#{type}s")}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
chef_fs = Chef::ChefFS::Config.new(options).local_fs
|
85
|
+
chef_fs.write_pretty_json = true
|
86
|
+
Chef::ChefFS::ChefFSDataStore.new(chef_fs)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Start the chef-zero server
|
90
|
+
Chef::Log.info("Starting chef-zero on port #{options[:port]} with repository at #{options[:data_store].chef_fs.fs_description}")
|
91
|
+
chef_zero_server = ChefZero::Server.new(options)
|
92
|
+
chef_zero_server.start_background
|
93
|
+
|
94
|
+
run_context.cheffish.local_servers << chef_zero_server
|
95
|
+
|
96
|
+
with_chef_server(chef_zero_server.url, &block)
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_private_key(name)
|
100
|
+
Cheffish.get_private_key(name, run_context.config)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class Config
|
106
|
+
default(:profile) { ENV['CHEF_PROFILE'] || 'default' }
|
107
|
+
configurable(:private_keys)
|
108
|
+
default(:private_key_paths) { [ Chef::Util::PathHelper.join(config_dir, 'keys'), Chef::Util::PathHelper.join(user_home, '.ssh') ] }
|
109
|
+
default(:private_key_write_path) { private_key_paths.first }
|
110
|
+
end
|
111
|
+
|
112
|
+
class RunContext
|
113
|
+
def cheffish
|
114
|
+
@cheffish ||= begin
|
115
|
+
run_data = Cheffish::ChefRunData.new(config)
|
116
|
+
events.register(Cheffish::ChefRunListener.new(self))
|
117
|
+
run_data
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def config
|
122
|
+
@config ||= Cheffish.profiled_config(Chef::Config)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
Chef::Client.when_run_starts do |run_status|
|
127
|
+
# Pulling on cheffish_run_data makes it initialize right now
|
128
|
+
run_status.run_context.cheffish
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
# Chef 12 moved Chef::Config.path_join to PathHelper.join
|
134
|
+
if Chef::VERSION.to_i >= 12
|
135
|
+
require 'chef/util/path_helper'
|
136
|
+
else
|
137
|
+
require 'chef/config'
|
138
|
+
class Chef
|
139
|
+
class Util
|
140
|
+
class PathHelper
|
141
|
+
def join(*args)
|
142
|
+
Chef::Config.path_join(*args)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#
|
2
|
+
# Author:: John Keiser (<jkeiser@opscode.com>)
|
3
|
+
# Copyright:: Copyright (c) 2012 Opscode, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'chef/version'
|
20
|
+
require 'chef/http'
|
21
|
+
require 'chef/http/authenticator'
|
22
|
+
require 'chef/http/cookie_manager'
|
23
|
+
require 'chef/http/decompressor'
|
24
|
+
require 'chef/http/json_input'
|
25
|
+
require 'chef/http/json_output'
|
26
|
+
if Gem::Version.new(Chef::VERSION) >= Gem::Version.new('11.12')
|
27
|
+
require 'chef/http/remote_request_id'
|
28
|
+
end
|
29
|
+
|
30
|
+
module Cheffish
|
31
|
+
# Exactly like Chef::ServerAPI, but requires you to pass in what keys you want (no defaults)
|
32
|
+
class ServerAPI < Chef::HTTP
|
33
|
+
|
34
|
+
def initialize(url, options = {})
|
35
|
+
super(url, options)
|
36
|
+
root_url = URI.parse(url)
|
37
|
+
root_url.path = ''
|
38
|
+
@root_url = root_url.to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :root_url
|
42
|
+
|
43
|
+
use Chef::HTTP::JSONInput
|
44
|
+
use Chef::HTTP::JSONOutput
|
45
|
+
use Chef::HTTP::CookieManager
|
46
|
+
use Chef::HTTP::Decompressor
|
47
|
+
use Chef::HTTP::Authenticator
|
48
|
+
if Gem::Version.new(Chef::VERSION) >= Gem::Version.new('11.12')
|
49
|
+
use Chef::HTTP::RemoteRequestID
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|