opscode-pushy-client 2.100.0 → 3.0.0.pre

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
  SHA256:
3
- metadata.gz: 44b1f6febf06aecd9bffcece1c65126a9e58d9a4fdd1925506782f0400cdf189
4
- data.tar.gz: 8e288e08d58790b64be013bfea14ca6b1838c4e136aa9a19cc1c116a71ccb1ec
3
+ metadata.gz: c77b2f636067d317b9810cc3d5835084e6eb9ce4973af66c40a340caa44e534a
4
+ data.tar.gz: f01c7d95292f4d1d4b6beef2bc695ace5eca8e65b999f0708875b5557950b286
5
5
  SHA512:
6
- metadata.gz: 52ab8e2aa49d1b0af72a1474d798ca711b878fdc1d8f8686c8b3ec9d182770996afd18559af44987a497a3257c4ff38c767881b776d5a830ae664efc8f520801
7
- data.tar.gz: d01ca100485a00b6cdfb8eff100993de90791ef133ac92117d18dca5ac121bbd65a309f1ac9aecf9230340dd023370723b0b3fb1bbb8b618c4178cc0febf32d1
6
+ metadata.gz: bad3691458ecc47aa5167e2f0fa80b039ad318287e0d412f1daf26039a858f78134fe855efcb80bfd0e935eb977ce328a7c31bbcb19142f7013fa36992096fb9
7
+ data.tar.gz: 8f830c8e0af5bfd83e6879cacb7759f95e9835f368421997f1cac034b3587564eb6e38e5cbb63861cf40da6068fcf403711c26467502ef12ed563de83934c907
data/Gemfile CHANGED
@@ -9,6 +9,7 @@ end
9
9
  platforms :mswin, :mingw do
10
10
  gem "ffi"
11
11
  gem "rdp-ruby-wmi"
12
+ gem "windows-api"
12
13
  gem "windows-pr"
13
14
  gem "win32-api"
14
15
  gem "win32-dir"
File without changes
@@ -41,6 +41,7 @@ class PushyClient
41
41
  @file_dir = options[:file_dir] || '/tmp/pushy'
42
42
  @file_dir_expiry = options[:file_dir_expiry] || 86400
43
43
  @allowed_overwritable_env_vars = options[:allowed_overwritable_env_vars]
44
+ @max_body_size = options[:max_body_size] || 63000
44
45
 
45
46
  @allow_unencrypted = options[:allow_unencrypted] || false
46
47
  @client_curve_pub_key, @client_curve_sec_key = ZMQ::Util.curve_keypair
@@ -79,6 +80,7 @@ class PushyClient
79
80
  attr_accessor :node_name
80
81
  attr_accessor :hostname
81
82
  attr_accessor :whitelist
83
+ attr_accessor :max_body_size
82
84
  attr_reader :incarnation_id
83
85
  attr_reader :legacy_mode # indicate we've fallen back to 1.x
84
86
  attr_reader :allowed_overwritable_env_vars
@@ -70,6 +70,12 @@ class PushyClient
70
70
  :description => "The node name for this client",
71
71
  :proc => nil
72
72
 
73
+ option :max_body_size,
74
+ :short => "-M MAX_BODY_SIZE",
75
+ :long => "--max-body-size MAX_BODY_SIZE",
76
+ :description => "MAX_BODY_SIZE of STDER/SDTOUT for this client",
77
+ :proc => nil
78
+
73
79
  option :chef_server_url,
74
80
  :short => "-S CHEFSERVERURL",
75
81
  :long => "--server CHEFSERVERURL",
@@ -133,7 +139,8 @@ class PushyClient
133
139
  :hostname => ohai[:hostname],
134
140
  :filedir => Chef::Config[:file_dir],
135
141
  :allow_unencrypted => Chef::Config[:allow_unencrypted],
136
- :allowed_overwritable_env_vars => Chef::Config[:allowed_overwritable_env_vars]
142
+ :allowed_overwritable_env_vars => Chef::Config[:allowed_overwritable_env_vars],
143
+ :max_body_size => Chef::Config[:max_body_size]
137
144
  )
