knife-solo 0.3.0 → 0.4.0

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 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