Skip to content

Commit

Permalink
Merge pull request #30 from chef/ma/push-2x
Browse files Browse the repository at this point in the history
Ma/push 2x
  • Loading branch information
markan committed Aug 22, 2015
2 parents 21bf515 + 95f97e2 commit 3875cf2
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 105 deletions.
60 changes: 51 additions & 9 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ This plugin provides the following Knife subcommands. Specific command options

The <tt>job list</tt> subcommand is used to view a list of Push jobs.

== Syntax
=== Syntax
$ knife job list

== job start

The <tt>job start</tt> subcommand is used to start a Push job.

== Syntax
=== Syntax
$ knife job start (options) COMMAND [NODE, NODE, ...]

== Options
=== Options
This argument has the following options:

--timeout TIMEOUT
Expand All @@ -48,7 +48,26 @@ percentage (e.g. 50%) or as an absolute number of nodes (e.g. 145). Default valu

Exit immediately after starting a job instead of waiting for it to complete.

== Examples
--with-env ENVIRONMENT

Accept a json blob of environment variables and use those to set the
variables for the client. For example '{"test": "foo"}' will set the
push client environment variable "test" to "foo". (Push 2.0 and later)

--in-dir DIR

Execute the remote command in the directory DIR. (Push 2.0 and later)

--file DATAFILE

Send the file to the client. (Push 2.0 and later)

--capture

Capture stdin and stdout for this job. (Push 2.0 and later)


=== Examples
For example, to search for nodes assigned the role “webapp”, and where 90% of those nodes must be available, enter:

$ knife job start -quorum 90% 'chef-client' --search 'role:webapp'
Expand All @@ -65,14 +84,35 @@ For example, to run a job named add-glasses against a node named “ricardosalaz

$ knife job start add-glasses 'ricardosalazar'

== job output

The <tt>job output </tt> command is used to view the output of Push
jobs. (Push 2.0 and later). The output capture flag must have been set
on job start; see the --capture option.

=== Syntax

$ knife job output JOBID

=== Examples

$ knife job output 26e98ba162fa7ba6fb2793125553c7ae test --channel stdout

=== Options

--channel [stderr|stdout]

The output channel to capture.


== job status

The <tt>job status</tt> argument is used to view the status of Push jobs.
The <tt>job status</tt> command is used to view the status of Push jobs.

== Syntax
$ knife job status
=== Syntax
$ knife job status JOBID

== Examples
=== Examples
For example, to view the status of a job that has the identifier of “235”, enter:

$ knife job status 235
Expand All @@ -81,10 +121,12 @@ For example, to view the status of a job that has the identifier of “235”, e

The <tt>node status</tt> argument is used to identify nodes that Push may interact with.

== Syntax
=== Syntax
$ knife node status




== License

Push - The push jobs component for chef
Expand Down
4 changes: 2 additions & 2 deletions knife-push.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.has_rdoc = true
s.extra_rdoc_files = ["README.rdoc", "LICENSE"]
s.summary = "Knife plugin for OPC push"
s.summary = "Knife plugin for chef push"
s.description = s.summary
s.author = "John Keiser"
s.email = "[email protected]"
s.homepage = "http://www.opscode.com"
s.homepage = "http://www.chef.io"

# We need a more recent version of mixlib-cli in order to support --no- options.
# ... but, we can live with those options not working, if it means the plugin
Expand Down
142 changes: 142 additions & 0 deletions lib/chef/knife/job_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# @copyright Copyright 2015 Chef Software, Inc. All Rights Reserved.
#
# This file is provided to you under the Apache License,
# Version 2.0 (the "License"); you may not use this file
# except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#

class Chef
class Knife
module JobHelpers
def self.process_search(search, nodes)
node_names = []
if search
q = Chef::Search::Query.new
escaped_query = URI.escape(search,
Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
begin
nodes = q.search(:node, escaped_query).first
rescue Net::HTTPServerException => e
msg Chef::JSONCompat.from_json(e.response.body)['error'].first
ui.error("knife search failed: #{msg}")
exit 1
end
nodes.each { |node| node_names << node.name }
else
node_names = nodes
end

if node_names.empty?
ui.error "No nodes to run job on. Specify nodes as arguments or use -s to specify a search query."
exit 1
end

return node_names
end

def self.status_string(job)
case job['status']
when 'new'
[false, 'Initialized.']
when 'voting'
[false, job['status'].capitalize + '.']
else
total = job['nodes'].values.inject(0) { |sum,nodes| sum+nodes.length }
in_progress = job['nodes'].keys.inject(0) { |sum,status|
nodes = job['nodes'][status]
sum + (%w(new voting running).include?(status) ? 1 : 0)
}
if job['status'] == 'running'
[false, job['status'].capitalize + " (#{in_progress}/#{total} in progress) ..."]
else
[true, job['status'].capitalize + '.']
end
end
end

def self.get_quorum(quorum, total_nodes)
unless qmatch = /^(\d+)(\%?)$/.match(quorum)
raise "Invalid Format please enter integer or percent"
end

num = qmatch[1]

case qmatch[2]
when "%" then
((num.to_f/100)*total_nodes).ceil
else
num.to_i
end
end

def self.status_code(job)
if job['status'] == "complete" && job["nodes"].keys.all? do |key|
key == "succeeded" || key == "nacked" || key == "unavailable"
end
0
else
1
end
end

def self.run_helper(config, job_json)
job_json['run_timeout'] ||= config[:run_timeout].to_i if config[:run_timeout]

rest = Chef::REST.new(Chef::Config[:chef_server_url])
result = rest.post_rest('pushy/jobs', job_json)
job_uri = result['uri']
puts "Started. Job ID: #{job_uri[-32,32]}"
exit(0) if config[:nowait]
previous_state = "Initialized."
begin
sleep(config[:poll_interval].to_f)
putc(".")
job = rest.get_rest(job_uri)
finished, state = JobHelpers.status_string(job)
if state != previous_state
puts state
previous_state = state
end
end until finished
job
end

def self.file_helper(file_name)
if file_name.nil?
ui.error "No file specified."
show_usage
exit 1
end
contents = ""
if File.exists?(file_name)
File.open(file_name, "rb") do |file|
contents = file.read
end
else
ui.error "#{file_name} not found"
exit 1
end
return contents
end

def self.get_env(config)
env = {}
begin
env = config[:with_env] ? JSON.parse(config[:with_env]) : {}
rescue Exception => e
Chef::Log.info("Can't parse environment as JSON")
end
end
end
end
end
57 changes: 57 additions & 0 deletions lib/chef/knife/job_output.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# @copyright Copyright 2014 Chef Software, Inc. All Rights Reserved.
#
# This file is provided to you under the Apache License,
# Version 2.0 (the "License"); you may not use this file
# except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#

class Chef
class Knife
class JobOutput < Chef::Knife
banner "knife job output <job id> <node> [<node> ...]"

option :channel,
:long => '--channel stdout|stderr',
:default => 'stdout',
:description => "Which output channel to fetch (default stdout)."

def run
rest = Chef::REST.new(Chef::Config[:chef_server_url])

job_id = name_args[0]
channel = get_channel(config[:channel])
node = name_args[1]

uri = "pushy/jobs/#{job_id}/output/#{node}/#{channel}"

job = rest.get_rest(uri, false, {"Accept"=>"application/octet-stream"})

output(job)
end

def get_channel(channel)
channel = channel || "stdout"
case channel
when "stdout"
return channel
when "stderr"
return channel
else
raise "Invalid Format please enter stdout or stderr"
end
end

end
end
end

Loading

0 comments on commit 3875cf2

Please sign in to comment.