138
145
 
139
146
  @client.start
@@ -60,14 +60,6 @@ class PushyClient
60
60
  end
61
61
  end
62
62
 
63
- # The maximum size, in bytes, allowed for a message body. This is not
64
- # configurable because it is configurable (though not documented) on the
65
- # server, and we don't want to make users have to sync the two values.
66
- # The max on the server is actually 65536, but we leave a little room since
67
- # the server is measuring the signed message and we're just counting
68
- # the size of the stderr and stdout.
69
- MAX_BODY_SIZE = 63000
70
-
71
63
  def initialize(client)
72
64
  @client = client
73
65
  # We synchronize on this when we change the socket (so if you want a
@@ -114,6 +106,7 @@ class PushyClient
114
106
  @server_public_key = OpenSSL::PKey::RSA.new(client.config['public_key'])
115
107
  @client_private_key = ProtocolHandler::load_key(client.client_key)
116
108
  @max_message_skew = client.config['max_message_skew']
109
+ @max_body_size = client.config['max_body_size']
117
110
 
118
111
  if client.using_curve
119
112
  server_curve_pub_key = client.config['curve_public_key']
@@ -417,9 +410,8 @@ class PushyClient
417
410
  def validate_params(params = {})
418
411
  stdout_bytes = params[:stdout].to_s.bytesize
419
412
  stderr_bytes = params[:stderr].to_s.bytesize
420
-
421
- if (stdout_bytes + stderr_bytes) > MAX_BODY_SIZE
422
- Chef::Log.warn("Command output too long. Will not be sent to server.")
413
+ if (stdout_bytes + stderr_bytes) > client.max_body_size.to_i
414
+ Chef::Log.warn("Command output #{stdout_bytes + stderr_bytes} is larger than the Client MAX_BODY_SIZE of #{client.max_body_size.to_i}")
423
415
  params.delete_if { |k| [:stdout, :stderr].include? k }
424
416
  else
425
417
  params
@@ -471,7 +463,7 @@ class PushyClient
471
463
  @socket_lock.synchronize do
472
464
  @command_socket_outgoing_seq += 1
473
465
  json[:sequence] = @command_socket_outgoing_seq
474
- message = JSON.generate(json)
466
+ message = validate_and_generate_json(json)
475
467
  if @command_socket
476
468
  ProtocolHandler::send_signed_message(@command_socket, method, @client_private_key, @session_key, message, @client)
477
469
  else
@@ -480,6 +472,25 @@ class PushyClient
480
472
  end
481
473
  end
482
474
 
475
+ # This method validates and generates the JSON message
476
+ # params user packet
477
+ # Returns Waring or JSON message
478
+ def validate_and_generate_json(json)
479
+ message = JSON.generate(json)
480
+ if message.to_s.bytesize > client.max_body_size.to_i
481
+ deleted_packet = json.select { |k| [:stdout, :stderr].include? k }
482
+ json.delete_if { |k| [:stdout, :stderr].include? k }
483
+ Chef::Log.warn("Deleted packet #{deleted_packet} from Client packet") if deleted_packet.to_s.size > 1
484
+ # Re-generate json message
485
+ message = JSON.generate(json)
486
+ end
487
+ if message.to_s.bytesize > client.max_body_size.to_i
488
+ Chef::Log.warn("Client message size #{message.to_s.bytesize} is larger than the Client MAX_BODY_SIZE of #{client.max_body_size.to_i}")
489
+ else
490
+ message
491
+ end
492
+ end
493
+
483
494
  def self.send_signed_message(socket, method, client_private_key, session_key, message, client)
484
495
  auth = case method
485
496
  when :rsa2048_sha1
