knife-solo 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fbd9171b48ca9a42d6e3c65c7a60d40680089574
4
- data.tar.gz: e96f7e04397a1ef4301bfd5f51f1b108d0f9b605
3
+ metadata.gz: e6a9075b35d746ef773b48fab2a4058b9b10b007
4
+ data.tar.gz: 2a4a78e808b7052a9775fb8da0018abfdb07bd33
5
5
  SHA512:
6
- metadata.gz: 04e3c21476fb20b17175d6225e4a64c75e3e7fcab24a295ba1fac27e8dedae394f0306fb333a3cea8e9c183cfda41e13a34ed11ca83ddf3b190300c6b7743ab5
7
- data.tar.gz: 0e0879f24ade781b7b96ebbcd25255f99d1bd9777bfac3b249a30fa0bedee700df02b1aa287816eaeec501eebbe9a299b28110044218a9d7c03e8d12f22a640c
6
+ metadata.gz: a2020e3b7d7971c882e29a4fe1391f96231d10de8643e5a045d612b45181df2f2e95e78728af0ee10be4c5513f2a2df127e5c928620a6988077d8e1e7bb6cacd
7
+ data.tar.gz: b6c1d721762e00bcfbc5cc25626094184c91d5feae3c882947cbf2772a62bd952c47eb5675af38ab07c4f7952cbd8f1012035ddca2b11b9e546e62435ceda8ce
@@ -0,0 +1,2 @@
1
+ service_name: travis-pro
2
+ repo_token: Z5jr8jFYz6ILfHjkgsFw6fd6CEMR8Mmdn
@@ -1,3 +1,49 @@
1
+ # 0.4.0 / 2013-10-30
2
+
3
+ ## Changes and new features
4
+
5
+ * Add SSH option --[no-]host-key-verify ([274])
6
+ * Add --no-sync option to skip syncing and only run Chef ([284])
7
+ * Use Omnibus installer for Debian 7 ([287])
8
+ * Support Raspbian ([295])
9
+ * Support environments (from Chef 11.6.0 on) ([285])
10
+
11
+ ## Fixes
12
+
13
+ * Cache SSH connections ([265])
14
+ * Support Berkshelf 3.0 ([268])
15
+ * Follow symlinks when uploading kitchen ([279], [289])
16
+ * Quote rsync paths to avoid problems with spaces in directory names ([281], [286])
17
+ * Fix precedence of automatic cookbook_path components ([296], [298])
18
+ * Print an error message if a `*_path` configuration value is not a String ([278], [300])
19
+ * Mention knife-solo_data_bag gem in the docs ([83])
20
+ * Pin to ffi 1.9.1 due to issues with newer versions
21
+ * Docs around berkshelf and librarian-chef integrations
22
+
23
+ ## Thanks to our contributors!
24
+
25
+ * [Andreas Josephson][teyrow]
26
+ * [Markus Kern][makern]
27
+ * [Michael Glass][michaelglass]
28
+ * [Mathieu Allaire][allaire]
29
+
30
+ [83]: https://github.com/matschaffer/knife-solo/issues/83
31
+ [265]: https://github.com/matschaffer/knife-solo/issues/265
32
+ [268]: https://github.com/matschaffer/knife-solo/issues/268
33
+ [274]: https://github.com/matschaffer/knife-solo/issues/274
34
+ [278]: https://github.com/matschaffer/knife-solo/issues/278
35
+ [279]: https://github.com/matschaffer/knife-solo/issues/279
36
+ [281]: https://github.com/matschaffer/knife-solo/issues/281
37
+ [284]: https://github.com/matschaffer/knife-solo/issues/284
38
+ [285]: https://github.com/matschaffer/knife-solo/issues/285
39
+ [286]: https://github.com/matschaffer/knife-solo/issues/286
40
+ [287]: https://github.com/matschaffer/knife-solo/issues/287
41
+ [289]: https://github.com/matschaffer/knife-solo/issues/289
42
+ [295]: https://github.com/matschaffer/knife-solo/issues/295
43
+ [296]: https://github.com/matschaffer/knife-solo/issues/296
44
+ [298]: https://github.com/matschaffer/knife-solo/issues/298
45
+ [300]: https://github.com/matschaffer/knife-solo/issues/300
46
+
1
47
  # 0.3.0 / 2013-08-01
2
48
 
