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.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/README.md +4 -0
  4. data/Rakefile +23 -0
  5. data/lib/chef/provider/chef_acl.rb +434 -0
  6. data/lib/chef/provider/chef_client.rb +48 -0
  7. data/lib/chef/provider/chef_container.rb +50 -0
  8. data/lib/chef/provider/chef_data_bag.rb +50 -0
  9. data/lib/chef/provider/chef_data_bag_item.rb +273 -0
  10. data/lib/chef/provider/chef_environment.rb +78 -0
  11. data/lib/chef/provider/chef_group.rb +78 -0
  12. data/lib/chef/provider/chef_mirror.rb +138 -0
  13. data/lib/chef/provider/chef_node.rb +82 -0
  14. data/lib/chef/provider/chef_organization.rb +150 -0
  15. data/lib/chef/provider/chef_resolved_cookbooks.rb +41 -0
  16. data/lib/chef/provider/chef_role.rb +79 -0
  17. data/lib/chef/provider/chef_user.rb +53 -0
  18. data/lib/chef/provider/private_key.rb +219 -0
  19. data/lib/chef/provider/public_key.rb +82 -0
  20. data/lib/chef/resource/chef_acl.rb +65 -0
  21. data/lib/chef/resource/chef_client.rb +44 -0
  22. data/lib/chef/resource/chef_container.rb +18 -0
  23. data/lib/chef/resource/chef_data_bag.rb +18 -0
  24. data/lib/chef/resource/chef_data_bag_item.rb +114 -0
  25. data/lib/chef/resource/chef_environment.rb +71 -0
  26. data/lib/chef/resource/chef_group.rb +49 -0
  27. data/lib/chef/resource/chef_mirror.rb +47 -0
  28. data/lib/chef/resource/chef_node.rb +18 -0
  29. data/lib/chef/resource/chef_organization.rb +64 -0
  30. data/lib/chef/resource/chef_resolved_cookbooks.rb +31 -0
  31. data/lib/chef/resource/chef_role.rb +104 -0
  32. data/lib/chef/resource/chef_user.rb +51 -0
  33. data/lib/chef/resource/private_key.rb +44 -0
  34. data/lib/chef/resource/public_key.rb +21 -0
  35. data/lib/cheffish.rb +222 -0
  36. data/lib/cheffish/actor_provider_base.rb +131 -0
  37. data/lib/cheffish/basic_chef_client.rb +115 -0
  38. data/lib/cheffish/chef_provider_base.rb +231 -0
  39. data/lib/cheffish/chef_run_data.rb +19 -0
  40. data/lib/cheffish/chef_run_listener.rb +28 -0
  41. data/lib/cheffish/key_formatter.rb +109 -0
  42. data/lib/cheffish/merged_config.rb +94 -0
  43. data/lib/cheffish/recipe_dsl.rb +147 -0
  44. data/lib/cheffish/server_api.rb +52 -0
  45. data/lib/cheffish/version.rb +3 -0
  46. data/lib/cheffish/with_pattern.rb +21 -0
  47. data/spec/functional/fingerprint_spec.rb +64 -0
  48. data/spec/functional/merged_config_spec.rb +20 -0
  49. data/spec/integration/chef_acl_spec.rb +914 -0
  50. data/spec/integration/chef_client_spec.rb +110 -0
  51. data/spec/integration/chef_container_spec.rb +34 -0
  52. data/spec/integration/chef_group_spec.rb +324 -0
  53. data/spec/integration/chef_mirror_spec.rb +244 -0
  54. data/spec/integration/chef_node_spec.rb +211 -0
  55. data/spec/integration/chef_organization_spec.rb +244 -0
  56. data/spec/integration/chef_user_spec.rb +90 -0
  57. data/spec/integration/private_key_spec.rb +446 -0
  58. data/spec/integration/recipe_dsl_spec.rb +29 -0
  59. data/spec/support/key_support.rb +29 -0
  60. data/spec/support/repository_support.rb +103 -0
  61. data/spec/support/spec_support.rb +176 -0
  62. data/spec/unit/get_private_key_spec.rb +93 -0
  63. 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