@@ -0,0 +1,308 @@
1
+ # @copyright Copyright 2014 Chef Software, Inc. All Rights Reserved.
2
+ #
3
+ # This file is provided to you under the Apache License,
4
+ # Version 2.0 (the "License"); you may not use this file
5
+ # except in compliance with the License. You may obtain
6
+ # a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing,
11
+ # software distributed under the License is distributed on an
12
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
13
+ # KIND, either express or implied. See the License for the
14
+ # specific language governing permissions and limitations
15
+ # under the License.
16
+ #
17
+
18
+ # This is needed to fix an issue in win32-process v. 0.6.5
19
+ # where Process.wait blocks the entire Ruby interpreter
20
+ # for the duration of the process.
21
+ require 'chef/platform'
22
+ require 'mixlib/shellout'
23
+ if Chef::Platform.windows?
24
+ require 'pushy_client/win32'
25
+ end
26
+
27
+ class PushyClient
28
+ class JobRunner
29
+ def initialize(client)
30
+ @client = client
31
+ @on_job_state_change = []
32
+
33
+ set_job_state(:idle)
34
+ @pid = nil
35
+ @process_thread = nil
36
+
37
+ # Keep job state and process state in sync
38
+ @state_lock = Mutex.new
39
+ end
40
+
41
+ attr_reader :client
42
+ attr_reader :state
43
+ attr_reader :job_id
44
+ attr_reader :command
45
+ attr_reader :lockfile
46
+
47
+ def node_name
48
+ client.node_name
49
+ end
50
+
51
+ def start
52
+ end
53
+
54
+ def stop
55
+ if @state == :running
56
+ kill_process
57
+ end
58
+ set_job_state(:idle)
59
+ end
60
+
61
+ def reconfigure
62
+ # We have no configuration, and keep state between reconfigures
63
+ end
64
+
65
+ def commit(job_id, command, opts)
66
+ @opts = opts
67
+ @state_lock.synchronize do
68
+ if @state == :idle
69
+ # If we're being asked to lock
70
+ if client.whitelist[command] &&
71
+ client.whitelist[command].is_a?(Hash) &&
72
+ client.whitelist[command][:lock]
73
+ # If the command is chef-client
74
+ # We don't want to run if there is already another instance of chef-client going,
75
+ # so we check to see if there is a runlock on chef-client before committing. This
76
+ # currently only works in versions of chef where runlock has been implemented.
77
+
78
+ # The location of our lockfile
79
+ if client.whitelist[command][:lock] == true
80
+ lockfile_location = Chef::Config[:lockfile] || "#{Chef::Config[:file_cache_path]}/chef-client-running.pid"
81
+ else
82
+ lockfile_location = client.whitelist[command][:lock]
83
+ end
84
+ # Open the Lockfile
85
+ begin
86
+ @lockfile = File.open(lockfile_location, 'w')
87
+ locked = lockfile.flock(File::LOCK_EX|File::LOCK_NB)
88
+ unless locked
89
+ Chef::Log.info("[#{node_name}] Received commit #{job_id} but is already running '#{command}'")
90
+ client.send_command(:nack_commit, job_id)
91
+ return false
92
+ end
93
+ rescue Errno::ENOENT
94
+ end
95
+ elsif client.whitelist[command]
96
+ user_ok = check_user(job_id)
97
+ dir_ok = check_dir(job_id)
98
+ file_ok = check_file(job_id)
99
+ if user_ok && dir_ok && file_ok
100
+ Chef::Log.info("[#{node_name}] Received commit #{job_id}")
101
+ set_job_state(:committed, job_id, command)
102
+ client.send_command(:ack_commit, job_id)
103
+ true
104
+ else
105
+ client.send_command(:nack_commit, job_id)
106
+ end
107
+ else
108
+ Chef::Log.error("[#{node_name}] Received commit #{job_id}, but command '#{command}' is not in the whitelist!")
109
+ client.send_command(:nack_commit, job_id)
110
+ false
111
+ end
112
+ else
113
+ Chef::Log.warn("[#{node_name}] Received commit #{job_id} but current state is #{@state} #{@job_id}")
114
+ client.send_command(:nack_commit, job_id)
115
+ false
116
+ end
117
+ end
118
+ end
119
+
120
+ def run(job_id)
121
+ @state_lock.synchronize do
122
+ if @state == :committed && @job_id == job_id
123
+ Chef::Log.info("[#{node_name}] Received run #{job_id}")
124
+ pid, process_thread = start_process
125
+ set_job_state(:running, job_id, @command, pid, process_thread)
126
+ client.send_command(:ack_run, job_id)
127
+ true
128
+ else
129
+ Chef::Log.warn("[#{node_name}] Received run #{job_id} but current state is #{@state} #{@job_id}")
130
+ client.send_command(:nack_run, job_id)
131
+ false
132
+ end
133
+ end
134
+ end
135
+
136
+ def abort
137
+ Chef::Log.info("[#{node_name}] Received abort")
138
+ @state_lock.synchronize do
139
+ _job_id = job_id
140
+ stop
141
+ client.send_command(:aborted, _job_id)
142
+ end
143
+ end
144
+
145
+ def job_state
146
+ @state_lock.synchronize do
147
+ get_job_state
148
+ end
149
+ end
150
+
151
+ def on_job_state_change(&block)
152
+ @on_job_state_change << block
153
+ end
154
+
155
+ private
156
+
157
+ def get_job_state
158
+ {
159
+ :state => @state,
160
+ :job_id => @job_id,
161
+ :command => @command
162
+ }
163
+ end
164
+
165
+ def set_job_state(state, job_id = nil, command = nil, pid = nil, process_thread = nil)
166
+ if state == :idle || state == :running
167
+ if @lockfile
168
+ # If there is a lockfile Release the lock to allow chef-client to run
169
+ lockfile.flock(File::LOCK_UN)
170
+ lockfile.close
171
+ end
172
+ end
173
+ @state = state
174
+ @job_id = job_id
175
+ @command = command
176
+ @pid = pid
177
+ @process_thread = process_thread
178
+
179
+ # Notify people of the change
180
+ @on_job_state_change.each { |block| block.call(get_job_state) }
181
+ end
182
+
183
+ def completed(job_id, exit_code, stdout, stderr)
184
+ Chef::Log.info("[#{node_name}] Job #{job_id} completed with exit code #{exit_code}")
185
+ @state_lock.synchronize do
186
+ if @state == :running && @job_id == job_id
187
+ set_job_state(:idle)
188
+ status = exit_code == 0 ? :succeeded : :failed
189
+ params = {}
190
+ params[:stdout] = stdout if stdout
191
+ params[:stderr] = stderr if stderr
192
+ client.send_command(status, job_id, params)
193
+ end
194
+ end
195
+ end
196
+
197
+ def start_process
198
+ # _pid and _job_id are local variables so that if @pid or @job_id change
199
+ # for any reason (for example, they become nil), the thread we create
200
+ # still tracks the correct pid.
201
+ if client.whitelist[command].is_a?(Hash)
202
+ command_line = client.whitelist[command][:command_line]
203
+ else
204
+ command_line = client.whitelist[command]
205
+ end
206
+ user = @opts['user']
207
+ dir = @opts['dir']
208
+ env = @opts['env'] || {}
209
+ capture = @opts['capture'] || false
210
+ path = extract_file
211
+ env.merge!({'CHEF_PUSH_JOB_FILE' => path}) if path
212
+ std_env = {'CHEF_PUSH_NODE_NAME' => node_name, 'CHEF_PUSH_JOB_ID' => @job_id}
213
+ env.merge!(std_env)
214
+ # XXX We set the timeout to 86400, because the time in ShellOut is
215
+ # 60 seconds, and that might be too slow. But we currently don't
216
+ # have the timeout from the pushy-server. Instead of changing it from
217
+ # a hard-coded value to a config option, we should expand the protocol
218
+ # to support sending the timeout.
219
+ command = Mixlib::ShellOut.new(command_line,
220
+ :user => user,
221
+ :cwd => dir,
222
+ :env => env,
223
+ :timeout => 86400)
224
+ _job_id = @job_id
225
+ # Can't get the _pid from the ShellOut command. So
226
+ # we can't kill it, either.
227
+ _pid = nil
228
+ Chef::Log.info("[#{node_name}] Job #{job_id}: started command '#{command_line}' with PID '#{_pid}'")
229
+
230
+ # Wait for the job to complete and close it out.
231
+ process_thread = Thread.new do
232
+ begin
233
+ command.run_command
234
+ stdout = command.stdout if capture
235
+ stderr = command.stderr if capture
236
+ completed(_job_id, command.status.exitstatus, stdout, stderr)
237
+ rescue
238
+ client.log_exception("Exception raised while waiting for job #{_job_id} to complete", $!)
239
+ abort
240
+ end
241
+ end
242
+
243
+ [ _pid, process_thread ]
244
+ end
245
+
246
+ def kill_process
247
+ Chef::Log.info("[#{node_name}] Killing process #{@pid}")
248
+ @process_thread.kill
249
+ @process_thread.join
250
+ begin
251
+ Process.kill(1, @pid) if @pid
252
+ rescue
253
+ client.log_exception("Exception in Process.kill(1, #{@pid})", $!)
254
+ end
255
+ end
256
+
257
+ def check_user(job_id)
258
+ user = @opts['user']
259
+ if user
260
+ begin
261
+ Etc.getpwnam(user)
262
+ true
263
+ rescue
264
+ Chef::Log.error("[#{node_name}] Received commit #{job_id}, but user '#{user}' does not exist!")
265
+ false
266
+ end
267
+ else
268
+ true
269
+ end
270
+ end
271
+
272
+ def check_dir(job_id)
273
+ # XX Perhaps should be stricted, e.g. forking a process to actually try to chdir
274
+ dir = @opts['dir']
275
+ dir_ok = !dir || Dir.exists?(dir)
276
+ Chef::Log.error("[#{node_name}] Received commit #{job_id}, but dir '#{dir}' does not exist!") unless dir_ok
277
+ dir_ok
278
+ end
279
+
280
+ def check_file(job_id)
281
+ file = @opts['file']
282
+ file_ok = !file || file.start_with?('base64:', 'raw:')
283
+ Chef::Log.error("[#{node_name}] Received commit #{job_id}, but file '#{file}' is a bad format!") unless file_ok
284
+ file_ok
285
+ end
286
+
287
+ def extract_file
288
+ file = @opts['file']
289
+ return nil unless file
290
+ require 'tmpdir'
291
+ dir = client.file_dir
292
+ Dir.mkdir(dir) unless Dir.exists?(dir)
293
+ path = Dir::Tmpname.create('pushy_file', dir){|p| p}
294
+ File.open(path, 'w') do |f|
295
+ type, filedata = file.split(/:/, 2)
296
+ case type
297
+ when "raw"
298
+ f.write(filedata)
299
+ when "base64"
300
+ f.write(Base64.decode64(filedata))
301
+ else
302
+ Chef::Log.error("[#{node_name}] Received commit #{job_id}, but file starting with '#{file.slice(0,80)}' has a bad format!")
303
+ end
304
+ end
305
+ path
306
+ end
307
+ end
308
+ end
@@ -18,6 +18,6 @@
18
18
  # Note: the version must also be updated in
