clc-cheffish 0.8.clc
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.
- 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
|