Skip to content

Commit

Permalink
Merge pull request #43 from chef/jk/org_and_scope
Browse files Browse the repository at this point in the history
Add encapsulated Chef runs that capture and stream output
  • Loading branch information
tyler-ball committed Apr 7, 2015
2 parents 27efbda + ade163c commit 3f766af
Show file tree
Hide file tree
Showing 11 changed files with 373 additions and 90 deletions.
20 changes: 20 additions & 0 deletions bin/resource
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env ruby
require 'cheffish/chef_run'

def post(resource_type, name, properties)
chef_run = Cheffish::ChefRun.new
begin
r = chef_run.client.build_resource(resource_type, name) do
properties.each { |attr, value| public_send(attr, value) }
end
chef_run.client.add_resource(r)
chef_run.converge
puts "CODE: #{chef_run.updated? ? 201 : 200}"
puts "STDOUT: #{chef_run.stdout}\nSTDERR: #{chef_run.stderr}\nLOGS: #{chef_run.logs}"
rescue
puts "CODE: #{400}"
puts "ERROR: #{$!}\nBACKTRACE: #{$!.backtrace}\nSTDOUT: #{chef_run.stdout}\nSTDERR: #{chef_run.stderr}\nLOGS: #{chef_run.logs}"
end
end

post(ARGV.shift, ARGV.shift, Hash[*ARGV])
2 changes: 1 addition & 1 deletion cheffish.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Gem::Specification.new do |s|
s.email = '[email protected]'
s.homepage = 'http://wiki.opscode.com/display/chef'

s.add_dependency 'chef-zero', '~> 4.0'
s.add_dependency 'chef-zero', '~> 4.2'
s.add_dependency 'chef' , '~> 12.1'

s.add_development_dependency 'rake'
Expand Down
1 change: 1 addition & 0 deletions lib/chef/provider/private_key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ def load_current_resource
end
rescue
# If there's an error reading, we assume format and type are wrong and don't futz with them
Chef::Log.warn("Error reading #{new_path}: #{$!}")
end
else
resource.action :delete
Expand Down
18 changes: 10 additions & 8 deletions lib/cheffish.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
require 'chef/run_list/run_list_item'
require 'cheffish/basic_chef_client'
require 'cheffish/server_api'
require 'chef/knife'
require 'chef/config_fetcher'
require 'chef/log'
require 'chef/application'

module Cheffish
NAME_REGEX = /^[.\-[:alnum:]_]+$/

Expand Down Expand Up @@ -92,8 +84,10 @@ def self.get_private_key(name, config = profiled_config)
def self.get_private_key_with_path(name, config = profiled_config)
if config[:private_keys] && config[:private_keys][name]
if config[:private_keys][name].is_a?(String)
Chef::Log.info("Got key #{name} from Chef::Config.private_keys.#{name}, which points at #{config[:private_keys[name]]}. Reading key from there ...")
return [ IO.read(config[:private_keys][name]), config[:private_keys][name] ]
else
Chef::Log.info("Got key #{name} raw from Chef::Config.private_keys.#{name}.")
return [ config[:private_keys][name].to_pem, nil ]
end
elsif config[:private_key_paths]
Expand All @@ -104,6 +98,7 @@ def self.get_private_key_with_path(name, config = profiled_config)
if ext == '' || ext == '.pem'
key_name = key[0..-(ext.length+1)]
if key_name == name
Chef::Log.info("Reading key #{name} from file #{private_key_path}/#{key}")
return [ IO.read("#{private_key_path}/#{key}"), "#{private_key_path}/#{key}" ]
end
end
Expand Down Expand Up @@ -223,4 +218,11 @@ def remove_role(*roles)

# Include all recipe objects so require 'cheffish' brings in the whole recipe DSL

require 'chef/run_list/run_list_item'
require 'cheffish/basic_chef_client'
require 'cheffish/server_api'
require 'chef/knife'
require 'chef/config_fetcher'
require 'chef/log'
require 'chef/application'
require 'cheffish/recipe_dsl'
99 changes: 84 additions & 15 deletions lib/cheffish/basic_chef_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,42 +13,61 @@ module Cheffish
class BasicChefClient
include Chef::DSL::Recipe