3
49
  **NOTE**: This release includes breaking changes. See [upgrade instructions](https://github.com/matschaffer/knife-solo/wiki/Upgrading-to-0.3.0) for more information.
@@ -358,6 +404,8 @@ And a special thanks to [Teemu Matilainen][tmatilai] who is now on the list of d
358
404
  [jgarber]: https://github.com/jgarber
359
405
  [jgrevich]: https://github.com/jgrevich
360
406
  [kmdsbng]: https://github.com/kmdsbng
407
+ [makern]: https://github.com/makern
408
+ [michaelglass]: https://github.com/michaelglass
361
409
  [naoya]: https://github.com/naoya
362
410
  [natlownes]: https://github.com/natlownes
363
411
  [patatepartie]: https://github.com/patatepartie
@@ -373,8 +421,10 @@ And a special thanks to [Teemu Matilainen][tmatilai] who is now on the list of d
373
421
  [searlm]: https://github.com/searlm
374
422
  [skyeagle]: https://github.com/skyeagle
375
423
  [smdern]: https://github.com/smdern
424
+ [teyrow]: https://github.com/teyrow
376
425
  [tknerr]: https://github.com/tknerr
377
426
  [tmatilai]: https://github.com/tmatilai
378
- [tocky]: https://github.com/tocky
427
+ [tocky]: https://github.com/tocky
379
428
  [vjpr]: https://github.com/vjpr
380
429
  [zeph]: https://github.com/zeph
430
+ [allaire]: https://github.com/allaire
@@ -26,6 +26,26 @@ If you need to install from git run:
26
26
 
27
27
  bundle && bundle exec rake install
28
28
 
29
+ == Integration with Berkshelf & Librarian
30
+
31
+ knife-solo also integrates with {Berkshelf}[http://berkshelf.com/] and {Librarian-Chef}[https://github.com/applicationsonline/librarian-chef] for managing your cookbooks out of the box.
32
+
33
+ We try to do this somewhat automatically by first checking if you have either of the two gems installed. If you have both, we will default to Berkshelf.
34
+
35
+ During <tt>knife solo init</tt> we'll generate the appropriate configuration file for either gem. Then during <tt>knife solo cook</tt> we'll run the installation step for whichever configuration file is in your kitchen.
36
+
37
+ Both commands accept option flags to disable this feature if needed (<tt>--no-berkshelf</tt> or <tt>--no-librarian</tt>). The init command also offers enable flags to generate configuration files regardless of whether or not you have the supporting gem installed.
38
+
39
+ More detailed logic for this integration is available in the {Berkshelf & Librarian-Chef integration}[https://github.com/matschaffer/knife-solo/wiki/Berkshelf-&-Librarian-Chef-integration] wiki page.
40
+
41
+ === A note about the "cookbooks" directory
42
+
43
+ One common "gotcha" is that you may have Berkshelf or Librarian-Chef installed without knowing it. This will generate a kitchen that is configured to use them which might not have been your intention. Once the configuration file is available, the <tt>cookbooks</tt> directory will be reserved for cookbooks that are resolved via one of those tools. Any cookbooks that you create there will be removed when you run <tt>knife solo cook</tt>.
44
+
45
+ Please use <tt>site-cookbooks</tt> for custom cookbooks or (better yet) give them their own git repositories which are then included using Berkshelf or Librarian-Chef.
46
+
47
+ == knife-solo commands
48
+
29
49
  === Init command
30
50
 
31
51
  The init command simply takes a name of the directory to store the kitchen structure. Use "." to initialize the current directory.
@@ -74,7 +94,7 @@ This uploads all of your cookbooks in addition to a patch that allows you to use
74
94
 
75
95
  This also supports encrypted data bags. To use them, set the path to your key with +encrypted_data_bag_secret+ in .chef/knife.rb.
76
96
 
77
- The knife command for creating encrypted data bags doesn't work well without a Chef server, so use {this gist}[https://gist.github.com/2896172] as an example on how to create encrypted data bag items on your local file system.
97
+ The built-in knife commands for working with data bags don't work well without a Chef server so we recommend using the {knife-solo_data_bag}[https://github.com/thbishop/knife-solo_data_bag] gem. This will provide "solo" versions of all the typical data bag commands. The default kitchen structure generated by <tt>knife solo init</tt> should be compatible with all the operations listed in the documentation for that gem.
78
98
 
79
99
  === Bootstrap command
80
100
 
data/Rakefile CHANGED
@@ -15,7 +15,6 @@ MANIFEST_IGNORES = %w[
15
15
  script/test
16
16
  ]
17
17
 
18
-
19
18
  namespace :manifest do
20
19
  desc 'Checks for outstanding changes to the manifest'
21
20
  task :verify => :update do
@@ -72,6 +71,11 @@ task 'gh-pages' do
72
71
  end
73
72
 
74
73
  namespace :test do
74
+ Rake::TestTask.new(:performance) do |t|
75
+ t.libs << "test"
76
+ t.test_files = FileList['test/performance/*_test.rb']
77
+ end
78
+
75
79
  Rake::TestTask.new(:integration) do |t|
76
80
  t.libs << "test"
77
81
  t.test_files = FileList['test/integration/*_test.rb']
@@ -41,6 +41,10 @@ class Chef
41
41
  :long => '--sync-only',
42
42
  :description => 'Only sync the cookbook - do not run Chef'
43
43
 
44
+ option :sync,
45
+ :long => '--no-sync',
46
+ :description => 'Do not sync kitchen - only run Chef'
47
+
44
48
  option :berkshelf,
45
49
  :long => '--no-berkshelf',
46
50
  :description => 'Skip berks install'
@@ -76,11 +80,14 @@ class Chef
76
80
  ui.msg "Running Chef on #{host}..."
77
81
 
78
82
  check_chef_version if config[:chef_check]
79
- generate_node_config
80
- berkshelf_install if config_value(:berkshelf, true)
81
- librarian_install if config_value(:librarian, true)
82
- sync_kitchen
83
- generate_solorb
83
+ if config_value(:sync, true)
84
+ generate_node_config
85
+ berkshelf_install if config_value(:berkshelf, true)
86
+ librarian_install if config_value(:librarian, true)
87
+ patch_cookbooks_install
88
+ sync_kitchen
89
+ generate_solorb
90
+ end
84
91
  cook unless config[:sync_only]
85
92
  end
86
93
  end
@@ -104,13 +111,14 @@ class Chef
104
111
  run_portable_mkdir_p(provisioning_path, '0700')
105
112
 
106
113
  cookbook_paths.each_with_index do |path, i|
107
- upload_to_provision_path(path, "/cookbooks-#{i + 1}", 'cookbook_path')
114
+ upload_to_provision_path(path.to_s, "/cookbooks-#{i + 1}", 'cookbook_path')
108
115
  end
109
- upload_to_provision_path(node_config, 'dna.json')
116
+ upload_to_provision_path(node_config.to_s, 'dna.json')
110
117
  upload_to_provision_path(nodes_path, 'nodes')
111
118
  upload_to_provision_path(:role_path, 'roles')
112
119
  upload_to_provision_path(:data_bag_path, 'data_bags')
113
120
  upload_to_provision_path(:encrypted_data_bag_secret, 'data_bag_key')
121
+ upload_to_provision_path(:environment_path, 'environments')
114
122
  end
115
123
 
116
124
  def expand_path(path)
@@ -122,7 +130,7 @@ class Chef
122
130
  end
123
131
 
124
132
  def cookbook_paths
125
- @cookbook_paths ||= expanded_config_paths(:cookbook_path) + [patch_cookbooks_path]
133
+ @cookbook_paths ||= expanded_config_paths(:cookbook_path)
126
134
  end
127
135
 
128
136
  def proxy_setting_keys
@@ -219,6 +227,8 @@ class Chef
219
227
 
220
228
  if src.nil?
221
229
  Chef::Log.debug "'#{key_name}' not set"
230
+ elsif !src.is_a?(String)
231
+ ui.error "#{key_name} is not a String: #{src.inspect}"
222
232
  elsif !File.exist?(src)
223
233
  ui.warn "Local #{key_name} '#{src}' does not exist"
224
234
  else
@@ -237,24 +247,36 @@ class Chef
237
247
  end
238
248
 
239
249
  def rsync(source_path, target_path, extra_opts = '--delete')
240
- cmd = %Q{rsync -rl #{rsync_debug} #{rsync_permissions} --rsh="ssh #{ssh_args}" #{extra_opts}}
241
- cmd << rsync_excludes.map { |ignore| " --exclude '#{ignore}'" }.join
242
- cmd << %Q{ #{adjust_rsync_path_on_client(source_path)} :#{adjust_rsync_path_on_node(target_path)}}
243
- Chef::Log.debug cmd
244
- system! cmd
250
+ cmd = ['rsync', '-rL', rsync_debug, rsync_permissions, %Q{--rsh=ssh #{ssh_args}}, extra_opts]
251
+ cmd += rsync_excludes.map { |ignore| "--exclude=#{ignore}" }
252
+ cmd << adjust_rsync_path_on_client(source_path)
253
+ cmd << %Q{:#{adjust_rsync_path_on_node(target_path)}}
254
+ cmd = cmd.flatten.compact
255
+ Chef::Log.debug cmd.inspect
256
+ system!(*cmd)
245
257
  end
246
258
 
247
259
  def check_chef_version
248
260
  ui.msg "Checking Chef version..."
249
- unless Gem::Requirement.new(CHEF_VERSION_CONSTRAINT).satisfied_by? Gem::Version.new(chef_version)
261
+ unless chef_version_satisfies? CHEF_VERSION_CONSTRAINT
250
262
  raise "Couldn't find Chef #{CHEF_VERSION_CONSTRAINT} on #{host}. Please run `knife solo prepare #{ssh_args}` to ensure Chef is installed and up to date."
251
263
  end
264
+ if node_environment != '_default' && chef_version_satisfies?('<11.6.0')
265
+ ui.warn "Chef version #{chef_version} does not support environments. Environment '#{node_environment}' will be ignored."
266
+ end
267
+ end
268
+
269
+ def chef_version_satisfies?(requirement)
270
+ Gem::Requirement.new(requirement).satisfied_by? Gem::Version.new(chef_version)
252
271
  end
253
272
 
254
273
  # Parses "Chef: x.y.z" from the chef-solo version output
255
274
  def chef_version
256
- cmd = %q{sudo chef-solo --version 2>/dev/null | awk '$1 == "Chef:" {print $2}'}
257
- run_command(cmd).stdout.strip
275
+ # Memoize the version to avoid multiple SSH calls
276
+ @chef_version ||= lambda do
277
+ cmd = %q{sudo chef-solo --version 2>/dev/null | awk '$1 == "Chef:" {print $2}'}
278
+ run_command(cmd).stdout.strip
279
+ end.call
258
280
  end
259
281
 
260
282
  def cook
@@ -268,6 +290,12 @@ class Chef
268
290
  result = stream_command cmd
269
291
  raise "chef-solo failed. See output above." unless result.success?
270
292
  end
293
+
294
+ protected
295
+
296
+ def patch_cookbooks_install
297
+ add_cookbook_path(patch_cookbooks_path)
298
+ end
271
299
  end
272
300
  end
273
301
  end
@@ -32,7 +32,7 @@ class Chef
32
32
  validate!
33
33
  create_kitchen
34
34
  create_config
35
- create_cupboards %w[nodes roles data_bags site-cookbooks cookbooks]
35
+ create_cupboards %w[nodes roles data_bags environments site-cookbooks cookbooks]
36
36
  gitignore %w[/cookbooks/]
37
37
  if (cm = cookbook_manager)
38
38
  cm.bootstrap(@base)
@@ -1,4 +1,5 @@
1
1
  require 'digest/sha1'
2
+ require 'fileutils'
2
3
  require 'knife-solo/cookbook_manager'
3
4
  require 'knife-solo/tools'
4
5
 
@@ -17,7 +18,15 @@ module KnifeSolo
17
18
  def install!
18
19
  path = berkshelf_path
19
20
  ui.msg "Installing Berkshelf cookbooks to '#{path}'..."
20
- ::Berkshelf::Berksfile.from_file('Berksfile').install(:path => path)
21
+
22
+ berksfile = ::Berkshelf::Berksfile.from_file('Berksfile')
23
+ if berksfile.respond_to?(:vendor)
24
+ FileUtils.rm_rf(path)
25
+ berksfile.vendor(path)
26
+ else
27
+ berksfile.install(:path => path)
28
+ end
29
+
21
30
  path
22
31
  end
23
32
 
@@ -63,10 +63,12 @@ module KnifeSolo::Bootstraps
63
63
  def distro
64
64
  return @distro if @distro
65
65
  @distro = case issue
66
- when %r{Debian GNU/Linux 6}
66
+ when %r{Debian GNU/Linux [67]}
67
67
  {:type => (x86? ? "debianoid_omnibus" : "debianoid_gem")}
68
68
  when %r{Debian}
69
69
  {:type => "debianoid_gem"}
70
+ when %r{Raspbian}
71
+ {:type => "debianoid_gem"}
70
72
  when %r{Ubuntu}i
71
73
  {:type => (x86? ? "debianoid_omnibus" : "debianoid_gem")}
72
74
  when %r{Linaro}
@@ -1,6 +1,6 @@
1
1
  module KnifeSolo
2
2
  def self.version
3
- '0.3.0'
3
+ '0.4.0'
4
4
  end
5
5
 
6
6
  def self.post_install_message
@@ -30,6 +30,12 @@ module KnifeSolo
30
30
  :description => 'A JSON string to be added to node config (if it does not exist)',
31
31
  :proc => lambda { |o| JSON.parse(o) },
32
32
  :default => nil
33
+
34
+ option :environment,
35
+ :short => '-E ENVIRONMENT',
36
+ :long => '--environment ENVIRONMENT',
37
+ :description => 'The Chef environment for your node'
38
+
33
39
  end
34
40
  end
35
41
 
@@ -47,6 +53,11 @@ module KnifeSolo
47
53
  config[:chef_node_name] || host
48
54
  end
49
55
 
56
+ def node_environment
57
+ node = node_config.exist? ? JSON.parse(IO.read(node_config)) : {}
58
+ config[:environment] || node['environment'] || '_default'
59
+ end
60
+
50
61
  def generate_node_config
51
62
  if node_config.exist?
52
63
  Chef::Log.debug "Node config '#{node_config}' already exists"
@@ -56,7 +67,8 @@ module KnifeSolo
56
67
  File.open(node_config, 'w') do |f|
57
68
  attributes = config[:json_attributes] || config[:first_boot_attributes] || {}
58
69
  run_list = { :run_list => config[:run_list] || [] }
59
- f.print attributes.merge(run_list).to_json
70
+ environment = config[:environment] ? { :environment => config[:environment] } : {}
71
+ f.print attributes.merge(run_list).merge(environment).to_json
60
72
  end
61
73
  end
62
74
  end
@@ -1,7 +1,8 @@
1
- cookbook_path ["cookbooks", "site-cookbooks"]
2
- node_path "nodes"
3
- role_path "roles"
4
- data_bag_path "data_bags"
1
+ cookbook_path ["cookbooks", "site-cookbooks"]
2
+ node_path "nodes"
3
+ role_path "roles"
4
+ environment_path "environments"
5
+ data_bag_path "data_bags"
5
6
  #encrypted_data_bag_secret "data_bag_key"
6
7
 
7
8
  knife[:berkshelf_path] = "cookbooks"
@@ -4,6 +4,8 @@ nodes_path File.join(base, 'nodes')
4
4
  role_path File.join(base, 'roles')
5
5
  data_bag_path File.join(base, 'data_bags')
6
6
  encrypted_data_bag_secret File.join(base, 'data_bag_key')
7
+ environment_path File.join(base, 'environments')
8
+ environment <%= node_environment.inspect %>
7
9
 
8
10
  cookbook_path []
9
11
  <% cookbook_paths.each_with_index do |path, i| -%>
@@ -1,7 +1,9 @@
1
+
1
2
  module KnifeSolo
2
3
  module SshCommand
3
4
 
4
5
  def self.load_deps
6
+ require 'knife-solo/ssh_connection'
5
7
  require 'net/ssh'
6
8
  end
7
9
 
@@ -59,6 +61,12 @@ module KnifeSolo
59
61
  :long => '--sudo-command SUDO_COMMAND',
60
62
  :description => 'The command to use instead of sudo for admin privileges'
61
63
 
64
+ option :host_key_verify,
65
+ :long => "--[no-]host-key-verify",
66
+ :description => "Verify host key, enabled by default.",
67
+ :boolean => true,
68
+ :default => true
69
+
62
70
  end
63
71
  end
64
72
 
@@ -124,6 +132,10 @@ module KnifeSolo
124
132
  options[:port] = config[:ssh_port] if config[:ssh_port]
125
133
  options[:password] = config[:ssh_password] if config[:ssh_password]
126
134
  options[:keys] = [config[:identity_file]] if config[:identity_file]
135
+ if !config[:host_key_verify]
136
+ options[:paranoid] = false
137
+ options[:user_known_hosts_file] = "/dev/null"
138
+ end
127
139
  options
128
140
  end
129
141
 
@@ -148,9 +160,12 @@ module KnifeSolo
148
160
  host_arg = [user, host].compact.join('@')
149
161
  config_arg = "-F #{config[:ssh_config]}" if config[:ssh_config]
150
162
  ident_arg = "-i #{config[:identity_file]}" if config[:identity_file]
151
- port_arg = "-p #{config[:ssh_port]}" if config[:ssh_port]
163
+ port_arg = "-p #{config[:ssh_port]}" if config[:ssh_port]
164
+ knownhosts_arg = "-o UserKnownHostsFile=#{connection_options[:user_known_hosts_file]}" if config[:host_key_verify] == false
165
+ stricthosts_arg = "-o StrictHostKeyChecking=no" if config[:host_key_verify] == false
166
+
152
167
 
153
- [host_arg, config_arg, ident_arg, port_arg].compact.join(' ')
168
+ [host_arg, config_arg, ident_arg, port_arg, knownhosts_arg, stricthosts_arg].compact.join(' ')
154
169
  end
155
170
 
156
171
  def sudo_command
@@ -161,27 +176,6 @@ module KnifeSolo
161
176
  config[:startup_script]
162
177
  end
163
178
 
164
- class ExecResult
165
- attr_accessor :stdout, :stderr, :exit_code
166
-
167
- def initialize(exit_code = nil)
168
- @exit_code = exit_code
169
- @stdout = ""
170
- @stderr = ""
171
- end
172
-
173
- def success?
174
- exit_code == 0
175
- end
176
-
177
- # Helper to use when raising exceptions since some operations
178
- # (e.g., command not found) error on stdout
179
- def stderr_or_stdout
180
- return stderr unless stderr.empty?
181
- stdout
182
- end
183
- end
184
-
185
179
  def windows_node?
186
180
  return @windows_node unless @windows_node.nil?
187
181
  @windows_node = run_command('ver', :process_sudo => false).stdout =~ /Windows/i
@@ -229,43 +223,14 @@ module KnifeSolo
229
223
  detect_authentication_method
230
224
 
231
225
  Chef::Log.debug("Initial command #{command}")
232
- result = ExecResult.new
233
226
 
234
227
  command = processed_command(command, options)
235
228
  Chef::Log.debug("Running processed command #{command}")
236
229
 
237
- Net::SSH.start(host, user, connection_options) do |ssh|
238
- ssh.open_channel do |channel|
239
- channel.request_pty
240
- channel.exec(command) do |ch, success|
241
- raise "ssh.channel.exec failure" unless success
242
-
243
- channel.on_data do |ch, data| # stdout
244
- if data =~ /^knife sudo password: /
245
- ch.send_data("#{password}\n")
246
- else
247
- Chef::Log.debug("#{command} stdout: #{data}")
248
- ui.stdout << data if options[:streaming]
249
- result.stdout << data
250
- end
251
- end
252
-
253
- channel.on_extended_data do |ch, type, data|
254
- next unless type == 1
255
- Chef::Log.debug("#{command} stderr: #{data}")
256
- ui.stderr << data if options[:streaming]
257
- result.stderr << data
258
- end
259
-
260
- channel.on_request("exit-status") do |ch, data|
261
- result.exit_code = data.read_long
262
- end
263
-
264
- end
265
- ssh.loop
266
- end
267
- end
268
- result
230
+ output = ui.stdout if options[:streaming]
231
+
232
+ @connection ||= SshConnection.new(host, user, connection_options, method(:password))
233
+ @connection.run_command(command, output)
269
234
  end
270
235
 
271
236
  # Runs commands from the specified array until successful.
@@ -276,7 +241,7 @@ module KnifeSolo
276
241
  result = run_command(command, options)
277
242
  return result if result.success?
278
243
  end
279
- ExecResult.new(1)
244
+ SshConnection::ExecResult.new(1)
280
245
  end
281
246
 
282
247
  # TODO:
@@ -0,0 +1,78 @@
1
+ require 'net/ssh'
2
+
3
+ module KnifeSolo
4
+ class SshConnection
5
+ class ExecResult
6
+ attr_accessor :stdout, :stderr, :exit_code
7
+
8
+ def initialize(exit_code = nil)
9
+ @exit_code = exit_code
10
+ @stdout = ""
11
+ @stderr = ""
12
+ end
13
+
14
+ def success?
15
+ exit_code == 0
16
+ end
17
+
18
+ # Helper to use when raising exceptions since some operations
19
+ # (e.g., command not found) error on stdout
20
+ def stderr_or_stdout
21
+ return stderr unless stderr.empty?
22
+ stdout
23
+ end
24
+ end
25
+
26
+ def initialize(host, user, connection_options, sudo_password_hook)
27
+ @host = host
28
+ @user = user
29
+ @connection_options = connection_options
30
+ @password_hook = sudo_password_hook
31
+ end
32
+
33
+ attr_reader :host, :user, :connection_options
34
+
35
+ def session
36
+ @session ||= Net::SSH.start(host, user, connection_options)
37
+ end
38
+
39
+ def password
40
+ @password ||= @password_hook.call
41
+ end
42
+
43
+ def run_command(command, output = nil)
44
+ result = ExecResult.new
45
+
46
+ session.open_channel do |channel|
47
+ channel.request_pty
48
+ channel.exec(command) do |ch, success|
49
+ raise "ssh.channel.exec failure" unless success
50
+
51
+ channel.on_data do |ch, data| # stdout
52
+ if data =~ /^knife sudo password: /
53
+ ch.send_data("#{password}\n")
54
+ else
55
+ Chef::Log.debug("#{command} stdout: #{data}")
56
+ output << data if output
57
+ result.stdout << data
58
+ end
59
+ end
60
+
61
+ channel.on_extended_data do |ch, type, data|
62
+ next unless type == 1
63
+ Chef::Log.debug("#{command} stderr: #{data}")
64
+ output << data if output
65
+ result.stderr << data
66
+ end
67
+
68
+ channel.on_request("exit-status") do |ch, data|
69
+ result.exit_code = data.read_long
70
+ end
71
+
72
+ end
73
+ end.wait
74
+
75
+ result
76
+ end
77
+ end
78
+ end
@@ -1,7 +1,7 @@
1
1
  module KnifeSolo
2
2
  module Tools
3
- def system!(command)
4
- raise "Failed to launch command #{command}" unless system(command)
3
+ def system!(*command)
4
+ raise "Failed to launch command #{command}" unless system(*command)
5
5
  end
6
6
 
7
7
  def windows_client?
@@ -0,0 +1,25 @@
1
+ module Environment
2
+ def setup
3
+ super
4
+ FileUtils.cp_r $base_dir.join('support', 'environment_cookbook'), 'site-cookbooks/environment_cookbook'
5
+ FileUtils.cp $base_dir.join('support', 'test_environment.json'), 'environments/test_environment.json'
6
+ end
7
+
8
+ def cook_environment(node)
9
+ write_nodefile(node)
10
+ assert_subcommand "cook"
11
+ `ssh #{connection_string} cat /etc/chef_environment`
12
+ end
13
+
14
+ # Test that chef picks up environments properly
15
+ # NOTE: This shells out to ssh, so may not be windows-compatible
16
+ def test_chef_environment
17
+ # If no environment is specified chef needs to use "_default" and attribute from cookbook
18
+ actual = cook_environment(run_list: ["recipe[environment_cookbook]"])
19
+ assert_equal "_default/untouched", actual
20
+
21
+ # If one is specified chef needs to pick it up and get override attibute
22
+ actual = cook_environment(run_list: ["recipe[environment_cookbook]"], environment: 'test_environment')
23
+ assert_equal "test_environment/test_env_was_here", actual
24
+ end
25
+ end
@@ -6,7 +6,7 @@ class Debian7KnifeBootstrapTest < IntegrationTest
6
6
  end
7
7
 
8
8
  def image_id
9
- "ami-1d620e74"
9
+ "ami-9e95e8f7"
10
10
  end
11
11
 
12
12
  def prepare_server
@@ -13,4 +13,5 @@ class Ubuntu12_04Test < IntegrationTest
13
13
  include Apache2Cook
14
14
  include EncryptedDataBag
15
15
  include CachePathUsage
16
+ include Environment
16
17
  end
@@ -0,0 +1,38 @@
1
+ require 'test_helper'
2
+ require 'support/kitchen_helper'
3
+ require 'chef/knife/solo_prepare'
4
+
5
+ require 'knife-solo/bootstraps'
6
+ require 'knife-solo/bootstraps/linux'
7
+
8
+ require 'knife-solo/ssh_connection'
9
+
10
+ require 'benchmark'
11
+
12
+ class KnifeSolo::Bootstraps::Linux
13
+ def debianoid_omnibus_install
14
+ run_command("echo apt-get update")
15
+ run_command("echo apt-get install")
16
+ run_command("echo curl omnibus")
17
+ run_command("echo run omnibus")
18
+ end
19
+ end
20
+
21
+ class SshPerformanceTest < TestCase
22
+ include KitchenHelper
23
+
24
+ def do_it
25
+ # NOTE: Assumes user & host on @matschaffer's machine. Modify or paramaterize if needed.
26
+ 10.times { knife_command(Chef::Knife::SoloPrepare, "ubuntu@172.16.20.133").run }
27
+ end
28
+
29
+ def test_ssh_performance_of_prepare
30
+ in_kitchen do
31
+ Benchmark.bmbm do |b|
32
+ b.report("cached attributes: ") do
33
+ do_it
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -56,11 +56,6 @@ class SoloCookTest < TestCase
56
56
  assert Dir.exist?(path), "patch_cookbooks_path is not a directory"
57
57
  end
58
58
 
59
- def test_cookbook_paths_includes_patch_cookbooks
60
- cmd = command
61
- assert_equal cmd.patch_cookbooks_path, cmd.cookbook_paths.last, "patch_cookbooks is not included"
62
- end
63
-
64
59
  def test_cookbook_paths_expands_paths
65
60
  cmd = command
66
61
  Chef::Config.cookbook_path = ["mycookbooks", "/some/other/path"]
@@ -84,9 +79,18 @@ class SoloCookTest < TestCase
84
79
  assert_equal({ :http_proxy => "http://proxy:3128" }, conf)
85
80
  end
86
81
 
82
+ def test_adds_patch_cookboks_with_lowest_precedence
83
+ in_kitchen do
84
+ cmd = command("somehost")
85
+ cmd.run
86
+ #note: cookbook_paths are in order of precedence (low->high)
87
+ assert_equal cmd.patch_cookbooks_path, cmd.cookbook_paths[0]
88
+ end
89
+ end
90
+
87
91
  def test_does_not_run_berkshelf_if_no_berkfile
88
92
  in_kitchen do
89
- Berkshelf::Berksfile.any_instance.expects(:install).never
93
+ Berkshelf::Berksfile.any_instance.expects(:vendor).never
90
94
  command("somehost").run
91
95
  end
92
96
  end
@@ -94,7 +98,7 @@ class SoloCookTest < TestCase
94
98
  def test_runs_berkshelf_if_berkfile_found
95
99
  in_kitchen do
96
100
  FileUtils.touch "Berksfile"
97
- Berkshelf::Berksfile.any_instance.expects(:install)
101
+ Berkshelf::Berksfile.any_instance.expects(:vendor)
98
102
  command("somehost").run
99
103
  end
100
104
  end
@@ -102,7 +106,7 @@ class SoloCookTest < TestCase
102
106
  def test_does_not_run_berkshelf_if_denied_by_option
103
107
  in_kitchen do
104
108
  FileUtils.touch "Berksfile"
105
- Berkshelf::Berksfile.any_instance.expects(:install).never
109
+ Berkshelf::Berksfile.any_instance.expects(:vendor).never
106
110
  command("somehost", "--no-berkshelf").run
107
111
  end
108
112
  end
@@ -113,7 +117,7 @@ class SoloCookTest < TestCase
113
117
  cmd = command("somehost")
114
118
  cmd.ui.expects(:err).with(regexp_matches(/berkshelf gem/))
115
119
  KnifeSolo::Berkshelf.expects(:load_gem).returns(false)
116
- Berkshelf::Berksfile.any_instance.expects(:install).never
120
+ Berkshelf::Berksfile.any_instance.expects(:vendor).never
117
121
  cmd.run
118
122
  end
119
123
  end
@@ -123,7 +127,7 @@ class SoloCookTest < TestCase
123
127
  cmd = command("somehost")
124
128
  cmd.ui.expects(:err).never
125
129
  KnifeSolo::Berkshelf.expects(:load_gem).never
126
- Berkshelf::Berksfile.any_instance.expects(:install).never
130
+ Berkshelf::Berksfile.any_instance.expects(:vendor).never
127
131
  cmd.run
128
132
  end
129
133
  end
@@ -132,9 +136,10 @@ class SoloCookTest < TestCase
132
136
  in_kitchen do
133
137
  FileUtils.touch "Berksfile"
134
138
  KnifeSolo::Berkshelf.any_instance.stubs(:berkshelf_path).returns("berkshelf/path")
139
+ Berkshelf::Berksfile.any_instance.stubs(:vendor)
135
140
  cmd = command("somehost")
136
141
  cmd.run
137
- assert_equal File.join(Dir.pwd, "berkshelf/path"), cmd.cookbook_paths[0].to_s
142
+ assert_equal File.join(Dir.pwd, "berkshelf/path"), cmd.cookbook_paths[1].to_s
138
143
  end
139
144
  end
140
145
 
@@ -186,9 +191,10 @@ class SoloCookTest < TestCase
186
191
  ENV['LIBRARIAN_CHEF_PATH'] = "librarian/path"
187
192
  in_kitchen do
188
193
  FileUtils.touch "Cheffile"
194
+ Librarian::Action::Install.any_instance.stubs(:run)
189
195
  cmd = command("somehost")
190
196
  cmd.run
191
- assert_equal File.join(Dir.pwd, "librarian/path"), cmd.cookbook_paths[0].to_s
197
+ assert_equal File.join(Dir.pwd, "librarian/path"), cmd.cookbook_paths[1].to_s
192
198
  end
193
199
  end
194
200
 
@@ -247,6 +253,14 @@ class SoloCookTest < TestCase
247
253
  end
248
254
  end
249
255
 
256
+ def test_does_not_sync_if_no_sync_specified
257
+ in_kitchen do
258
+ cmd = command("somehost", "--no-sync")
259
+ cmd.expects(:sync_kitchen).never
260
+ cmd.run
261
+ end
262
+ end
263
+
250
264
  def test_passes_node_name_to_chef_solo
251
265
  assert_chef_solo_option "--node-name=mynode", "-N mynode"
252
266
  end
@@ -82,6 +82,18 @@ class SshCommandTest < TestCase
82
82
  assert_equal "source ~/.bashrc && echo $TEST_PROP", cmd.processed_command("echo $TEST_PROP")
83
83
  end
84
84
 
85
+ def test_handle_no_host_key_verify
86
+ cmd = command("10.0.0.1", "--no-host-key-verify")
87
+ assert_equal false, cmd.connection_options[:paranoid]
88
+ assert_equal "/dev/null", cmd.connection_options[:user_known_hosts_file]
89
+ end
90
+
91
+ def test_handle_default_host_key_verify_is_paranoid
92
+ cmd = command("10.0.0.1")
93
+ assert_nil(cmd.connection_options[:paranoid]) # Net:SSH default is :paranoid => true
94
+ assert_nil(cmd.connection_options[:user_known_hosts_file])
95
+ end
96
+
85
97
  def test_builds_cli_ssh_args
86
98
  DummySshCommand.any_instance.stubs(:try_connection)
87
99
 
@@ -103,6 +115,10 @@ class SshCommandTest < TestCase
103
115
  cmd = command("usertest@10.0.0.1", "--ssh-port=222")
104
116
  cmd.validate_ssh_options!
105
117
  assert_equal "usertest@10.0.0.1 -p 222", cmd.ssh_args
118
+
119
+ cmd = command("usertest@10.0.0.1", "--no-host-key-verify")
120
+ cmd.validate_ssh_options!
121
+ assert_equal "usertest@10.0.0.1 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no", cmd.ssh_args
106
122
  end
107
123
 
108
124
  def test_barks_without_atleast_a_hostname
@@ -127,14 +143,14 @@ class SshCommandTest < TestCase
127
143
  def test_run_with_fallbacks_returns_error_if_all_fail
128
144
  cmd = command
129
145
  cmd.expects(:run_command).twice.returns(result(64, "fail"))
130
-
146
+
131
147
  res = cmd.run_with_fallbacks(["foo", "bar"])
132
148
  assert_equal "", res.stdout
133
149
  assert_equal 1, res.exit_code
134
150
  end
135
151
 
136
152
  def result(code, stdout = "")
137
- res = KnifeSolo::SshCommand::ExecResult.new(code)
153
+ res = KnifeSolo::SshConnection::ExecResult.new(code)
138
154
  res.stdout = stdout
139
155
  res
140
156
  end
@@ -0,0 +1 @@
1
+ default['environment']['test_attribute'] = "untouched"
@@ -0,0 +1,6 @@
1
+ name "environment"
2
+ maintainer "Mat Schaffer"
3
+ maintainer_email "mat@schaffer.me"
4
+ license "MIT"
5
+ description "Spits out a file containing the current chef environment and a test attribute"
6
+ version "0.0.1"
@@ -0,0 +1,4 @@
1
+ file "/etc/chef_environment" do
2
+ mode 0644
3
+ content "#{node.chef_environment}/#{node['environment']['test_attribute']}"
4
+ end
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "test_environment",
3
+ "description": "An environment for integration tests",
4
+ "override_attributes": {
5
+ "environment": {
6
+ "test_attribute": "test_env_was_here"
7
+ }
8
+ },
9
+ "json_class": "Chef::Environment",
10
+ "chef_type": "environment"
11
+ }
@@ -1,6 +1,9 @@
1
1
  require 'rubygems'
2
2
  require 'bundler/setup'
3
3
 
4
+ require 'coveralls'
5
+ Coveralls.wear!
6
+
4
7
  require 'minitest/autorun'
5
8
  require 'mocha/setup'
6
9
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-solo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mat Schaffer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-08-02 00:00:00.000000000 Z
11
+ date: 2013-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: berkshelf
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '>='
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 3.0.0.beta.2
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '>='
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: 3.0.0.beta.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ffi
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - <
46
+ - !ruby/object:Gem::Version
47
+ version: 1.9.1
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - <
53
+ - !ruby/object:Gem::Version
54
+ version: 1.9.1
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: fog
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -136,6 +150,20 @@ dependencies:
136
150
  - - ~>
137
151
  - !ruby/object:Gem::Version
138
152
  version: '3.12'
153
+ - !ruby/object:Gem::Dependency
154
+ name: coveralls
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - '>='
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
139
167
  - !ruby/object:Gem::Dependency
140
168
  name: chef
141
169
  requirement: !ruby/object:Gem::Requirement
@@ -190,6 +218,7 @@ executables: []
190
218
  extensions: []
191
219
  extra_rdoc_files: []
192
220
  files:
221
+ - .coveralls.yml
193
222
  - CHANGELOG.md
194
223
  - LICENSE
195
224
  - README.rdoc
@@ -221,6 +250,7 @@ files:
221
250
  - lib/knife-solo/resources/knife.rb
222
251
  - lib/knife-solo/resources/solo.rb.erb
223
252
  - lib/knife-solo/ssh_command.rb
253
+ - lib/knife-solo/ssh_connection.rb
224
254
  - lib/knife-solo/tools.rb
225
255
  - test/bootstraps_test.rb
226
256
  - test/deprecated_command_test.rb
@@ -233,6 +263,7 @@ files:
233
263
  - test/integration/cases/cache_path_usage.rb
234
264
  - test/integration/cases/empty_cook.rb
235
265
  - test/integration/cases/encrypted_data_bag.rb
266
+ - test/integration/cases/environment.rb
236
267
  - test/integration/cases/knife_bootstrap.rb
237
268
  - test/integration/centos5_8_test.rb
238
269
  - test/integration/centos6_3_test.rb
@@ -249,6 +280,7 @@ files:
249
280
  - test/knife_bootstrap_test.rb
250
281
  - test/minitest/parallel.rb
251
282
  - test/node_config_command_test.rb
283
+ - test/performance/ssh_performance_test.rb
252
284
  - test/solo_bootstrap_test.rb
253
285
  - test/solo_clean_test.rb
254
286
  - test/solo_cook_test.rb
@@ -260,6 +292,9 @@ files:
260
292
  - test/support/config.yml.example
261
293
  - test/support/data_bag_key
262
294
  - test/support/ec2_runner.rb
295
+ - test/support/environment_cookbook/attributes/default.rb
296
+ - test/support/environment_cookbook/metadata.rb
297
+ - test/support/environment_cookbook/recipes/default.rb
263
298
  - test/support/integration_test.rb
264
299
  - test/support/issue_files/gentoo2011
265
300
  - test/support/issue_files/sles11-sp1
@@ -270,6 +305,7 @@ files:
270
305
  - test/support/secret_cookbook/recipes/default.rb
271
306
  - test/support/ssh_config
272
307
  - test/support/test_case.rb
308
+ - test/support/test_environment.json
273
309
  - test/support/validation_helper.rb
274
310
  - test/test_helper.rb
275
311
  - test/tools_test.rb
@@ -341,6 +377,7 @@ test_files:
341
377
  - test/integration/cases/cache_path_usage.rb
342
378
  - test/integration/cases/empty_cook.rb
343
379
  - test/integration/cases/encrypted_data_bag.rb
380
+ - test/integration/cases/environment.rb
344
381
  - test/integration/cases/knife_bootstrap.rb
345
382
  - test/integration/centos5_8_test.rb
346
383
  - test/integration/centos6_3_test.rb
@@ -357,6 +394,7 @@ test_files:
357
394
  - test/knife_bootstrap_test.rb
358
395
  - test/minitest/parallel.rb
359
396
  - test/node_config_command_test.rb
397
+ - test/performance/ssh_performance_test.rb
360
398
  - test/solo_bootstrap_test.rb
361
399
  - test/solo_clean_test.rb
362
400
  - test/solo_cook_test.rb
@@ -368,6 +406,9 @@ test_files:
368
406
  - test/support/config.yml.example
369
407
  - test/support/data_bag_key
370
408
  - test/support/ec2_runner.rb
409
+ - test/support/environment_cookbook/attributes/default.rb
410
+ - test/support/environment_cookbook/metadata.rb
411
+ - test/support/environment_cookbook/recipes/default.rb
371
412
  - test/support/integration_test.rb
372
413
  - test/support/issue_files/gentoo2011
373
414
  - test/support/issue_files/sles11-sp1
@@ -378,6 +419,7 @@ test_files:
378
419
  - test/support/secret_cookbook/recipes/default.rb
379
420
  - test/support/ssh_config
380
421
  - test/support/test_case.rb
422
+ - test/support/test_environment.json
381
423
  - test/support/validation_helper.rb
382
424
  - test/test_helper.rb
383
425
  - test/tools_test.rb