19
19
  # omnibus/config/projects/push-jobs-client.rb
20
20
  class PushyClient
21
- VERSION = "2.100.0"
21
+ VERSION = "3.0.0.pre"
22
22
  PROTOCOL_VERSION = "2.0"
23
23
  end
@@ -17,8 +17,8 @@ Gem::Specification.new do |gem|
17
17
 
18
18
  gem.required_ruby_version = '>= 2.5'
19
19
 
20
- gem.add_dependency "chef", ">= 15.0", "< 17.0"
21
- gem.add_dependency "ohai", ">= 15.0", "< 17.0"
20
+ gem.add_dependency "chef", ">= 14.0", "< 16.0"
21
+ gem.add_dependency "ohai", ">= 14.0", "< 16.0"
22
22
  gem.add_dependency "ffi-rzmq"
23
23
  gem.add_dependency "uuidtools"
24
24
 
@@ -18,6 +18,7 @@ describe PushyClient::ProtocolHandler do
18
18
  allow(client).to receive(:hostname)
19
19
  allow(client).to receive(:org_name)
20
20
  allow(client).to receive(:incarnation_id)
21
+ allow(client).to receive(:max_body_size).and_return(63000)
21
22
  allow_any_instance_of(described_class).to receive(:send_signed_json_command)