def initialize(node = nil, events = nil)
def initialize(node = nil, events = nil, **chef_config)
if !node
node = Chef::Node.new
node.name 'basic_chef_client'
node.automatic[:platform] = 'basic_chef_client'
node.automatic[:platform_version] = Cheffish::VERSION
end

@event_catcher = BasicChefClientEvents.new
dispatcher = Chef::EventDispatch::Dispatcher.new(@event_catcher)
dispatcher.register(events) if events
@run_context = Chef::RunContext.new(node, {}, dispatcher)
@updated = []
@cookbook_name = 'basic_chef_client'
# Decide on the config we want for this chef client
@chef_config = chef_config

with_chef_config do
@cookbook_name = 'basic_chef_client'
@event_catcher = BasicChefClientEvents.new
dispatcher = Chef::EventDispatch::Dispatcher.new(@event_catcher)
case events
when nil
when Array
events.each { |e| dispatcher.register(e) } if events
else
dispatcher.register(events)
end
@run_context = Chef::RunContext.new(node, {}, dispatcher)
@updated = []
@cookbook_name = 'basic_chef_client'
end
end

extend Forwardable

# Stuff recipes need
attr_reader :chef_config
attr_reader :run_context
attr_accessor :cookbook_name
attr_accessor :recipe_name
def_delegators :@run_context, :resource_collection, :immediate_notifications, :delayed_notifications

def add_resource(resource)
resource.run_context = run_context
run_context.resource_collection.insert(resource)
with_chef_config do
resource.run_context = run_context
run_context.resource_collection.insert(resource)
end
end

def load_block(&block)
@recipe_name = 'block'
instance_eval(&block)
with_chef_config do
@recipe_name = 'block'
instance_eval(&block)
end
end

def converge
Chef::Runner.new(self).converge
with_chef_config do
Chef::Runner.new(self).converge
end
end

def updates
Expand All @@ -63,15 +82,21 @@ def updated?
# add_resource() method.
def self.build_resource(type, name, created_at=nil, &resource_attrs_block)
created_at ||= caller[0]
result = BasicChefClient.new.build_resource(type, name, created_at, &resource_attrs_block)
result = BasicChefClient.new.tap do |client|
client.with_chef_config do
client.build_resource(type, name, created_at, &resource_attrs_block)
end
end
result
end

def self.inline_resource(provider, provider_action, *resources, &block)
events = ProviderEventForwarder.new(provider, provider_action)
client = BasicChefClient.new(provider.node, events)
resources.each do |resource|
client.add_resource(resource)
client.with_chef_config do
resources.each do |resource|
client.add_resource(resource)
end
end
client.load_block(&block) if block
client.converge
Expand All @@ -85,6 +110,50 @@ def self.converge_block(node = nil, events = nil, &block)
client.updated?
end

def with_chef_config(&block)
old_chef_config = Chef::Config.save
if chef_config[:log_location]
old_loggers = Chef::Log.loggers
Chef::Log.init(chef_config[:log_location])
end
if chef_config[:log_level]
old_level = Chef::Log.level
Chef::Log.level(chef_config[:log_level])
end
# if chef_config[:stdout]
# old_stdout = $stdout
# $stdout = chef_config[:stdout]
# end
# if chef_config[:stderr]
# old_stderr = $stderr
# $stderr = chef_config[:stderr]
# end
begin
deep_merge_config(chef_config, Chef::Config)
block.call
ensure
# $stdout = old_stdout if chef_config[:stdout]
# $stderr = old_stderr if chef_config[:stderr]
if old_loggers
Chef::Log.logger = old_loggers.shift
old_loggers.each { |l| Chef::Log.loggers.push(l) }
elsif chef_config[:log_level]
Chef::Log.level = old_level
end
Chef::Config.restore(old_chef_config)
end
end

def deep_merge_config(src, dest)
src.each do |name, value|
if value.is_a?(Hash) && dest[name].is_a?(Hash)
deep_merge_config(value, dest[name])
else
dest[name] = value
end
end
end

