clc-cheffish 0.8.clc

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