22
23
  allow(Chef::Log).to receive(:warn)
23
24
  end
@@ -32,7 +33,7 @@ describe PushyClient::ProtocolHandler do
32
33
 
33
34
  it "logs a warning" do
34
35
  expect(Chef::Log).to receive(:warn).with(
35
- "Command output too long. Will not be sent to server."
36
+ "Command output #{params[:stdout].to_s.bytesize + params[:stderr].to_s.bytesize} is larger than the Client MAX_BODY_SIZE of #{client.max_body_size.to_i}"
36
37
  )
37
38
  send_command
38
39
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opscode-pushy-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.100.0
4
+ version: 3.0.0.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Anderson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-09 00:00:00.000000000 Z
11
+ date: 2019-05-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chef
@@ -16,40 +16,40 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '15.0'
19
+ version: '14.0'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '17.0'
22
+ version: '16.0'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '15.0'
29
+ version: '14.0'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '17.0'
32
+ version: '16.0'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: ohai
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: '15.0'
39
+ version: '14.0'
40
40
  - - "<"
41
41
  - !ruby/object:Gem::Version
42
- version: '17.0'
42
+ version: '16.0'
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
46
46
  requirements:
47
47
  - - ">="
48
48
  - !ruby/object:Gem::Version
49
- version: '15.0'
49
+ version: '14.0'
50
50
  - - "<"