class BasicChefClientEvents < Chef::EventDispatch::Base
def initialize
@updates = []
Expand Down
142 changes: 142 additions & 0 deletions lib/cheffish/chef_run.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
require 'cheffish/basic_chef_client'

module Cheffish
class ChefRun
#
# @param chef_config A hash with symbol keys that looks suspiciously similar to `Chef::Config`.
# Some possible options:
# - stdout: <IO object> - where to stream stdout to
# - stderr: <IO object> - where to stream stderr to
# - log_level: :debug|:info|:warn|:error|:fatal
# - log_location: <path|IO object> - where to stream logs to
# - verbose_logging: true|false - true if you want verbose logging in :debug
#
def initialize(chef_config={})
@chef_config = chef_config || {}
end

attr_reader :chef_config

class StringIOTee < StringIO
def initialize(*streams)
super()
@streams = streams.flatten.select { |s| !s.nil? }
end

attr_reader :streams

def write(*args, &block)
super
streams.each { |s| s.write(*args, &block) }
end
end

def client
@client ||= begin
chef_config = self.chef_config.dup
chef_config[:log_level] ||= :debug if !chef_config.has_key?(:log_level)
chef_config[:verbose_logging] = false if !chef_config.has_key?(:verbose_logging)
chef_config[:stdout] = StringIOTee.new(chef_config[:stdout])
chef_config[:stderr] = StringIOTee.new(chef_config[:stderr])
chef_config[:log_location] = StringIOTee.new(chef_config[:log_location])
@client = ::Cheffish::BasicChefClient.new(nil,
[ event_sink, Chef::Formatters.new(:doc, chef_config[:stdout], chef_config[:stderr]) ],
chef_config
)
end
end

def event_sink
@event_sink ||= EventSink.new
end

#
# output
#
def stdout
@client ? client.chef_config[:stdout].string : nil
end
def stderr
@client ? client.chef_config[:stderr].string : nil
end
def logs
@client ? client.chef_config[:log_location].string : nil
end

def resources
client.resource_collection
end

def converge
begin
client.converge
@converged = true
rescue RuntimeError => e
@raised_exception = e
raise
end
end

def reset
@client = nil
@converged = nil
@stdout = nil
@stderr = nil
@logs = nil
@raised_exception = nil
end

def converged?
!!@converged
end

def converge_failed?
@raised_exception.nil? ? false : true
end

def updated?
client.updated?
end

def up_to_date?
!client.updated?
end

def output_for_failure_message
message = ""
if stdout && !stdout.empty?
message << "--- ---\n"
message << "--- Chef Client Output ---\n"
message << "--- ---\n"
message << stdout
message << "\n" if !stdout.end_with?("\n")
end
if stderr && !stderr.empty?
message << "--- ---\n"
message << "--- Chef Client Error Output ---\n"
message << "--- ---\n"
message << stderr
message << "\n" if !stderr.end_with?("\n")
end
if logs && !logs.empty?
message << "--- ---\n"
message << "--- Chef Client Logs ---\n"
message << "--- ---\n"
message << logs
end
message
end

class EventSink
def initialize
@events = []
end

attr_reader :events

def method_missing(method, *args)
@events << [ method, *args ]
end
end
end
end
10 changes: 7 additions & 3 deletions lib/cheffish/key_formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def self.decode(str, pass_phrase=nil, filename='')
end

key_format[:type] = type_of(key)
key_format[:size] = size_of(key)
key_format[:size] = size_of(key) if size_of(key)
key_format[:pass_phrase] = pass_phrase if pass_phrase
# TODO cipher, exponent

Expand Down Expand Up @@ -102,8 +102,12 @@ def self.type_of(key)
end

def self.size_of(key)
# TODO DSA -- this is RSA only
key.n.num_bytes * 8
case key.class
when OpenSSL::PKey::RSA
key.n.num_bytes * 8
else
nil
end
end
end
end
Loading

0 comments on commit 3f766af

Please sign in to comment.