51
51
  - !ruby/object:Gem::Version
52
- version: '17.0'
52
+ version: '16.0'
53
53
  - !ruby/object:Gem::Dependency
54
54
  name: ffi-rzmq
55
55
  requirement: !ruby/object:Gem::Requirement
@@ -124,10 +124,11 @@ description: Client for Chef push jobs server
124
124
  email:
125
125
  - mark@chef.io
126
126
  executables:
127
- - pushy-service-manager
127
+ - pushy-client
128
128
  - print_execution_environment
129
129
  - push-apply
130
- - pushy-client
130
+ - pushy-service-manager
131
+ - print_execution_environment~
131
132
  extensions: []
132
133
  extra_rdoc_files: []
133
134
  files:
@@ -135,6 +136,7 @@ files:
135
136
  - LICENSE
136
137
  - Rakefile
137
138
  - bin/print_execution_environment
139
+ - bin/print_execution_environment~
138
140
  - bin/push-apply
139
141
  - bin/pushy-client
140
142
  - bin/pushy-service-manager
@@ -144,6 +146,7 @@ files:
144
146
  - lib/pushy_client/job_runner.rb
145
147
  - lib/pushy_client/periodic_reconfigurer.rb
146
148
  - lib/pushy_client/protocol_handler.rb
149
+ - lib/pushy_client/status_monitor.rb~
147
150
  - lib/pushy_client/version.rb
148
151
  - lib/pushy_client/whitelist.rb
149
152
  - lib/pushy_client/win32.rb
@@ -167,16 +170,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
167
170
  version: '2.5'
168
171
  required_rubygems_version: !ruby/object:Gem::Requirement
169
172
  requirements:
170
- - - ">="
173
+ - - ">"
171
174
  - !ruby/object:Gem::Version
172
- version: '0'
175
+ version: 1.3.1
173
176
  requirements: []
174
- rubygems_version: 3.0.8
177
+ rubyforge_project:
178
+ rubygems_version: 2.7.8
175
179
  signing_key:
176
180
  specification_version: 4
177
181
  summary: Client for Chef push jobs server
178
182
  test_files:
179
- - spec/pushy_client/job_runner_spec.rb
183
+ - spec/spec_helper.rb
180
184
  - spec/pushy_client/whitelist_spec.rb
181
185
  - spec/pushy_client/protocol_handler_spec.rb
182
- - spec/spec_helper.rb
186
+ - spec/pushy_client/job_runner_spec.rb