Skip to content

Instantly share code, notes, and snippets.

@stahnma
Created June 19, 2013 16:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stahnma/d7598b49a4abc07845b9 to your computer and use it in GitHub Desktop.
Save stahnma/d7598b49a4abc07845b9 to your computer and use it in GitHub Desktop.
From 859a3e0cfaed8f3097c72b2bb78c74fe9d96981b Mon Sep 17 00:00:00 2001
From: Patrick Carlisle <patrick@puppetlabs.com>
Date: Fri, 24 May 2013 12:00:03 -0700
Subject: [PATCH] 2.6.x - Patch for CVE-2013-3567
Add vendoring system into puppet
This adds support to Puppet for vendoring libraries.
Fix installation of vendored libs
Vendored libs were getting the path component 'lib' removed when only the
leading 'lib' should be removed.
Vendor safe_yaml 0.9.2
(#20584) Only deserialize expected objects from YAML
Puppet no longer allows the base YAML library to deserialize arbitrary
objects. By using the safe_yaml package, we deserialize only primitive data
structures. The remainder of the deserialization process is now unified
between YAML and PSON. This code will explicitly create only the expected
objects from the primitive data types.
As a side effect of this change the code for REST indirections now allows
specialized deserialization routines per method. The save method now returns
nil by default, and is only overridden in a couple of cases where Puppet was
using the return value to preserve previous behavior.
Improve CVE 2013 1654 SSLv2 Downgrade Master test
Previously we were only checking for the applicability of the master
(since we're testing the master), but we run the check from an agent.
This ensures the agent we run the check from has the necessary openssl
client for the test.
Signed-off-by: Justin Stoller <justin@puppetlabs.com>
---
install.rb | 2 +-
lib/puppet.rb | 11 +
lib/puppet/indirector/report/rest.rb | 7 +
lib/puppet/indirector/resource/rest.rb | 9 +
lib/puppet/indirector/rest.rb | 129 ++--
lib/puppet/indirector/run/rest.rb | 6 +
lib/puppet/network/formats.rb | 30 +-
lib/puppet/network/http/handler.rb | 2 +-
lib/puppet/node.rb | 25 +
lib/puppet/node/facts.rb | 27 +-
lib/puppet/resource.rb | 6 +-
lib/puppet/resource/status.rb | 28 +
lib/puppet/run.rb | 26 +-
lib/puppet/status.rb | 8 +-
lib/puppet/transaction/event.rb | 19 +
lib/puppet/transaction/report.rb | 39 ++
lib/puppet/util/log.rb | 19 +
lib/puppet/util/metric.rb | 6 +
lib/puppet/util/monkey_patches.rb | 1 -
lib/puppet/vendor.rb | 55 ++
lib/puppet/vendor/load_safe_yaml.rb | 1 +
lib/puppet/vendor/require_vendored.rb | 4 +
lib/puppet/vendor/safe_yaml/.gitignore | 2 +
lib/puppet/vendor/safe_yaml/.travis.yml | 47 ++
lib/puppet/vendor/safe_yaml/CHANGES.md | 104 +++
lib/puppet/vendor/safe_yaml/Gemfile | 11 +
lib/puppet/vendor/safe_yaml/LICENSE.txt | 22 +
lib/puppet/vendor/safe_yaml/README.md | 179 ++++++
lib/puppet/vendor/safe_yaml/Rakefile | 6 +
lib/puppet/vendor/safe_yaml/lib/safe_yaml.rb | 253 ++++++++
lib/puppet/vendor/safe_yaml/lib/safe_yaml/deep.rb | 34 +
.../vendor/safe_yaml/lib/safe_yaml/parse/date.rb | 27 +
.../safe_yaml/lib/safe_yaml/parse/hexadecimal.rb | 12 +
.../safe_yaml/lib/safe_yaml/parse/sexagesimal.rb | 26 +
.../safe_yaml/lib/safe_yaml/psych_handler.rb | 92 +++
.../safe_yaml/lib/safe_yaml/psych_resolver.rb | 52 ++
.../vendor/safe_yaml/lib/safe_yaml/resolver.rb | 94 +++
.../lib/safe_yaml/safe_to_ruby_visitor.rb | 17 +
.../vendor/safe_yaml/lib/safe_yaml/syck_hack.rb | 36 ++
.../lib/safe_yaml/syck_node_monkeypatch.rb | 43 ++
.../safe_yaml/lib/safe_yaml/syck_resolver.rb | 38 ++
.../vendor/safe_yaml/lib/safe_yaml/transform.rb | 41 ++
.../lib/safe_yaml/transform/to_boolean.rb | 21 +
.../safe_yaml/lib/safe_yaml/transform/to_date.rb | 11 +
.../safe_yaml/lib/safe_yaml/transform/to_float.rb | 33 +
.../lib/safe_yaml/transform/to_integer.rb | 25 +
.../safe_yaml/lib/safe_yaml/transform/to_nil.rb | 18 +
.../safe_yaml/lib/safe_yaml/transform/to_symbol.rb | 13 +
.../lib/safe_yaml/transform/transformation_map.rb | 47 ++
.../vendor/safe_yaml/lib/safe_yaml/version.rb | 3 +
.../safe_yaml/run_specs_all_ruby_versions.sh | 21 +
lib/puppet/vendor/safe_yaml/safe_yaml.gemspec | 18 +
.../vendor/safe_yaml/spec/exploit.1.9.2.yaml | 2 +
.../vendor/safe_yaml/spec/exploit.1.9.3.yaml | 2 +
.../vendor/safe_yaml/spec/psych_resolver_spec.rb | 10 +
lib/puppet/vendor/safe_yaml/spec/resolver_specs.rb | 250 ++++++++
lib/puppet/vendor/safe_yaml/spec/safe_yaml_spec.rb | 702 +++++++++++++++++++++
lib/puppet/vendor/safe_yaml/spec/spec_helper.rb | 18 +
.../spec/support/exploitable_back_door.rb | 29 +
.../vendor/safe_yaml/spec/syck_resolver_spec.rb | 10 +
.../vendor/safe_yaml/spec/transform/base64_spec.rb | 11 +
.../safe_yaml/spec/transform/to_date_spec.rb | 34 +
.../safe_yaml/spec/transform/to_float_spec.rb | 42 ++
.../safe_yaml/spec/transform/to_integer_spec.rb | 59 ++
.../safe_yaml/spec/transform/to_symbol_spec.rb | 49 ++
spec/integration/indirector/report/rest_spec.rb | 2 +-
spec/unit/file_serving/metadata_spec.rb | 7 -
spec/unit/indirector/report/rest_spec.rb | 41 ++
spec/unit/indirector/rest_spec.rb | 628 +++++++++---------
spec/unit/network/formats_spec.rb | 63 +-
spec/unit/network/http/handler_spec.rb | 16 +
spec/unit/node_spec.rb | 14 +
spec/unit/resource_spec.rb | 40 +-
spec/unit/run_spec.rb | 30 +-
spec/unit/status_spec.rb | 8 +
test/network/handler/report.rb | 36 --
76 files changed, 3401 insertions(+), 508 deletions(-)
create mode 100644 lib/puppet/vendor.rb
create mode 100644 lib/puppet/vendor/load_safe_yaml.rb
create mode 100644 lib/puppet/vendor/require_vendored.rb
create mode 100644 lib/puppet/vendor/safe_yaml/.gitignore
create mode 100644 lib/puppet/vendor/safe_yaml/.travis.yml
create mode 100644 lib/puppet/vendor/safe_yaml/CHANGES.md
create mode 100644 lib/puppet/vendor/safe_yaml/Gemfile
create mode 100644 lib/puppet/vendor/safe_yaml/LICENSE.txt
create mode 100644 lib/puppet/vendor/safe_yaml/README.md
create mode 100644 lib/puppet/vendor/safe_yaml/Rakefile
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/deep.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/date.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/hexadecimal.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/sexagesimal.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_handler.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_resolver.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/resolver.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/safe_to_ruby_visitor.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_hack.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_node_monkeypatch.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_resolver.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_boolean.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_date.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_float.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_integer.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_nil.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_symbol.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/transformation_map.rb
create mode 100644 lib/puppet/vendor/safe_yaml/lib/safe_yaml/version.rb
create mode 100755 lib/puppet/vendor/safe_yaml/run_specs_all_ruby_versions.sh
create mode 100644 lib/puppet/vendor/safe_yaml/safe_yaml.gemspec
create mode 100644 lib/puppet/vendor/safe_yaml/spec/exploit.1.9.2.yaml
create mode 100644 lib/puppet/vendor/safe_yaml/spec/exploit.1.9.3.yaml
create mode 100644 lib/puppet/vendor/safe_yaml/spec/psych_resolver_spec.rb
create mode 100644 lib/puppet/vendor/safe_yaml/spec/resolver_specs.rb
create mode 100644 lib/puppet/vendor/safe_yaml/spec/safe_yaml_spec.rb
create mode 100644 lib/puppet/vendor/safe_yaml/spec/spec_helper.rb
create mode 100644 lib/puppet/vendor/safe_yaml/spec/support/exploitable_back_door.rb
create mode 100644 lib/puppet/vendor/safe_yaml/spec/syck_resolver_spec.rb
create mode 100644 lib/puppet/vendor/safe_yaml/spec/transform/base64_spec.rb
create mode 100644 lib/puppet/vendor/safe_yaml/spec/transform/to_date_spec.rb
create mode 100644 lib/puppet/vendor/safe_yaml/spec/transform/to_float_spec.rb
create mode 100644 lib/puppet/vendor/safe_yaml/spec/transform/to_integer_spec.rb
create mode 100644 lib/puppet/vendor/safe_yaml/spec/transform/to_symbol_spec.rb
diff --git a/install.rb b/install.rb
index 351ba25..a672629 100755
--- a/install.rb
+++ b/install.rb
@@ -110,7 +110,7 @@ end
def do_libs(libs, strip = 'lib/')
libs.each do |lf|
- olf = File.join(InstallOptions.site_dir, lf.gsub(/#{strip}/, ''))
+ olf = File.join(InstallOptions.site_dir, lf.sub(/^#{strip}/, ''))
op = File.dirname(olf)
if $haveftools
File.makedirs(op, true)
diff --git a/lib/puppet.rb b/lib/puppet.rb
index db1a160..7da768d 100644
--- a/lib/puppet.rb
+++ b/lib/puppet.rb
@@ -5,6 +5,8 @@ rescue LoadError
end
# see the bottom of the file for further inclusions
+# Also see the new Vendor support - towards the end
+
require 'singleton'
require 'facter'
require 'puppet/error'
@@ -151,6 +153,15 @@ module Puppet
Puppet.warning "Puppet.type is deprecated; use Puppet::Type.type"
Puppet::Type.type(name)
end
+
+ # Load vendored (setup paths, and load what is needed upfront).
+ # See the Vendor class for how to add additional vendored gems/code
+ require "puppet/vendor"
+ Puppet::Vendor.load_vendored
+
+ # Set default for YAML.load to unsafe so we don't affect programs
+ # requiring puppet -- in puppet we will call safe explicitly
+ SafeYAML::OPTIONS[:default_mode] = :unsafe
end
require 'puppet/type'
diff --git a/lib/puppet/indirector/report/rest.rb b/lib/puppet/indirector/report/rest.rb
index 601da9e..6febdb8 100644
--- a/lib/puppet/indirector/report/rest.rb
+++ b/lib/puppet/indirector/report/rest.rb
@@ -4,4 +4,11 @@ class Puppet::Transaction::Report::Rest < Puppet::Indirector::REST
desc "Get server report over HTTP via REST."
use_server_setting(:report_server)
use_port_setting(:report_port)
+
+ private
+
+ def deserialize_save(content_type, body)
+ format = Puppet::Network::FormatHandler.protected_format(content_type)
+ format.intern(Array, body)
+ end
end
diff --git a/lib/puppet/indirector/resource/rest.rb b/lib/puppet/indirector/resource/rest.rb
index 7848ae6..b9cae86 100644
--- a/lib/puppet/indirector/resource/rest.rb
+++ b/lib/puppet/indirector/resource/rest.rb
@@ -2,4 +2,13 @@ require 'puppet/indirector/status'
require 'puppet/indirector/rest'
class Puppet::Resource::Rest < Puppet::Indirector::REST
+
+ private
+
+ def deserialize_save(content_type, body)
+ # Body is [ral_res.to_resource, transaction.report]
+ format = Puppet::Network::FormatHandler.protected_format(content_type)
+ ary = format.intern(Array, body)
+ [Puppet::Resource.from_pson(ary[0]), Puppet::Transaction::Report.from_pson(ary[1])]
+ end
end
diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb
index 775cc82..0bedc96 100644
--- a/lib/puppet/indirector/rest.rb
+++ b/lib/puppet/indirector/rest.rb
@@ -32,36 +32,6 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus
Puppet.settings[port_setting || :masterport].to_i
end
- # Figure out the content type, turn that into a format, and use the format
- # to extract the body of the response.
- def deserialize(response, multiple = false)
- case response.code
- when "404"
- return nil
- when /^2/
- raise "No content type in http response; cannot parse" unless response['content-type']
-
- content_type = response['content-type'].gsub(/\s*;.*$/,'') # strip any appended charset
-
- body = uncompress_body(response)
-
- # Convert the response to a deserialized object.
- if multiple
- model.convert_from_multiple(content_type, body)
- else
- model.convert_from(content_type, body)
- end
- else
- # Raise the http error if we didn't get a 'success' of some kind.
- raise convert_to_http_error(response)
- end
- end
-
- def convert_to_http_error(response)
- message = "Error #{response.code} on SERVER: #{(response.body||'').empty? ? response.message : uncompress_body(response)}"
- Net::HTTPError.new(message, response)
- end
-
# Provide appropriate headers.
def headers
add_accept_encoding({"Accept" => model.supported_formats.join(", ")})
@@ -72,39 +42,59 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus
end
def find(request)
- return nil unless result = deserialize(network(request).get(indirection2uri(request), headers))
- result.name = request.key if result.respond_to?(:name=)
- result
+ response = network(request).get(indirection2uri(request), headers)
+
+ if is_http_200?(response)
+ content_type, body = parse_response(response)
+ result = deserialize_find(content_type, body)
+ result.name = request.key if result.respond_to?(:name=)
+ result
+ else
+ nil
+ end
end
def head(request)
response = network(request).head(indirection2uri(request), headers)
- case response.code
- when "404"
- return false
- when /^2/
- return true
- else
- # Raise the http error if we didn't get a 'success' of some kind.
- raise convert_to_http_error(response)
- end
+
+ !!is_http_200?(response)
end
def search(request)
- unless result = deserialize(network(request).get(indirection2uri(request), headers), true)
- return []
+ response = network(request).get(indirection2uri(request), headers)
+
+ if is_http_200?(response)
+ content_type, body = parse_response(response)
+ deserialize_search(content_type, body) || []
+ else
+ []
end
- result
end
def destroy(request)
raise ArgumentError, "DELETE does not accept options" unless request.options.empty?
- deserialize network(request).delete(indirection2uri(request), headers)
+
+ response = network(request).delete(indirection2uri(request), headers)
+
+ if is_http_200?(response)
+ content_type, body = parse_response(response)
+ deserialize_destroy(content_type, body)
+ else
+ nil
+ end
end
def save(request)
raise ArgumentError, "PUT does not accept options" unless request.options.empty?
- deserialize network(request).put(indirection2uri(request), request.instance.render, headers.merge({ "Content-Type" => request.instance.mime }))
+
+ response = network(request).put(indirection2uri(request), request.instance.render, headers.merge({ "Content-Type" => request.instance.mime }))
+
+ if is_http_200?(response)
+ content_type, body = parse_response(response)
+ deserialize_save(content_type, body)
+ else
+ nil
+ end
end
def validate_key(request)
@@ -113,6 +103,51 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus
private
+ def is_http_200?(response)
+ case response.code
+ when "404"
+ false
+ when /^2/
+ true
+ else
+ # Raise the http error if we didn't get a 'success' of some kind.
+ raise convert_to_http_error(response)
+ end
+ end
+
+ def convert_to_http_error(response)
+ message = "Error #{response.code} on SERVER: #{(response.body||'').empty? ? response.message : uncompress_body(response)}"
+ Net::HTTPError.new(message, response)
+ end
+
+ # Returns the content_type, stripping any appended charset, and the
+ # body, decompressed if necessary (content-encoding is checked inside
+ # uncompress_body)
+ def parse_response(response)
+ if response['content-type']
+ [ response['content-type'].gsub(/\s*;.*$/,''),
+ body = uncompress_body(response) ]
+ else
+ raise "No content type in http response; cannot parse"
+ end
+ end
+
+ def deserialize_find(content_type, body)
+ model.convert_from(content_type, body)
+ end
+
+ def deserialize_search(content_type, body)
+ model.convert_from_multiple(content_type, body)
+ end
+
+ def deserialize_destroy(content_type, body)
+ model.convert_from(content_type, body)
+ end
+
+ def deserialize_save(content_type, body)
+ nil
+ end
+
def environment
Puppet::Node::Environment.new
end
diff --git a/lib/puppet/indirector/run/rest.rb b/lib/puppet/indirector/run/rest.rb
index cbd3481..f857d45 100644
--- a/lib/puppet/indirector/run/rest.rb
+++ b/lib/puppet/indirector/run/rest.rb
@@ -3,4 +3,10 @@ require 'puppet/indirector/rest'
class Puppet::Run::Rest < Puppet::Indirector::REST
desc "Trigger Agent runs via REST."
+
+ private
+
+ def deserialize_save(content_type, body)
+ model.convert_from(content_type, body)
+ end
end
diff --git a/lib/puppet/network/formats.rb b/lib/puppet/network/formats.rb
index dd19692..7604987 100644
--- a/lib/puppet/network/formats.rb
+++ b/lib/puppet/network/formats.rb
@@ -3,12 +3,20 @@ require 'puppet/network/format_handler'
Puppet::Network::FormatHandler.create_serialized_formats(:yaml) do
# Yaml doesn't need the class name; it's serialized.
def intern(klass, text)
- YAML.load(text)
+ data = YAML.load(text, :safe => true, :deserialize_symbols => true)
+ return data if data.is_a?(klass)
+ klass.from_pson(data)
end
# Yaml doesn't need the class name; it's serialized.
def intern_multiple(klass, text)
- YAML.load(text)
+ YAML.load(text, :safe => true, :deserialize_symbols => true).collect do |data|
+ if data.is_a?(klass)
+ data
+ else
+ klass.from_pson(data)
+ end
+ end
end
def render(instance)
@@ -45,11 +53,15 @@ Puppet::Network::FormatHandler.create_serialized_formats(:b64_zlib_yaml) do
end
def intern(klass, text)
- decode(text)
+ requiring_zlib do
+ Puppet::Network::FormatHandler.format(:yaml).intern(klass, decode(text))
+ end
end
def intern_multiple(klass, text)
- decode(text)
+ requiring_zlib do
+ Puppet::Network::FormatHandler.format(:yaml).intern_multiple(klass, decode(text))
+ end
end
def render(instance)
@@ -64,15 +76,13 @@ Puppet::Network::FormatHandler.create_serialized_formats(:b64_zlib_yaml) do
true
end
- def encode(text)
- requiring_zlib do
- Base64.encode64(Zlib::Deflate.deflate(text, Zlib::BEST_COMPRESSION))
- end
+ def decode(data)
+ Zlib::Inflate.inflate(Base64.decode64(data))
end
- def decode(yaml)
+ def encode(text)
requiring_zlib do
- YAML.load(Zlib::Inflate.inflate(Base64.decode64(yaml)))
+ Base64.encode64(Zlib::Deflate.deflate(text, Zlib::BEST_COMPRESSION))
end
end
end
diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb
index b8e6499..ecfaa1a 100644
--- a/lib/puppet/network/http/handler.rb
+++ b/lib/puppet/network/http/handler.rb
@@ -239,7 +239,7 @@ module Puppet::Network::HTTP::Handler
next result if param == :ip
value = CGI.unescape(value)
if value =~ /^---/
- value = YAML.load(value)
+ value = YAML.load(value, :safe => true, :deserialize_symbols => true)
else
value = true if value == "true"
value = false if value == "false"
diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb
index 2453cd1..1b60335 100644
--- a/lib/puppet/node.rb
+++ b/lib/puppet/node.rb
@@ -19,6 +19,31 @@ class Puppet::Node
attr_accessor :name, :classes, :source, :ipaddress, :parameters
attr_reader :time
+ ::PSON.register_document_type('Node',self)
+
+ def self.from_pson(pson)
+ raise ArgumentError, "No name provided in serialized data" unless name = pson['name']
+
+ node = new(name)
+ node.classes = pson['classes']
+ node.parameters = pson['parameters']
+ node.environment = pson['environment']
+ node
+ end
+
+ def to_pson(*args)
+ result = {
+ 'document_type' => "Node",
+ 'data' => {}
+ }
+ result['data']['name'] = name
+ result['data']['classes'] = classes unless classes.empty?
+ result['data']['parameters'] = parameters unless parameters.empty?
+ result['data']['environment'] = environment.name
+
+ result.to_pson(*args)
+ end
+
def environment
return super if @environment
diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb
index 0a96e55..4c9d473 100755
--- a/lib/puppet/node/facts.rb
+++ b/lib/puppet/node/facts.rb
@@ -47,6 +47,26 @@ class Puppet::Node::Facts
end
end
+ def initialize_from_hash(data)
+ @name = data['name']
+ @values = data['values']
+ # Timestamp will be here in YAML
+ timestamp = data['values']['_timestamp']
+ @values.delete_if do |key, val|
+ key =~ /^_/
+ end
+
+ #Timestamp will be here in pson
+ timestamp ||= data['timestamp']
+ timestamp = Time.parse(timestamp) if timestamp.is_a? String
+ self.timestamp = timestamp
+
+ self.expiration = data['expiration']
+ if expiration.is_a? String
+ self.expiration = Time.parse(expiration)
+ end
+ end
+
# Convert all fact values into strings.
def stringify
values.each do |fact, value|
@@ -68,10 +88,9 @@ class Puppet::Node::Facts
end
def self.from_pson(data)
- result = new(data['name'], data['values'])
- result.timestamp = Time.parse(data['timestamp'])
- result.expiration = Time.parse(data['expiration'])
- result
+ new_facts = allocate
+ new_facts.initialize_from_hash(data)
+ new_facts
end
def to_pson(*args)
diff --git a/lib/puppet/resource.rb b/lib/puppet/resource.rb
index 2145169..058f653 100644
--- a/lib/puppet/resource.rb
+++ b/lib/puppet/resource.rb
@@ -27,8 +27,8 @@ class Puppet::Resource
ATTRIBUTES = [:file, :line, :exported]
def self.from_pson(pson)
- raise ArgumentError, "No resource type provided in pson data" unless type = pson['type']
- raise ArgumentError, "No resource title provided in pson data" unless title = pson['title']
+ raise ArgumentError, "No resource type provided in serialized data" unless type = pson['type']
+ raise ArgumentError, "No resource title provided in serialized data" unless title = pson['title']
resource = new(type, title)
@@ -46,8 +46,6 @@ class Puppet::Resource
end
end
- resource.exported ||= false
-
resource
end
diff --git a/lib/puppet/resource/status.rb b/lib/puppet/resource/status.rb
index dea8c10..c1df25c 100644
--- a/lib/puppet/resource/status.rb
+++ b/lib/puppet/resource/status.rb
@@ -14,6 +14,13 @@ module Puppet
YAML_ATTRIBUTES = %w{@resource @file @line @evaluation_time @change_count @out_of_sync_count @tags @time @events @out_of_sync @changed @resource_type @title @skipped @failed}
+
+ def self.from_pson(data)
+ obj = self.allocate
+ obj.initialize_from_hash(data)
+ obj
+ end
+
# Provide a boolean method for each of the states.
STATES.each do |attr|
define_method("#{attr}?") do
@@ -65,6 +72,27 @@ module Puppet
@title = resource.title
end
+ def initialize_from_hash(data)
+ @resource_type = data['resource_type']
+ @title = data['title']
+ @resource = data['resource']
+ @file = data['file']
+ @line = data['line']
+ @evaluation_time = data['evaluation_time']
+ @change_count = data['change_count']
+ @out_of_sync_count = data['out_of_sync_count']
+ @tags = data['tags']
+ @time = data['time']
+ @out_of_sync = data['out_of_sync']
+ @changed = data['changed']
+ @skipped = data['skipped']
+ @failed = data['failed']
+
+ @events = data['events'].map do |event|
+ Puppet::Transaction::Event.from_pson(event)
+ end
+ end
+
def to_yaml_properties
(YAML_ATTRIBUTES & instance_variables).sort
end
diff --git a/lib/puppet/run.rb b/lib/puppet/run.rb
index 150e644..d378e53 100644
--- a/lib/puppet/run.rb
+++ b/lib/puppet/run.rb
@@ -32,6 +32,17 @@ class Puppet::Run
@options = options
end
+ def initialize_from_hash(hash)
+ @options = {}
+
+ hash['options'].each do |key, value|
+ @options[key.to_sym] = value
+ end
+
+ @background = hash['background']
+ @status = hash['status']
+ end
+
def log_run
msg = ""
msg += "triggered run" % if options[:tags]
@@ -62,9 +73,20 @@ class Puppet::Run
self
end
- def self.from_pson( pson )
+ def self.from_hash(hash)
+ obj = allocate
+ obj.initialize_from_hash(hash)
+ obj
+ end
+
+ def self.from_pson(hash)
+ if hash['options']
+ return from_hash(hash)
+ end
+
options = {}
- pson.each do |key, value|
+
+ hash.each do |key, value|
options[key.to_sym] = value
end
diff --git a/lib/puppet/status.rb b/lib/puppet/status.rb
index eecd0e1..471f7d4 100644
--- a/lib/puppet/status.rb
+++ b/lib/puppet/status.rb
@@ -14,8 +14,12 @@ class Puppet::Status
@status.to_pson
end
- def self.from_pson( pson )
- self.new( pson )
+ def self.from_pson(pson)
+ if pson.include?('status')
+ self.new(pson['status'])
+ else
+ self.new(pson)
+ end
end
def name
diff --git a/lib/puppet/transaction/event.rb b/lib/puppet/transaction/event.rb
index cd695cf..bc51617 100644
--- a/lib/puppet/transaction/event.rb
+++ b/lib/puppet/transaction/event.rb
@@ -16,6 +16,12 @@ class Puppet::Transaction::Event
EVENT_STATUSES = %w{noop success failure audit}
+ def self.from_pson(data)
+ obj = self.allocate
+ obj.initialize_from_hash(data)
+ obj
+ end
+
def initialize(options = {})
@audited = false
options.each { |attr, value| send(attr.to_s + "=", value) }
@@ -23,6 +29,19 @@ class Puppet::Transaction::Event
@time = Time.now
end
+ def initialize_from_hash(data)
+ @audited = data['audited']
+ @property = data['property']
+ @previous_value = data['previous_value']
+ @desired_value = data['desired_value']
+ @historical_value = data['historical_value']
+ @message = data['message']
+ @name = data['name'].intern
+ @status = data['status']
+ @time = data['time']
+ @time = Time.parse(@time) if @time.is_a? String
+ end
+
def property=(prop)
@property = prop.to_s
end
diff --git a/lib/puppet/transaction/report.rb b/lib/puppet/transaction/report.rb
index 77b9da8..3c907db 100644
--- a/lib/puppet/transaction/report.rb
+++ b/lib/puppet/transaction/report.rb
@@ -19,6 +19,12 @@ class Puppet::Transaction::Report
:yaml
end
+ def self.from_pson(data)
+ obj = self.allocate
+ obj.initialize_from_hash(data)
+ obj
+ end
+
def <<(msg)
@logs << msg
self
@@ -76,6 +82,39 @@ class Puppet::Transaction::Report
@status = 'failed' # assume failed until the report is finalized
end
+ def initialize_from_hash(data)
+ @puppet_version = data['puppet_version']
+ @report_format = data['report_format']
+ @configuration_version = data['configuration_version']
+ @environment = data['environment']
+ @status = data['status']
+ @host = data['host']
+ @time = data['time']
+ if @time.is_a? String
+ @time = Time.parse(@time)
+ end
+ @kind = data['kind']
+
+ @metrics = {}
+ data['metrics'].each do |name, hash|
+ @metrics[name] = Puppet::Util::Metric.from_pson(hash)
+ end
+
+ @logs = data['logs'].map do |record|
+ Puppet::Util::Log.from_pson(record)
+ end
+
+ @resource_statuses = {}
+ data['resource_statuses'].map do |record|
+ if record[1] == {}
+ status = nil
+ else
+ status = Puppet::Resource::Status.from_pson(record[1])
+ end
+ @resource_statuses[record[0]] = status
+ end
+ end
+
def name
host
end
diff --git a/lib/puppet/util/log.rb b/lib/puppet/util/log.rb
index ba16900..152bcc4 100644
--- a/lib/puppet/util/log.rb
+++ b/lib/puppet/util/log.rb
@@ -195,6 +195,12 @@ class Puppet::Util::Log
@levels.include?(level)
end
+ def self.from_pson(data)
+ obj = allocate
+ obj.initialize_from_hash(data)
+ obj
+ end
+
attr_accessor :time, :remote, :file, :line, :source
attr_reader :level, :message
@@ -217,6 +223,19 @@ class Puppet::Util::Log
Log.newmessage(self)
end
+ def initialize_from_hash(data)
+ @level = data['level'].intern
+ @message = data['message']
+ @source = data['source']
+ @tags = data['tags']
+ @time = data['time']
+ if @time.is_a? String
+ @time = Time.parse(@time)
+ end
+ @file = data['file'] if data['file']
+ @line = data['line'] if data['line']
+ end
+
def message=(msg)
raise ArgumentError, "Puppet::Util::Log requires a message" unless msg
@message = msg.to_s
diff --git a/lib/puppet/util/metric.rb b/lib/puppet/util/metric.rb
index 835e1d6..4e7e231 100644
--- a/lib/puppet/util/metric.rb
+++ b/lib/puppet/util/metric.rb
@@ -9,6 +9,12 @@ class Puppet::Util::Metric
attr_writer :basedir
+ def self.from_pson(data)
+ metric = new(data['name'], data['label'])
+ metric.values = data['values']
+ metric
+ end
+
# Return a specific value
def [](name)
if value = @values.find { |v| v[0] == name }
diff --git a/lib/puppet/util/monkey_patches.rb b/lib/puppet/util/monkey_patches.rb
index 1478ba4..00bec34 100644
--- a/lib/puppet/util/monkey_patches.rb
+++ b/lib/puppet/util/monkey_patches.rb
@@ -1,4 +1,3 @@
-
unless defined? JRUBY_VERSION
Process.maxgroups = 1024
end
diff --git a/lib/puppet/vendor.rb b/lib/puppet/vendor.rb
new file mode 100644
index 0000000..fdaf5c0
--- /dev/null
+++ b/lib/puppet/vendor.rb
@@ -0,0 +1,55 @@
+module Puppet
+ # Simple module to manage vendored code.
+ #
+ # To vendor a library:
+ #
+ # * Download its whole git repo or untar into `lib/puppet/vendor/<libname>`
+ # * Create a lib/puppetload_libraryname.rb file to add its libdir into the $:.
+ # (Look at existing load_xxx files, they should all follow the same pattern).
+ # * To load the vendored lib upfront, add a `require '<vendorlib>'`line to
+ # `vendor/require_vendored.rb`.
+ # * To load the vendored lib on demand, add a comment to `vendor/require_vendored.rb`
+ # to make it clear it should not be loaded upfront.
+ #
+ # At runtime, the #load_vendored method should be called. It will ensure
+ # all vendored libraries are added to the global `$:` path, and
+ # will then call execute the up-front loading specified in `vendor/require_vendored.rb`.
+ #
+ # The intention is to not change vendored libraries and to eventually
+ # make adding them in optional so that distros can simply adjust their
+ # packaging to exclude this directory and the various load_xxx.rb scripts
+ # if they wish to install these gems as native packages.
+ #
+ class Vendor
+ class << self
+ # @api private
+ def vendor_dir
+ File.join([File.dirname(File.expand_path(__FILE__)), "vendor"])
+ end
+
+ # @api private
+ def load_entry(entry)
+ Puppet.debug("Loading vendored #{$1}")
+ load "#{vendor_dir}/#{entry}"
+ end
+
+ # @api private
+ def require_libs
+ require 'puppet/vendor/require_vendored'
+ end
+
+ # Configures the path for all vendored libraries and loads required libraries.
+ # (This is the entry point for loading vendored libraries).
+ #
+ def load_vendored
+ Dir.entries(vendor_dir).each do |entry|
+ if entry.match(/load_(\w+?)\.rb$/)
+ load_entry entry
+ end
+ end
+
+ require_libs
+ end
+ end
+ end
+end
diff --git a/lib/puppet/vendor/load_safe_yaml.rb b/lib/puppet/vendor/load_safe_yaml.rb
new file mode 100644
index 0000000..07aa0a6
--- /dev/null
+++ b/lib/puppet/vendor/load_safe_yaml.rb
@@ -0,0 +1 @@
+$: << File.join([File.dirname(__FILE__), "safe_yaml/lib"])
diff --git a/lib/puppet/vendor/require_vendored.rb b/lib/puppet/vendor/require_vendored.rb
new file mode 100644
index 0000000..1de2458
--- /dev/null
+++ b/lib/puppet/vendor/require_vendored.rb
@@ -0,0 +1,4 @@
+# This adds upfront requirements on vendored code found under lib/vendor/x
+# Add one requirement per vendored package (or a comment if it is loaded on demand).
+
+require 'safe_yaml'
diff --git a/lib/puppet/vendor/safe_yaml/.gitignore b/lib/puppet/vendor/safe_yaml/.gitignore
new file mode 100644
index 0000000..c6067bf
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/.gitignore
@@ -0,0 +1,2 @@
+Gemfile.lock
+dist/
diff --git a/lib/puppet/vendor/safe_yaml/.travis.yml b/lib/puppet/vendor/safe_yaml/.travis.yml
new file mode 100644
index 0000000..ec42077
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/.travis.yml
@@ -0,0 +1,47 @@
+language:
+ ruby
+
+before_install:
+ gem install bundler
+
+script:
+ bundle exec rake spec
+
+rvm:
+ - ruby-head
+ - 2.0.0
+ - 1.9.3
+ - 1.9.2
+ - 1.8.7
+ - rbx-19mode
+ - rbx-18mode
+ - jruby-head
+ - jruby-19mode
+ - jruby-18mode
+ - ree
+
+env:
+ - YAMLER=syck
+ - YAMLER=psych
+
+matrix:
+ allow_failures:
+ - rvm: ruby-head
+ - rvm: rbx-19mode
+ - rvm: rbx-18mode
+ - rvm: ree
+
+ exclude:
+ - rvm: 1.8.7
+ env: YAMLER=psych
+ - rvm: jruby-head
+ env: YAMLER=syck
+ - rvm: jruby-19mode
+ env: YAMLER=syck
+ - rvm: jruby-18mode
+ env: YAMLER=syck
+
+branches:
+ only:
+ - master
+
diff --git a/lib/puppet/vendor/safe_yaml/CHANGES.md b/lib/puppet/vendor/safe_yaml/CHANGES.md
new file mode 100644
index 0000000..1efd80c
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/CHANGES.md
@@ -0,0 +1,104 @@
+0.9.2
+-----
+
+- fixed error w/ parsing "!" when whitelisting tags
+- fixed parsing of the number 0 (d'oh!)
+
+0.9.1
+-----
+
+- added Yecht support (JRuby)
+- more bug fixes
+
+0.9.0
+-----
+
+- added `whitelist!` method for easily whitelisting tags
+- added support for call-specific options
+- removed deprecated methods
+
+0.8.6
+-----
+
+- fixed bug in float matcher
+
+0.8.5
+-----
+
+- performance improvements
+- made less verbose by default
+- bug fixes
+
+0.8.4
+-----
+
+- enhancements to parsing of integers, floats, and dates
+- updated built-in whitelist
+- more bug fixes
+
+0.8.3
+-----
+
+- fixed exception on parsing empty document
+- fixed handling of octal & hexadecimal numbers
+
+0.8.2
+-----
+
+- bug fixes
+
+0.8.1
+-----
+
+- added `:raise_on_unknown_tag` option
+- renamed `reset_defaults!` to `restore_defaults!`
+
+0.8
+---
+
+- added tag whitelisting
+- more API changes
+
+0.7
+---
+
+- separated YAML engine support from Ruby version
+- added support for binary scalars
+- numerous bug fixes and enhancements
+
+0.6
+---
+
+- several API changes
+- added `SafeYAML::OPTIONS` for specifying default behavior
+
+0.5
+---
+
+Added support for dates
+
+0.4
+---
+
+- efficiency improvements
+- made `YAML.load` use `YAML.safe_load` by default
+- made symbol deserialization optional
+
+0.3
+---
+
+Added Syck support
+
+0.2
+---
+
+Added support for:
+
+- anchors & aliases
+- booleans
+- nils
+
+0.1
+---
+
+Initial release
\ No newline at end of file
diff --git a/lib/puppet/vendor/safe_yaml/Gemfile b/lib/puppet/vendor/safe_yaml/Gemfile
new file mode 100644
index 0000000..24d7e3e
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/Gemfile
@@ -0,0 +1,11 @@
+source "https://rubygems.org"
+
+gemspec
+
+group :development do
+ gem "hashie"
+ gem "heredoc_unindent"
+ gem "rake"
+ gem "rspec"
+ gem "travis-lint"
+end
diff --git a/lib/puppet/vendor/safe_yaml/LICENSE.txt b/lib/puppet/vendor/safe_yaml/LICENSE.txt
new file mode 100644
index 0000000..4b276dd
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright (c) 2013 Dan Tao
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/lib/puppet/vendor/safe_yaml/README.md b/lib/puppet/vendor/safe_yaml/README.md
new file mode 100644
index 0000000..58b39c0
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/README.md
@@ -0,0 +1,179 @@
+SafeYAML
+========
+
+[![Build Status](https://travis-ci.org/dtao/safe_yaml.png)](http://travis-ci.org/dtao/safe_yaml)
+
+The **SafeYAML** gem provides an alternative implementation of `YAML.load` suitable for accepting user input in Ruby applications. Unlike Ruby's built-in implementation of `YAML.load`, SafeYAML's version will not expose apps to arbitrary code execution exploits (such as [the ones discovered](http://www.reddit.com/r/netsec/comments/167c11/serious_vulnerability_in_ruby_on_rails_allowing/) [in Rails in early 2013](http://www.h-online.com/open/news/item/Rails-developers-close-another-extremely-critical-flaw-1793511.html)).
+
+**If you encounter any issues with SafeYAML, check out the 'Common Issues' section below.** If you don't see anything that addresses the problem you're experiencing, by all means, [create an issue](https://github.com/dtao/safe_yaml/issues/new)!
+
+Installation
+------------
+
+Add this line to your application's Gemfile:
+
+ gem "safe_yaml"
+
+And then execute:
+
+ $ bundle
+
+Or install it yourself as:
+
+ $ gem install safe_yaml
+
+Configuration
+-------------
+
+Configuring SafeYAML should be quick. In most cases, you will probably only have to think about two things:
+
+1. What do you want the `YAML` module's *default* behavior to be? Set the `SafeYAML::OPTIONS[:default_mode]` option to either `:safe` or `:unsafe` to control this. If you do neither, SafeYAML will default to `:safe` mode but will issue a warning the first time you call `YAML.load`.
+2. Do you want to allow symbols by default? Set the `SafeYAML::OPTIONS[:deserialize_symbols]` option to `true` or `false` to control this. The default is `false`, which means that SafeYAML will deserialize symbols in YAML documents as strings.
+
+For more information on these and other options, see the "Usage" section down below.
+
+Explanation
+-----------
+
+Suppose your application were to use a popular open source library which contained code like this:
+
+```ruby
+class ClassBuilder
+ def []=(key, value)
+ @class ||= Class.new
+
+ @class.class_eval <<-EOS
+ def #{key}
+ #{value}
+ end
+ EOS
+ end
+
+ def create
+ @class.new
+ end
+end
+```
+
+Now, if you were to use `YAML.load` on user input anywhere in your application without the SafeYAML gem installed, an attacker who suspected you were using this library could send a request with a carefully-crafted YAML string to execute arbitrary code (yes, including `system("unix command")`) on your servers.
+
+This simple example demonstrates the vulnerability:
+
+```ruby
+yaml = <<-EOYAML
+--- !ruby/hash:ClassBuilder
+"foo; end; puts %(I'm in yr system!); def bar": "baz"
+EOYAML
+```
+
+ > YAML.load(yaml)
+ I'm in yr system!
+ => #<ClassBuilder:0x007fdbbe2e25d8 @class=#<Class:0x007fdbbe2e2510>>
+
+With SafeYAML, the same attacker would be thwarted:
+
+ > require "safe_yaml"
+ => true
+ > YAML.load(yaml, :safe => true)
+ => {"foo; end; puts %(I'm in yr system!); def bar"=>"baz"}
+
+Usage
+-----
+
+When you require the safe_yaml gem in your project, `YAML.load` is patched to accept one additional (optional) `options` parameter. This changes the method signature as follows:
+
+- for Syck and Psych prior to Ruby 1.9.3: `YAML.load(yaml, options={})`
+- for Psych in 1.9.3 and later: `YAML.load(yaml, filename=nil, options={})`
+
+The most important option is the `:safe` option (default: `true`), which controls whether or not to deserialize arbitrary objects when parsing a YAML document. The other options, along with explanations, are as follows.
+
+- `:deserialize_symbols` (default: `false`): Controls whether or not YAML will deserialize symbols. It is probably best to only enable this option where necessary, e.g. to make trusted libraries work. Symbols receive special treatment in Ruby and are not garbage collected, which means deserializing them indiscriminately may render your site vulnerable to a DOS attack (hence `false` as a default value).
+
+- `:whitelisted_tags`: Accepts an array of YAML tags that designate trusted types, e.g., ones that can be deserialized without worrying about any resulting security vulnerabilities. When any of the given tags are encountered in a YAML document, the associated data will be parsed by the underlying YAML engine (Syck or Psych) for the version of Ruby you are using. See the "Whitelisting Trusted Types" section below for more information.
+
+- `:custom_initializers`: Similar to the `:whitelisted_tags` option, but allows you to provide your own initializers for specified tags rather than using Syck or Psyck. Accepts a hash with string tags for keys and lambdas for values.
+
+- `:raise_on_unknown_tag` (default: `false`): Represents the highest possible level of paranoia (not necessarily a bad thing); if the YAML engine encounters any tag other than ones that are automatically trusted by SafeYAML or that you've explicitly whitelisted, it will raise an exception. This may be a good choice if you expect to always be dealing with perfectly safe YAML and want your application to fail loudly upon encountering questionable data.
+
+All of the above options can be set at the global level via `SafeYAML::OPTIONS`. You can also set each one individually per call to `YAML.load`; an option explicitly passed to `load` will take precedence over an option specified globally.
+
+Supported Types
+---------------
+
+The way that SafeYAML works is by restricting the kinds of objects that can be deserialized via `YAML.load`. More specifically, only the following types of objects can be deserialized by default:
+
+- Hashes
+- Arrays
+- Strings
+- Numbers
+- Dates
+- Times
+- Booleans
+- Nils
+
+Again, deserialization of symbols can be enabled globally by setting `SafeYAML::OPTIONS[:deserialize_symbols] = true`, or in a specific call to `YAML.load([some yaml], :deserialize_symbols => true)`.
+
+Whitelisting Trusted Types
+--------------------------
+
+SafeYAML supports whitelisting certain YAML tags for trusted types. This is handy when your application uses YAML to serialize and deserialize certain types not listed above, which you know to be free of any deserialization-related vulnerabilities.
+
+The easiest way to whitelist types is by calling `SafeYAML.whitelist!`, which can accept a variable number of safe types, e.g.:
+
+```ruby
+SafeYAML.whitelist!(FrobDispenser, GobbleFactory)
+```
+
+You can also whitelist YAML *tags* via the `:whitelisted_tags` option:
+
+```ruby
+# Using Syck
+SafeYAML::OPTIONS[:whitelisted_tags] = ["tag:ruby.yaml.org,2002:object:OpenStruct"]
+
+# Using Psych
+SafeYAML::OPTIONS[:whitelisted_tags] = ["!ruby/object:OpenStruct"]
+```
+
+And in case you were wondering: no, this feature will *not* allow would-be attackers to embed untrusted types within trusted types:
+
+```ruby
+yaml = <<-EOYAML
+--- !ruby/object:OpenStruct
+table:
+ :backdoor: !ruby/hash:ClassBuilder
+ "foo; end; puts %(I'm in yr system!); def bar": "baz"
+EOYAML
+```
+
+ > YAML.safe_load(yaml)
+ => #<OpenStruct :backdoor={"foo; end; puts %(I'm in yr system!); def bar"=>"baz"}>
+
+Known Issues
+------------
+
+If you add SafeYAML to your project and start seeing any errors about missing keys, or you notice mysterious strings that look like `":foo"` (i.e., start with a colon), it's likely you're seeing errors from symbols being saved in YAML format. If you are able to modify the offending code, you might want to consider changing your YAML content to use plain vanilla strings instead of symbols. If not, you may need to set the `:deserialize_symbols` option to `true`, either in calls to `YAML.load` or--as a last resort--globally, with `SafeYAML::OPTIONS[:deserialize_symbols]`.
+
+Also be aware that some Ruby libraries, particularly those requiring inter-process communication, leverage YAML's object deserialization functionality and therefore may break or otherwise be impacted by SafeYAML. The following list includes known instances of SafeYAML's interaction with other Ruby gems:
+
+- [**ActiveRecord**](https://github.com/rails/rails/tree/master/activerecord): uses YAML to control serialization of model objects using the `serialize` class method. If you find that accessing serialized properties on your ActiveRecord models is causing errors, chances are you may need to:
+ 1. set the `:deserialize_symbols` option to `true`,
+ 2. whitelist some of the types in your serialized data via `SafeYAML.whitelist!` or the `:whitelisted_tags` option, or
+ 3. both
+- [**Guard**](https://github.com/guard/guard): Uses YAML as a serialization format for notifications. The data serialized uses symbolic keys, so setting `SafeYAML::OPTIONS[:deserialize_symbols] = true` is necessary to allow Guard to work.
+- [**sidekiq**](https://github.com/mperham/sidekiq): Uses a YAML configiuration file with symbolic keys, so setting `SafeYAML::OPTIONS[:deserialize_symbols] = true` should allow it to work.
+
+The above list will grow over time, as more issues are discovered.
+
+Caveat
+------
+
+My intention is to eventually adopt [semantic versioning](http://semver.org/) with this gem, if it ever gets to version 1.0 (i.e., doesn't become obsolete by then). Since it isn't there yet, that means that API may well change from one version to the next. Please keep that in mind if you are using it in your application.
+
+To be clear: my *goal* is for SafeYAML to make it as easy as possible to protect existing applications from object deserialization exploits. Any and all feedback is more than welcome!
+
+Requirements
+------------
+
+SafeYAML requires Ruby 1.8.7 or newer and works with both [Syck](http://www.ruby-doc.org/stdlib-1.8.7/libdoc/yaml/rdoc/YAML.html) and [Psych](http://github.com/tenderlove/psych).
+
+If you are using a version of Ruby where Psych is the default YAML engine (e.g., 1.9.3) but you want to use Syck, be sure to set `YAML::ENGINE.yamler = "syck"` **before** requiring the safe_yaml gem.
diff --git a/lib/puppet/vendor/safe_yaml/Rakefile b/lib/puppet/vendor/safe_yaml/Rakefile
new file mode 100644
index 0000000..3199194
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/Rakefile
@@ -0,0 +1,6 @@
+require "rspec/core/rake_task"
+
+desc "Run specs"
+RSpec::Core::RakeTask.new(:spec) do |t|
+ t.rspec_opts = %w(--color)
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml.rb
new file mode 100644
index 0000000..8670ace
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml.rb
@@ -0,0 +1,253 @@
+require "yaml"
+
+# This needs to be defined up front in case any internal classes need to base
+# their behavior off of this.
+module SafeYAML
+ YAML_ENGINE = defined?(YAML::ENGINE) ? YAML::ENGINE.yamler : "syck"
+end
+
+require "set"
+require "safe_yaml/deep"
+require "safe_yaml/parse/hexadecimal"
+require "safe_yaml/parse/sexagesimal"
+require "safe_yaml/parse/date"
+require "safe_yaml/transform/transformation_map"
+require "safe_yaml/transform/to_boolean"
+require "safe_yaml/transform/to_date"
+require "safe_yaml/transform/to_float"
+require "safe_yaml/transform/to_integer"
+require "safe_yaml/transform/to_nil"
+require "safe_yaml/transform/to_symbol"
+require "safe_yaml/transform"
+require "safe_yaml/resolver"
+require "safe_yaml/syck_hack" if defined?(JRUBY_VERSION)
+
+module SafeYAML
+ MULTI_ARGUMENT_YAML_LOAD = YAML.method(:load).arity != 1
+
+ DEFAULT_OPTIONS = Deep.freeze({
+ :default_mode => nil,
+ :suppress_warnings => false,
+ :deserialize_symbols => false,
+ :whitelisted_tags => [],
+ :custom_initializers => {},
+ :raise_on_unknown_tag => false
+ })
+
+ OPTIONS = Deep.copy(DEFAULT_OPTIONS)
+
+ module_function
+ def restore_defaults!
+ OPTIONS.clear.merge!(Deep.copy(DEFAULT_OPTIONS))
+ end
+
+ def tag_safety_check!(tag, options)
+ return if tag.nil? || tag == "!"
+ if options[:raise_on_unknown_tag] && !options[:whitelisted_tags].include?(tag) && !tag_is_explicitly_trusted?(tag)
+ raise "Unknown YAML tag '#{tag}'"
+ end
+ end
+
+ def whitelist!(*classes)
+ classes.each do |klass|
+ whitelist_class!(klass)
+ end
+ end
+
+ def whitelist_class!(klass)
+ raise "#{klass} not a Class" unless klass.is_a?(::Class)
+
+ klass_name = klass.name
+ raise "#{klass} cannot be anonymous" if klass_name.nil? || klass_name.empty?
+
+ # Whitelist any built-in YAML tags supplied by Syck or Psych.
+ predefined_tag = predefined_tags[klass]
+ if predefined_tag
+ OPTIONS[:whitelisted_tags] << predefined_tag
+ return
+ end
+
+ # Exception is exceptional (har har).
+ tag_class = klass < Exception ? "exception" : "object"
+
+ tag_prefix = case YAML_ENGINE
+ when "psych" then "!ruby/#{tag_class}"
+ when "syck" then "tag:ruby.yaml.org,2002:#{tag_class}"
+ else raise "unknown YAML_ENGINE #{YAML_ENGINE}"
+ end
+ OPTIONS[:whitelisted_tags] << "#{tag_prefix}:#{klass_name}"
+ end
+
+ def predefined_tags
+ if @predefined_tags.nil?
+ @predefined_tags = {}
+
+ if YAML_ENGINE == "syck"
+ YAML.tagged_classes.each do |tag, klass|
+ @predefined_tags[klass] = tag
+ end
+
+ else
+ # Special tags appear to be hard-coded in Psych:
+ # https://github.com/tenderlove/psych/blob/v1.3.4/lib/psych/visitors/to_ruby.rb
+ # Fortunately, there aren't many that SafeYAML doesn't already support.
+ @predefined_tags.merge!({
+ Exception => "!ruby/exception",
+ Range => "!ruby/range",
+ Regexp => "!ruby/regexp",
+ })
+ end
+ end
+
+ @predefined_tags
+ end
+
+ if YAML_ENGINE == "psych"
+ def tag_is_explicitly_trusted?(tag)
+ false
+ end
+
+ else
+ TRUSTED_TAGS = Set.new([
+ "tag:yaml.org,2002:binary",
+ "tag:yaml.org,2002:bool#no",
+ "tag:yaml.org,2002:bool#yes",
+ "tag:yaml.org,2002:float",
+ "tag:yaml.org,2002:float#fix",
+ "tag:yaml.org,2002:int",
+ "tag:yaml.org,2002:map",
+ "tag:yaml.org,2002:null",
+ "tag:yaml.org,2002:seq",
+ "tag:yaml.org,2002:str",
+ "tag:yaml.org,2002:timestamp",
+ "tag:yaml.org,2002:timestamp#ymd"
+ ]).freeze
+
+ def tag_is_explicitly_trusted?(tag)
+ TRUSTED_TAGS.include?(tag)
+ end
+ end
+end
+
+module YAML
+ def self.load_with_options(yaml, *original_arguments)
+ filename, options = filename_and_options_from_arguments(original_arguments)
+ safe_mode = safe_mode_from_options("load", options)
+ arguments = [yaml]
+
+ if safe_mode == :safe
+ arguments << filename if SafeYAML::YAML_ENGINE == "psych"
+ arguments << options_for_safe_load(options)
+ safe_load(*arguments)
+ else
+ arguments << filename if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
+ unsafe_load(*arguments)
+ end
+ end
+
+ def self.load_file_with_options(file, options={})
+ safe_mode = safe_mode_from_options("load_file", options)
+ if safe_mode == :safe
+ safe_load_file(file, options_for_safe_load(options))
+ else
+ unsafe_load_file(file)
+ end
+ end
+
+ if SafeYAML::YAML_ENGINE == "psych"
+ require "safe_yaml/psych_handler"
+ require "safe_yaml/psych_resolver"
+ require "safe_yaml/safe_to_ruby_visitor"
+
+ def self.safe_load(yaml, filename=nil, options={})
+ # If the user hasn't whitelisted any tags, we can go with this implementation which is
+ # significantly faster.
+ if (options && options[:whitelisted_tags] || SafeYAML::OPTIONS[:whitelisted_tags]).empty?
+ safe_handler = SafeYAML::PsychHandler.new(options)
+ arguments_for_parse = [yaml]
+ arguments_for_parse << filename if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
+ Psych::Parser.new(safe_handler).parse(*arguments_for_parse)
+ return safe_handler.result || false
+
+ else
+ safe_resolver = SafeYAML::PsychResolver.new(options)
+ tree = SafeYAML::MULTI_ARGUMENT_YAML_LOAD ?
+ Psych.parse(yaml, filename) :
+ Psych.parse(yaml)
+ return safe_resolver.resolve_node(tree)
+ end
+ end
+
+ def self.safe_load_file(filename, options={})
+ File.open(filename, 'r:bom|utf-8') { |f| self.safe_load(f, filename, options) }
+ end
+
+ def self.unsafe_load_file(filename)
+ if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
+ # https://github.com/tenderlove/psych/blob/v1.3.2/lib/psych.rb#L296-298
+ File.open(filename, 'r:bom|utf-8') { |f| self.unsafe_load(f, filename) }
+ else
+ # https://github.com/tenderlove/psych/blob/v1.2.2/lib/psych.rb#L231-233
+ self.unsafe_load File.open(filename)
+ end
+ end
+
+ else
+ require "safe_yaml/syck_resolver"
+ require "safe_yaml/syck_node_monkeypatch"
+
+ def self.safe_load(yaml, options={})
+ resolver = SafeYAML::SyckResolver.new(SafeYAML::OPTIONS.merge(options || {}))
+ tree = YAML.parse(yaml)
+ return resolver.resolve_node(tree)
+ end
+
+ def self.safe_load_file(filename, options={})
+ File.open(filename) { |f| self.safe_load(f, options) }
+ end
+
+ def self.unsafe_load_file(filename)
+ # https://github.com/indeyets/syck/blob/master/ext/ruby/lib/yaml.rb#L133-135
+ File.open(filename) { |f| self.unsafe_load(f) }
+ end
+ end
+
+ class << self
+ alias_method :unsafe_load, :load
+ alias_method :load, :load_with_options
+ alias_method :load_file, :load_file_with_options
+
+ private
+ def filename_and_options_from_arguments(arguments)
+ if arguments.count == 1
+ if arguments.first.is_a?(String)
+ return arguments.first, {}
+ else
+ return nil, arguments.first || {}
+ end
+
+ else
+ return arguments.first, arguments.last || {}
+ end
+ end
+
+ def safe_mode_from_options(method, options={})
+ if options[:safe].nil?
+ safe_mode = SafeYAML::OPTIONS[:default_mode] || :safe
+ if SafeYAML::OPTIONS[:default_mode].nil? && !SafeYAML::OPTIONS[:suppress_warnings]
+ Kernel.warn "Called '#{method}' without the :safe option -- defaulting to #{safe_mode} mode."
+ SafeYAML::OPTIONS[:suppress_warnings] = true
+ end
+ return safe_mode
+ end
+
+ options[:safe] ? :safe : :unsafe
+ end
+
+ def options_for_safe_load(base_options)
+ options = base_options.dup
+ options.delete(:safe)
+ options
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/deep.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/deep.rb
new file mode 100644
index 0000000..6a5e037
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/deep.rb
@@ -0,0 +1,34 @@
+module SafeYAML
+ class Deep
+ def self.freeze(object)
+ object.each do |*entry|
+ value = entry.last
+ case value
+ when String, Regexp
+ value.freeze
+ when Enumerable
+ Deep.freeze(value)
+ end
+ end
+
+ return object.freeze
+ end
+
+ def self.copy(object)
+ duplicate = object.dup rescue object
+
+ case object
+ when Array
+ (0...duplicate.count).each do |i|
+ duplicate[i] = Deep.copy(duplicate[i])
+ end
+ when Hash
+ duplicate.keys.each do |key|
+ duplicate[key] = Deep.copy(duplicate[key])
+ end
+ end
+
+ duplicate
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/date.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/date.rb
new file mode 100644
index 0000000..0772941
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/date.rb
@@ -0,0 +1,27 @@
+module SafeYAML
+ class Parse
+ class Date
+ # This one's easy enough :)
+ DATE_MATCHER = /\A(\d{4})-(\d{2})-(\d{2})\Z/.freeze
+
+ # This unbelievable little gem is taken basically straight from the YAML spec, but made
+ # slightly more readable (to my poor eyes at least) to me:
+ # http://yaml.org/type/timestamp.html
+ TIME_MATCHER = /\A\d{4}-\d{1,2}-\d{1,2}(?:[Tt]|\s+)\d{1,2}:\d{2}:\d{2}(?:\.\d*)?\s*(?:Z|[-+]\d{1,2}(?::?\d{2})?)?\Z/.freeze
+
+ SECONDS_PER_DAY = 60 * 60 * 24
+ MICROSECONDS_PER_SECOND = 1000000
+
+ # So this is weird. In Ruby 1.8.7, the DateTime#sec_fraction method returned fractional
+ # seconds in units of DAYS for some reason. In 1.9.2, they changed the units -- much more
+ # reasonably -- to seconds.
+ SEC_FRACTION_MULTIPLIER = RUBY_VERSION == "1.8.7" ? (SECONDS_PER_DAY * MICROSECONDS_PER_SECOND) : MICROSECONDS_PER_SECOND
+
+ def self.value(value)
+ d = DateTime.parse(value)
+ usec = d.sec_fraction * SEC_FRACTION_MULTIPLIER
+ Time.utc(d.year, d.month, d.day, d.hour, d.min, d.sec, usec) - (d.offset * SECONDS_PER_DAY)
+ end
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/hexadecimal.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/hexadecimal.rb
new file mode 100644
index 0000000..8da3624
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/hexadecimal.rb
@@ -0,0 +1,12 @@
+module SafeYAML
+ class Parse
+ class Hexadecimal
+ MATCHER = /\A[-+]?0x[0-9a-fA-F_]+\Z/.freeze
+
+ def self.value(value)
+ # This is safe to do since we already validated the value.
+ return Integer(value.gsub(/_/, ""))
+ end
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/sexagesimal.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/sexagesimal.rb
new file mode 100644
index 0000000..3fff5bb
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/sexagesimal.rb
@@ -0,0 +1,26 @@
+module SafeYAML
+ class Parse
+ class Sexagesimal
+ INTEGER_MATCHER = /\A[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+\Z/.freeze
+ FLOAT_MATCHER = /\A[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+\.[0-9_]*\Z/.freeze
+
+ def self.value(value)
+ before_decimal, after_decimal = value.split(".")
+
+ whole_part = 0
+ multiplier = 1
+
+ before_decimal = before_decimal.split(":")
+ until before_decimal.empty?
+ whole_part += (Float(before_decimal.pop) * multiplier)
+ multiplier *= 60
+ end
+
+ result = whole_part
+ result += Float("." + after_decimal) unless after_decimal.nil?
+ result *= -1 if value[0] == "-"
+ result
+ end
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_handler.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_handler.rb
new file mode 100644
index 0000000..327fbc1
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_handler.rb
@@ -0,0 +1,92 @@
+require "psych"
+require "base64"
+
+module SafeYAML
+ class PsychHandler < Psych::Handler
+ def initialize(options)
+ @options = SafeYAML::OPTIONS.merge(options || {})
+ @initializers = @options[:custom_initializers] || {}
+ @anchors = {}
+ @stack = []
+ @current_key = nil
+ @result = nil
+ end
+
+ def result
+ @result
+ end
+
+ def add_to_current_structure(value, anchor=nil, quoted=nil, tag=nil)
+ value = Transform.to_proper_type(value, quoted, tag, @options)
+
+ @anchors[anchor] = value if anchor
+
+ if @result.nil?
+ @result = value
+ @current_structure = @result
+ return
+ end
+
+ if @current_structure.respond_to?(:<<)
+ @current_structure << value
+
+ elsif @current_structure.respond_to?(:[]=)
+ if @current_key.nil?
+ @current_key = value
+
+ else
+ if @current_key == "<<"
+ @current_structure.merge!(value)
+ else
+ @current_structure[@current_key] = value
+ end
+
+ @current_key = nil
+ end
+
+ else
+ raise "Don't know how to add to a #{@current_structure.class}!"
+ end
+ end
+
+ def end_current_structure
+ @stack.pop
+ @current_structure = @stack.last
+ end
+
+ def streaming?
+ false
+ end
+
+ # event handlers
+ def alias(anchor)
+ add_to_current_structure(@anchors[anchor])
+ end
+
+ def scalar(value, anchor, tag, plain, quoted, style)
+ add_to_current_structure(value, anchor, quoted, tag)
+ end
+
+ def start_mapping(anchor, tag, implicit, style)
+ map = @initializers.include?(tag) ? @initializers[tag].call : {}
+ self.add_to_current_structure(map, anchor)
+ @current_structure = map
+ @stack.push(map)
+ end
+
+ def end_mapping
+ self.end_current_structure()
+ end
+
+ def start_sequence(anchor, tag, implicit, style)
+ seq = @initializers.include?(tag) ? @initializers[tag].call : []
+ self.add_to_current_structure(seq, anchor)
+ @current_structure = seq
+ @stack.push(seq)
+ end
+
+ def end_sequence
+ self.end_current_structure()
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_resolver.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_resolver.rb
new file mode 100644
index 0000000..851989b
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_resolver.rb
@@ -0,0 +1,52 @@
+module SafeYAML
+ class PsychResolver < Resolver
+ NODE_TYPES = {
+ Psych::Nodes::Document => :root,
+ Psych::Nodes::Mapping => :map,
+ Psych::Nodes::Sequence => :seq,
+ Psych::Nodes::Scalar => :scalar,
+ Psych::Nodes::Alias => :alias
+ }.freeze
+
+ def initialize(options={})
+ super
+ @aliased_nodes = {}
+ end
+
+ def resolve_root(root)
+ resolve_seq(root).first
+ end
+
+ def resolve_alias(node)
+ resolve_node(@aliased_nodes[node.anchor])
+ end
+
+ def native_resolve(node)
+ @visitor ||= SafeYAML::SafeToRubyVisitor.new(self)
+ @visitor.accept(node)
+ end
+
+ def get_node_type(node)
+ NODE_TYPES[node.class]
+ end
+
+ def get_node_tag(node)
+ node.tag
+ end
+
+ def get_node_value(node)
+ @aliased_nodes[node.anchor] = node if node.respond_to?(:anchor) && node.anchor
+
+ case get_node_type(node)
+ when :root, :map, :seq
+ node.children
+ when :scalar
+ node.value
+ end
+ end
+
+ def value_is_quoted?(node)
+ node.quoted
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/resolver.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/resolver.rb
new file mode 100644
index 0000000..d15343f
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/resolver.rb
@@ -0,0 +1,94 @@
+module SafeYAML
+ class Resolver
+ def initialize(options)
+ @options = SafeYAML::OPTIONS.merge(options || {})
+ @whitelist = @options[:whitelisted_tags] || []
+ @initializers = @options[:custom_initializers] || {}
+ @raise_on_unknown_tag = @options[:raise_on_unknown_tag]
+ end
+
+ def resolve_node(node)
+ return node if !node
+ return self.native_resolve(node) if tag_is_whitelisted?(self.get_node_tag(node))
+
+ case self.get_node_type(node)
+ when :root
+ resolve_root(node)
+ when :map
+ resolve_map(node)
+ when :seq
+ resolve_seq(node)
+ when :scalar
+ resolve_scalar(node)
+ when :alias
+ resolve_alias(node)
+ else
+ raise "Don't know how to resolve this node: #{node.inspect}"
+ end
+ end
+
+ def resolve_map(node)
+ tag = get_and_check_node_tag(node)
+ hash = @initializers.include?(tag) ? @initializers[tag].call : {}
+ map = normalize_map(self.get_node_value(node))
+
+ # Take the "<<" key nodes first, as these are meant to approximate a form of inheritance.
+ inheritors = map.select { |key_node, value_node| resolve_node(key_node) == "<<" }
+ inheritors.each do |key_node, value_node|
+ merge_into_hash(hash, resolve_node(value_node))
+ end
+
+ # All that's left should be normal (non-"<<") nodes.
+ (map - inheritors).each do |key_node, value_node|
+ hash[resolve_node(key_node)] = resolve_node(value_node)
+ end
+
+ return hash
+ end
+
+ def resolve_seq(node)
+ seq = self.get_node_value(node)
+
+ tag = get_and_check_node_tag(node)
+ arr = @initializers.include?(tag) ? @initializers[tag].call : []
+
+ seq.inject(arr) { |array, node| array << resolve_node(node) }
+ end
+
+ def resolve_scalar(node)
+ Transform.to_proper_type(self.get_node_value(node), self.value_is_quoted?(node), get_and_check_node_tag(node), @options)
+ end
+
+ def get_and_check_node_tag(node)
+ tag = self.get_node_tag(node)
+ SafeYAML.tag_safety_check!(tag, @options)
+ tag
+ end
+
+ def tag_is_whitelisted?(tag)
+ @whitelist.include?(tag)
+ end
+
+ def options
+ @options
+ end
+
+ private
+ def normalize_map(map)
+ # Syck creates Hashes from maps.
+ if map.is_a?(Hash)
+ map.inject([]) { |arr, key_and_value| arr << key_and_value }
+
+ # Psych is really weird; it flattens out a Hash completely into: [key, value, key, value, ...]
+ else
+ map.each_slice(2).to_a
+ end
+ end
+
+ def merge_into_hash(hash, array)
+ array.each do |key, value|
+ hash[key] = value
+ end
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/safe_to_ruby_visitor.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/safe_to_ruby_visitor.rb
new file mode 100644
index 0000000..0a8c422
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/safe_to_ruby_visitor.rb
@@ -0,0 +1,17 @@
+module SafeYAML
+ class SafeToRubyVisitor < Psych::Visitors::ToRuby
+ def initialize(resolver)
+ super()
+ @resolver = resolver
+ end
+
+ def accept(node)
+ if node.tag
+ SafeYAML.tag_safety_check!(node.tag, @resolver.options)
+ return super
+ end
+
+ @resolver.resolve_node(node)
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_hack.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_hack.rb
new file mode 100644
index 0000000..08a5e47
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_hack.rb
@@ -0,0 +1,36 @@
+# Hack to JRuby 1.8's YAML Parser Yecht
+#
+# This file is always loaded AFTER either syck or psych are already
+# loaded. It then looks at what constants are available and creates
+# a consistent view on all rubys.
+#
+# Taken from rubygems and modified.
+# See https://github.com/rubygems/rubygems/blob/master/lib/rubygems/syck_hack.rb
+
+module YAML
+ # In newer 1.9.2, there is a Syck toplevel constant instead of it
+ # being underneith YAML. If so, reference it back under YAML as
+ # well.
+ if defined? ::Syck
+ # for tests that change YAML::ENGINE
+ # 1.8 does not support the second argument to const_defined?
+ remove_const :Syck rescue nil
+
+ Syck = ::Syck
+
+ # JRuby's "Syck" is called "Yecht"
+ elsif defined? YAML::Yecht
+ Syck = YAML::Yecht
+ end
+end
+
+# Sometime in the 1.9 dev cycle, the Syck constant was moved from under YAML
+# to be a toplevel constant. So gemspecs created under these versions of Syck
+# will have references to Syck::DefaultKey.
+#
+# So we need to be sure that we reference Syck at the toplevel too so that
+# we can always load these kind of gemspecs.
+#
+if !defined?(Syck)
+ Syck = YAML::Syck
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_node_monkeypatch.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_node_monkeypatch.rb
new file mode 100644
index 0000000..c026376
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_node_monkeypatch.rb
@@ -0,0 +1,43 @@
+# This is, admittedly, pretty insane. Fundamentally the challenge here is this: if we want to allow
+# whitelisting of tags (while still leveraging Syck's internal functionality), then we have to
+# change how Syck::Node#transform works. But since we (SafeYAML) do not control instantiation of
+# Syck::Node objects, we cannot, for example, subclass Syck::Node and override #tranform the "easy"
+# way. So the only choice is to monkeypatch, like this. And the only way to make this work
+# recursively with potentially call-specific options (that my feeble brain can think of) is to set
+# pseudo-global options on the first call and unset them once the recursive stack has fully unwound.
+
+monkeypatch = <<-EORUBY
+ class Node
+ @@safe_transform_depth = 0
+ @@safe_transform_whitelist = nil
+
+ def safe_transform(options={})
+ begin
+ @@safe_transform_depth += 1
+ @@safe_transform_whitelist ||= options[:whitelisted_tags]
+
+ if self.type_id
+ SafeYAML.tag_safety_check!(self.type_id, options)
+ return unsafe_transform if @@safe_transform_whitelist.include?(self.type_id)
+ end
+
+ SafeYAML::SyckResolver.new.resolve_node(self)
+
+ ensure
+ @@safe_transform_depth -= 1
+ if @@safe_transform_depth == 0
+ @@safe_transform_whitelist = nil
+ end
+ end
+ end
+
+ alias_method :unsafe_transform, :transform
+ alias_method :transform, :safe_transform
+ end
+EORUBY
+
+if defined?(YAML::Syck::Node)
+ YAML::Syck.module_eval monkeypatch
+else
+ Syck.module_eval monkeypatch
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_resolver.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_resolver.rb
new file mode 100644
index 0000000..10d55ab
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_resolver.rb
@@ -0,0 +1,38 @@
+module SafeYAML
+ class SyckResolver < Resolver
+ QUOTE_STYLES = [
+ :quote1,
+ :quote2
+ ].freeze
+
+ NODE_TYPES = {
+ Hash => :map,
+ Array => :seq,
+ String => :scalar
+ }.freeze
+
+ def initialize(options={})
+ super
+ end
+
+ def native_resolve(node)
+ node.transform(self.options)
+ end
+
+ def get_node_type(node)
+ NODE_TYPES[node.value.class]
+ end
+
+ def get_node_tag(node)
+ node.type_id
+ end
+
+ def get_node_value(node)
+ node.value
+ end
+
+ def value_is_quoted?(node)
+ QUOTE_STYLES.include?(node.instance_variable_get(:@style))
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform.rb
new file mode 100644
index 0000000..d61d1a9
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform.rb
@@ -0,0 +1,41 @@
+require 'base64'
+
+module SafeYAML
+ class Transform
+ TRANSFORMERS = [
+ Transform::ToSymbol.new,
+ Transform::ToInteger.new,
+ Transform::ToFloat.new,
+ Transform::ToNil.new,
+ Transform::ToBoolean.new,
+ Transform::ToDate.new
+ ]
+
+ def self.to_guessed_type(value, quoted=false, options=nil)
+ return value if quoted
+
+ if value.is_a?(String)
+ TRANSFORMERS.each do |transformer|
+ success, transformed_value = transformer.method(:transform?).arity == 1 ?
+ transformer.transform?(value) :
+ transformer.transform?(value, options)
+
+ return transformed_value if success
+ end
+ end
+
+ value
+ end
+
+ def self.to_proper_type(value, quoted=false, tag=nil, options=nil)
+ case tag
+ when "tag:yaml.org,2002:binary", "x-private:binary", "!binary"
+ decoded = Base64.decode64(value)
+ decoded = decoded.force_encoding(value.encoding) if decoded.respond_to?(:force_encoding)
+ decoded
+ else
+ self.to_guessed_type(value, quoted, options)
+ end
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_boolean.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_boolean.rb
new file mode 100644
index 0000000..99dc85e
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_boolean.rb
@@ -0,0 +1,21 @@
+module SafeYAML
+ class Transform
+ class ToBoolean
+ include TransformationMap
+
+ set_predefined_values({
+ "yes" => true,
+ "on" => true,
+ "true" => true,
+ "no" => false,
+ "off" => false,
+ "false" => false
+ })
+
+ def transform?(value)
+ return false if value.length > 5
+ return PREDEFINED_VALUES.include?(value), PREDEFINED_VALUES[value]
+ end
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_date.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_date.rb
new file mode 100644
index 0000000..f8f1265
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_date.rb
@@ -0,0 +1,11 @@
+module SafeYAML
+ class Transform
+ class ToDate
+ def transform?(value)
+ return true, Date.parse(value) if Parse::Date::DATE_MATCHER.match(value)
+ return true, Parse::Date.value(value) if Parse::Date::TIME_MATCHER.match(value)
+ false
+ end
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_float.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_float.rb
new file mode 100644
index 0000000..4ee3f5f
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_float.rb
@@ -0,0 +1,33 @@
+module SafeYAML
+ class Transform
+ class ToFloat
+ Infinity = 1.0 / 0.0
+ NaN = 0.0 / 0.0
+
+ PREDEFINED_VALUES = {
+ ".inf" => Infinity,
+ ".Inf" => Infinity,
+ ".INF" => Infinity,
+ "-.inf" => -Infinity,
+ "-.Inf" => -Infinity,
+ "-.INF" => -Infinity,
+ ".nan" => NaN,
+ ".NaN" => NaN,
+ ".NAN" => NaN,
+ }.freeze
+
+ MATCHER = /\A[-+]?(?:\d[\d_]*)?\.[\d_]+(?:[eE][-+][\d]+)?\Z/.freeze
+
+ def transform?(value)
+ return true, Float(value) if MATCHER.match(value)
+ try_edge_cases?(value)
+ end
+
+ def try_edge_cases?(value)
+ return true, PREDEFINED_VALUES[value] if PREDEFINED_VALUES.include?(value)
+ return true, Parse::Sexagesimal.value(value) if Parse::Sexagesimal::FLOAT_MATCHER.match(value)
+ return false
+ end
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_integer.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_integer.rb
new file mode 100644
index 0000000..25222d2
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_integer.rb
@@ -0,0 +1,25 @@
+module SafeYAML
+ class Transform
+ class ToInteger
+ MATCHERS = Deep.freeze([
+ /\A[-+]?(0|([1-9][0-9_,]*))\Z/, # decimal
+ /\A0[0-7]+\Z/, # octal
+ /\A0x[0-9a-f]+\Z/i, # hexadecimal
+ /\A0b[01_]+\Z/ # binary
+ ])
+
+ def transform?(value)
+ MATCHERS.each do |matcher|
+ return true, Integer(value.gsub(",", "")) if matcher.match(value)
+ end
+ try_edge_cases?(value)
+ end
+
+ def try_edge_cases?(value)
+ return true, Parse::Hexadecimal.value(value) if Parse::Hexadecimal::MATCHER.match(value)
+ return true, Parse::Sexagesimal.value(value) if Parse::Sexagesimal::INTEGER_MATCHER.match(value)
+ return false
+ end
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_nil.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_nil.rb
new file mode 100644
index 0000000..258ad46
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_nil.rb
@@ -0,0 +1,18 @@
+module SafeYAML
+ class Transform
+ class ToNil
+ include TransformationMap
+
+ set_predefined_values({
+ "" => nil,
+ "~" => nil,
+ "null" => nil,
+ })
+
+ def transform?(value)
+ return false if value.length > 4
+ return PREDEFINED_VALUES.include?(value), PREDEFINED_VALUES[value]
+ end
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_symbol.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_symbol.rb
new file mode 100644
index 0000000..d7eb494
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_symbol.rb
@@ -0,0 +1,13 @@
+module SafeYAML
+ class Transform
+ class ToSymbol
+ MATCHER = /\A:"?(\w+)"?\Z/.freeze
+
+ def transform?(value, options=nil)
+ options ||= SafeYAML::OPTIONS
+ return false unless options[:deserialize_symbols] && MATCHER.match(value)
+ return true, $1.to_sym
+ end
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/transformation_map.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/transformation_map.rb
new file mode 100644
index 0000000..d4e45ec
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/transformation_map.rb
@@ -0,0 +1,47 @@
+module SafeYAML
+ class Transform
+ module TransformationMap
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ class CaseAgnosticMap < Hash
+ def initialize(*args)
+ super
+ end
+
+ def include?(key)
+ super(key.downcase)
+ end
+
+ def [](key)
+ super(key.downcase)
+ end
+
+ # OK, I actually don't think it's all that important that this map be
+ # frozen.
+ def freeze
+ self
+ end
+ end
+
+ module ClassMethods
+ def set_predefined_values(predefined_values)
+ if SafeYAML::YAML_ENGINE == "syck"
+ expanded_map = predefined_values.inject({}) do |hash, (key, value)|
+ hash[key] = value
+ hash[key.capitalize] = value
+ hash[key.upcase] = value
+ hash
+ end
+ else
+ expanded_map = CaseAgnosticMap.new
+ expanded_map.merge!(predefined_values)
+ end
+
+ self.const_set(:PREDEFINED_VALUES, expanded_map.freeze)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/lib/safe_yaml/version.rb b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/version.rb
new file mode 100644
index 0000000..056d2b0
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/lib/safe_yaml/version.rb
@@ -0,0 +1,3 @@
+module SafeYAML
+ VERSION = "0.9.2"
+end
diff --git a/lib/puppet/vendor/safe_yaml/run_specs_all_ruby_versions.sh b/lib/puppet/vendor/safe_yaml/run_specs_all_ruby_versions.sh
new file mode 100755
index 0000000..4c17787
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/run_specs_all_ruby_versions.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+[[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm"
+
+rvm use 1.8.7@safe_yaml
+rake spec
+
+rvm use 1.9.2@safe_yaml
+YAMLER=syck rake spec
+
+rvm use 1.9.3@safe_yaml
+YAMLER=syck rake spec
+
+rvm use 1.9.2@safe_yaml
+YAMLER=psych rake spec
+
+rvm use 1.9.3@safe_yaml
+YAMLER=psych rake spec
+
+rvm use 2.0.0@safe_yaml
+YAMLER=psych rake spec
diff --git a/lib/puppet/vendor/safe_yaml/safe_yaml.gemspec b/lib/puppet/vendor/safe_yaml/safe_yaml.gemspec
new file mode 100644
index 0000000..dbcd926
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/safe_yaml.gemspec
@@ -0,0 +1,18 @@
+# -*- encoding: utf-8 -*-
+require File.join(File.dirname(__FILE__), "lib", "safe_yaml", "version")
+
+Gem::Specification.new do |gem|
+ gem.name = "safe_yaml"
+ gem.version = SafeYAML::VERSION
+ gem.authors = "Dan Tao"
+ gem.email = "daniel.tao@gmail.com"
+ gem.description = %q{Parse YAML safely, without that pesky arbitrary object deserialization vulnerability}
+ gem.summary = %q{SameYAML provides an alternative implementation of YAML.load suitable for accepting user input in Ruby applications.}
+ gem.homepage = "http://dtao.github.com/safe_yaml/"
+ gem.license = "MIT"
+ gem.files = `git ls-files`.split($\)
+ gem.test_files = gem.files.grep(%r{^spec/})
+ gem.require_paths = ["lib"]
+
+ gem.required_ruby_version = ">= 1.8.7"
+end
diff --git a/lib/puppet/vendor/safe_yaml/spec/exploit.1.9.2.yaml b/lib/puppet/vendor/safe_yaml/spec/exploit.1.9.2.yaml
new file mode 100644
index 0000000..bdd70cc
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/spec/exploit.1.9.2.yaml
@@ -0,0 +1,2 @@
+--- !ruby/object:ExploitableBackDoor
+foo: bar
diff --git a/lib/puppet/vendor/safe_yaml/spec/exploit.1.9.3.yaml b/lib/puppet/vendor/safe_yaml/spec/exploit.1.9.3.yaml
new file mode 100644
index 0000000..c24e04b
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/spec/exploit.1.9.3.yaml
@@ -0,0 +1,2 @@
+--- !ruby/hash:ExploitableBackDoor
+foo: bar
diff --git a/lib/puppet/vendor/safe_yaml/spec/psych_resolver_spec.rb b/lib/puppet/vendor/safe_yaml/spec/psych_resolver_spec.rb
new file mode 100644
index 0000000..269be5d
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/spec/psych_resolver_spec.rb
@@ -0,0 +1,10 @@
+require File.join(File.dirname(__FILE__), "spec_helper")
+
+if SafeYAML::YAML_ENGINE == "psych"
+ require "safe_yaml/psych_resolver"
+
+ describe SafeYAML::PsychResolver do
+ include ResolverSpecs
+ let(:resolver) { SafeYAML::PsychResolver.new }
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/spec/resolver_specs.rb b/lib/puppet/vendor/safe_yaml/spec/resolver_specs.rb
new file mode 100644
index 0000000..5e070b0
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/spec/resolver_specs.rb
@@ -0,0 +1,250 @@
+module ResolverSpecs
+ def self.included(base)
+ base.module_eval do
+ let(:resolver) { nil }
+ let(:result) { @result }
+
+ def parse(yaml)
+ tree = YAML.parse(yaml.unindent)
+ @result = resolver.resolve_node(tree)
+ end
+
+ # Isn't this how I should've been doing it all along?
+ def parse_and_test(yaml)
+ parse(yaml)
+ @result.should == YAML.unsafe_load(yaml)
+ end
+
+ context "by default" do
+ it "translates maps to hashes" do
+ parse <<-YAML
+ potayto: potahto
+ tomayto: tomahto
+ YAML
+
+ result.should == {
+ "potayto" => "potahto",
+ "tomayto" => "tomahto"
+ }
+ end
+
+ it "translates sequences to arrays" do
+ parse <<-YAML
+ - foo
+ - bar
+ - baz
+ YAML
+
+ result.should == ["foo", "bar", "baz"]
+ end
+
+ it "translates most values to strings" do
+ parse "string: value"
+ result.should == { "string" => "value" }
+ end
+
+ it "does not deserialize symbols" do
+ parse ":symbol: value"
+ result.should == { ":symbol" => "value" }
+ end
+
+ it "translates valid integral numbers to integers" do
+ parse "integer: 1"
+ result.should == { "integer" => 1 }
+ end
+
+ it "translates valid decimal numbers to floats" do
+ parse "float: 3.14"
+ result.should == { "float" => 3.14 }
+ end
+
+ it "translates valid dates" do
+ parse "date: 2013-01-24"
+ result.should == { "date" => Date.parse("2013-01-24") }
+ end
+
+ it "translates valid true/false values to booleans" do
+ parse <<-YAML
+ - yes
+ - true
+ - no
+ - false
+ YAML
+
+ result.should == [true, true, false, false]
+ end
+
+ it "translates valid nulls to nil" do
+ parse <<-YAML
+ -
+ - ~
+ - null
+ YAML
+
+ result.should == [nil] * 3
+ end
+
+ it "matches the behavior of the underlying YAML engine w/ respect to capitalization of boolean values" do
+ parse_and_test <<-YAML
+ - true
+ - True
+ - TRUE
+ - tRue
+ - TRue
+ - False
+ - FALSE
+ - fAlse
+ - FALse
+ YAML
+
+ # using Syck: [true, true, true, "tRue", "TRue", false, false, "fAlse", "FALse"]
+ # using Psych: all booleans
+ end
+
+ it "matches the behavior of the underlying YAML engine w/ respect to capitalization of nil values" do
+ parse_and_test <<-YAML
+ - Null
+ - NULL
+ - nUll
+ - NUll
+ YAML
+
+ # using Syck: [nil, nil, "nUll", "NUll"]
+ # using Psych: all nils
+ end
+
+ it "translates quoted empty strings to strings (not nil)" do
+ parse "foo: ''"
+ result.should == { "foo" => "" }
+ end
+
+ it "correctly reverse-translates strings encoded via #to_yaml" do
+ parse "5.10".to_yaml
+ result.should == "5.10"
+ end
+
+ it "does not specially parse any double-quoted strings" do
+ parse <<-YAML
+ - "1"
+ - "3.14"
+ - "true"
+ - "false"
+ - "2013-02-03"
+ - "2013-02-03 16:27:00 -0600"
+ YAML
+
+ result.should == ["1", "3.14", "true", "false", "2013-02-03", "2013-02-03 16:27:00 -0600"]
+ end
+
+ it "does not specially parse any single-quoted strings" do
+ parse <<-YAML
+ - '1'
+ - '3.14'
+ - 'true'
+ - 'false'
+ - '2013-02-03'
+ - '2013-02-03 16:27:00 -0600'
+ YAML
+
+ result.should == ["1", "3.14", "true", "false", "2013-02-03", "2013-02-03 16:27:00 -0600"]
+ end
+
+ it "deals just fine with nested maps" do
+ parse <<-YAML
+ foo:
+ bar:
+ marco: polo
+ YAML
+
+ result.should == { "foo" => { "bar" => { "marco" => "polo" } } }
+ end
+
+ it "deals just fine with nested sequences" do
+ parse <<-YAML
+ - foo
+ -
+ - bar1
+ - bar2
+ -
+ - baz1
+ - baz2
+ YAML
+
+ result.should == ["foo", ["bar1", "bar2", ["baz1", "baz2"]]]
+ end
+
+ it "applies the same transformations to keys as to values" do
+ parse <<-YAML
+ foo: string
+ :bar: symbol
+ 1: integer
+ 3.14: float
+ 2013-01-24: date
+ YAML
+
+ result.should == {
+ "foo" => "string",
+ ":bar" => "symbol",
+ 1 => "integer",
+ 3.14 => "float",
+ Date.parse("2013-01-24") => "date",
+ }
+ end
+
+ it "applies the same transformations to elements in sequences as to all values" do
+ parse <<-YAML
+ - foo
+ - :bar
+ - 1
+ - 3.14
+ - 2013-01-24
+ YAML
+
+ result.should == ["foo", ":bar", 1, 3.14, Date.parse("2013-01-24")]
+ end
+ end
+
+ context "for Ruby version #{RUBY_VERSION}" do
+ it "translates valid time values" do
+ parse "time: 2013-01-29 05:58:00 -0800"
+ result.should == { "time" => Time.utc(2013, 1, 29, 13, 58, 0) }
+ end
+
+ it "applies the same transformation to elements in sequences" do
+ parse "- 2013-01-29 05:58:00 -0800"
+ result.should == [Time.utc(2013, 1, 29, 13, 58, 0)]
+ end
+
+ it "applies the same transformation to keys" do
+ parse "2013-01-29 05:58:00 -0800: time"
+ result.should == { Time.utc(2013, 1, 29, 13, 58, 0) => "time" }
+ end
+ end
+
+ context "with symbol deserialization enabled" do
+ before :each do
+ SafeYAML::OPTIONS[:deserialize_symbols] = true
+ end
+
+ after :each do
+ SafeYAML.restore_defaults!
+ end
+
+ it "translates values starting with ':' to symbols" do
+ parse "symbol: :value"
+ result.should == { "symbol" => :value }
+ end
+
+ it "applies the same transformation to keys" do
+ parse ":bar: symbol"
+ result.should == { :bar => "symbol" }
+ end
+
+ it "applies the same transformation to elements in sequences" do
+ parse "- :bar"
+ result.should == [:bar]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/spec/safe_yaml_spec.rb b/lib/puppet/vendor/safe_yaml/spec/safe_yaml_spec.rb
new file mode 100644
index 0000000..d454b16
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/spec/safe_yaml_spec.rb
@@ -0,0 +1,702 @@
+require File.join(File.dirname(__FILE__), "spec_helper")
+
+require "exploitable_back_door"
+
+describe YAML do
+ # Essentially stolen from:
+ # https://github.com/rails/rails/blob/3-2-stable/activesupport/lib/active_support/core_ext/kernel/reporting.rb#L10-25
+ def silence_warnings
+ $VERBOSE = nil; yield
+ ensure
+ $VERBOSE = true
+ end
+
+ def safe_load_round_trip(object, options={})
+ yaml = object.to_yaml
+ if SafeYAML::YAML_ENGINE == "psych"
+ YAML.safe_load(yaml, nil, options)
+ else
+ YAML.safe_load(yaml, options)
+ end
+ end
+
+ before :each do
+ SafeYAML.restore_defaults!
+ end
+
+ after :each do
+ SafeYAML.restore_defaults!
+ end
+
+ describe "unsafe_load" do
+ if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
+ it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
+ backdoor = YAML.unsafe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
+ backdoor.should be_exploited_through_setter
+ end
+
+ it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
+ backdoor = YAML.unsafe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
+ backdoor.should be_exploited_through_init_with
+ end
+ end
+
+ it "allows exploits through objects w/ sensitive instance variables defined in YAML w/ !ruby/object" do
+ backdoor = YAML.unsafe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
+ backdoor.should be_exploited_through_ivars
+ end
+
+ context "with special whitelisted tags defined" do
+ before :each do
+ SafeYAML::whitelist!(OpenStruct)
+ end
+
+ it "effectively ignores the whitelist (since everything is whitelisted)" do
+ result = YAML.unsafe_load <<-YAML.unindent
+ --- !ruby/object:OpenStruct
+ table:
+ :backdoor: !ruby/object:ExploitableBackDoor
+ foo: bar
+ YAML
+
+ result.should be_a(OpenStruct)
+ result.backdoor.should be_exploited_through_ivars
+ end
+ end
+ end
+
+ describe "safe_load" do
+ it "does NOT allow exploits through objects defined in YAML w/ !ruby/hash" do
+ object = YAML.safe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
+ object.should_not be_a(ExploitableBackDoor)
+ end
+
+ it "does NOT allow exploits through objects defined in YAML w/ !ruby/object" do
+ object = YAML.safe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
+ object.should_not be_a(ExploitableBackDoor)
+ end
+
+ context "for YAML engine #{SafeYAML::YAML_ENGINE}" do
+ if SafeYAML::YAML_ENGINE == "psych"
+ let(:options) { nil }
+ let(:arguments) { ["foo: bar", nil, options] }
+
+ context "when no tags are whitelisted" do
+ it "constructs a SafeYAML::PsychHandler to resolve nodes as they're parsed, for optimal performance" do
+ Psych::Parser.should_receive(:new).with an_instance_of(SafeYAML::PsychHandler)
+ # This won't work now; we just want to ensure Psych::Parser#parse was in fact called.
+ YAML.safe_load(*arguments) rescue nil
+ end
+ end
+
+ context "when whitelisted tags are specified" do
+ let(:options) {
+ { :whitelisted_tags => ["foo"] }
+ }
+
+ it "instead uses Psych to construct a full tree before examining the nodes" do
+ Psych.should_receive(:parse)
+ # This won't work now; we just want to ensure Psych::Parser#parse was in fact called.
+ YAML.safe_load(*arguments) rescue nil
+ end
+ end
+ end
+
+ if SafeYAML::YAML_ENGINE == "syck"
+ it "uses Syck internally to parse YAML" do
+ YAML.should_receive(:parse).with("foo: bar")
+ # This won't work now; we just want to ensure YAML::parse was in fact called.
+ YAML.safe_load("foo: bar") rescue nil
+ end
+ end
+ end
+
+ it "loads a plain ol' YAML document just fine" do
+ result = YAML.safe_load <<-YAML.unindent
+ foo:
+ number: 1
+ string: Hello, there!
+ symbol: :blah
+ sequence:
+ - hi
+ - bye
+ YAML
+
+ result.should == {
+ "foo" => {
+ "number" => 1,
+ "string" => "Hello, there!",
+ "symbol" => ":blah",
+ "sequence" => ["hi", "bye"]
+ }
+ }
+ end
+
+ it "works for YAML documents with anchors and aliases" do
+ result = YAML.safe_load <<-YAML
+ - &id001 {}
+ - *id001
+ - *id001
+ YAML
+
+ result.should == [{}, {}, {}]
+ end
+
+ it "works for YAML documents with binary tagged keys" do
+ result = YAML.safe_load <<-YAML
+ ? !!binary >
+ Zm9v
+ : "bar"
+ ? !!binary >
+ YmFy
+ : "baz"
+ YAML
+
+ result.should == {"foo" => "bar", "bar" => "baz"}
+ end
+
+ it "works for YAML documents with binary tagged values" do
+ result = YAML.safe_load <<-YAML
+ "foo": !!binary >
+ YmFy
+ "bar": !!binary >
+ YmF6
+ YAML
+
+ result.should == {"foo" => "bar", "bar" => "baz"}
+ end
+
+ it "works for YAML documents with binary tagged array values" do
+ result = YAML.safe_load <<-YAML
+ - !binary |-
+ Zm9v
+ - !binary |-
+ YmFy
+ YAML
+
+ result.should == ["foo", "bar"]
+ end
+
+ it "works for YAML documents with sections" do
+ result = YAML.safe_load <<-YAML
+ mysql: &mysql
+ adapter: mysql
+ pool: 30
+ login: &login
+ username: user
+ password: password123
+ development: &development
+ <<: *mysql
+ <<: *login
+ host: localhost
+ YAML
+
+ result.should == {
+ "mysql" => {
+ "adapter" => "mysql",
+ "pool" => 30
+ },
+ "login" => {
+ "username" => "user",
+ "password" => "password123"
+ },
+ "development" => {
+ "adapter" => "mysql",
+ "pool" => 30,
+ "username" => "user",
+ "password" => "password123",
+ "host" => "localhost"
+ }
+ }
+ end
+
+ it "correctly prefers explicitly defined values over default values from included sections" do
+ # Repeating this test 100 times to increase the likelihood of running into an issue caused by
+ # non-deterministic hash key enumeration.
+ 100.times do
+ result = YAML.safe_load <<-YAML
+ defaults: &defaults
+ foo: foo
+ bar: bar
+ baz: baz
+ custom:
+ <<: *defaults
+ bar: custom_bar
+ baz: custom_baz
+ YAML
+
+ result["custom"].should == {
+ "foo" => "foo",
+ "bar" => "custom_bar",
+ "baz" => "custom_baz"
+ }
+ end
+ end
+
+ it "works with multi-level inheritance" do
+ result = YAML.safe_load <<-YAML
+ defaults: &defaults
+ foo: foo
+ bar: bar
+ baz: baz
+ custom: &custom
+ <<: *defaults
+ bar: custom_bar
+ baz: custom_baz
+ grandcustom: &grandcustom
+ <<: *custom
+ YAML
+
+ result.should == {
+ "defaults" => { "foo" => "foo", "bar" => "bar", "baz" => "baz" },
+ "custom" => { "foo" => "foo", "bar" => "custom_bar", "baz" => "custom_baz" },
+ "grandcustom" => { "foo" => "foo", "bar" => "custom_bar", "baz" => "custom_baz" }
+ }
+ end
+
+ it "returns false when parsing an empty document" do
+ result = YAML.safe_load ""
+ result.should == false
+ end
+
+ context "with custom initializers defined" do
+ before :each do
+ if SafeYAML::YAML_ENGINE == "psych"
+ SafeYAML::OPTIONS[:custom_initializers] = {
+ "!set" => lambda { Set.new },
+ "!hashiemash" => lambda { Hashie::Mash.new }
+ }
+ else
+ SafeYAML::OPTIONS[:custom_initializers] = {
+ "tag:yaml.org,2002:set" => lambda { Set.new },
+ "tag:yaml.org,2002:hashiemash" => lambda { Hashie::Mash.new }
+ }
+ end
+ end
+
+ it "will use a custom initializer to instantiate an array-like class upon deserialization" do
+ result = YAML.safe_load <<-YAML.unindent
+ --- !set
+ - 1
+ - 2
+ - 3
+ YAML
+
+ result.should be_a(Set)
+ result.to_a.should =~ [1, 2, 3]
+ end
+
+ it "will use a custom initializer to instantiate a hash-like class upon deserialization" do
+ result = YAML.safe_load <<-YAML.unindent
+ --- !hashiemash
+ foo: bar
+ YAML
+
+ result.should be_a(Hashie::Mash)
+ result.to_hash.should == { "foo" => "bar" }
+ end
+ end
+
+ context "with special whitelisted tags defined" do
+ before :each do
+ SafeYAML::whitelist!(OpenStruct)
+
+ # Necessary for deserializing OpenStructs properly.
+ SafeYAML::OPTIONS[:deserialize_symbols] = true
+ end
+
+ it "will allow objects to be deserialized for whitelisted tags" do
+ result = YAML.safe_load("--- !ruby/object:OpenStruct\ntable:\n foo: bar\n")
+ result.should be_a(OpenStruct)
+ result.instance_variable_get(:@table).should == { "foo" => "bar" }
+ end
+
+ it "will not deserialize objects without whitelisted tags" do
+ result = YAML.safe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
+ result.should_not be_a(ExploitableBackDoor)
+ result.should == { "foo" => "bar" }
+ end
+
+ it "will not allow non-whitelisted objects to be embedded within objects with whitelisted tags" do
+ result = YAML.safe_load <<-YAML.unindent
+ --- !ruby/object:OpenStruct
+ table:
+ :backdoor: !ruby/object:ExploitableBackDoor
+ foo: bar
+ YAML
+
+ result.should be_a(OpenStruct)
+ result.backdoor.should_not be_a(ExploitableBackDoor)
+ result.backdoor.should == { "foo" => "bar" }
+ end
+
+ context "with the :raise_on_unknown_tag option enabled" do
+ before :each do
+ SafeYAML::OPTIONS[:raise_on_unknown_tag] = true
+ end
+
+ after :each do
+ SafeYAML.restore_defaults!
+ end
+
+ it "raises an exception if a non-nil, non-whitelisted tag is encountered" do
+ lambda {
+ YAML.safe_load <<-YAML.unindent
+ --- !ruby/object:Unknown
+ foo: bar
+ YAML
+ }.should raise_error
+ end
+
+ it "checks all tags, even those within objects with trusted tags" do
+ lambda {
+ YAML.safe_load <<-YAML.unindent
+ --- !ruby/object:OpenStruct
+ table:
+ :backdoor: !ruby/object:Unknown
+ foo: bar
+ YAML
+ }.should raise_error
+ end
+
+ it "does not raise an exception as long as all tags are whitelisted" do
+ result = YAML.safe_load <<-YAML.unindent
+ --- !ruby/object:OpenStruct
+ table:
+ :backdoor:
+ string: foo
+ integer: 1
+ float: 3.14
+ symbol: :bar
+ date: 2013-02-20
+ array: []
+ hash: {}
+ YAML
+
+ result.should be_a(OpenStruct)
+ result.backdoor.should == {
+ "string" => "foo",
+ "integer" => 1,
+ "float" => 3.14,
+ "symbol" => :bar,
+ "date" => Date.parse("2013-02-20"),
+ "array" => [],
+ "hash" => {}
+ }
+ end
+
+ it "does not raise an exception on the non-specific '!' tag" do
+ result = nil
+ expect { result = YAML.safe_load "--- ! 'foo'" }.to_not raise_error
+ result.should == "foo"
+ end
+
+ context "with whitelisted custom class" do
+ class SomeClass
+ attr_accessor :foo
+ end
+ let(:instance) { SomeClass.new }
+
+ before do
+ SafeYAML::whitelist!(SomeClass)
+ instance.foo = 'with trailing whitespace: '
+ end
+
+ it "does not raise an exception on the non-specific '!' tag" do
+ result = nil
+ expect { result = YAML.safe_load(instance.to_yaml) }.to_not raise_error
+ result.foo.should == 'with trailing whitespace: '
+ end
+ end
+ end
+ end
+
+ context "when options are passed direclty to #load which differ from the defaults" do
+ let(:default_options) { {} }
+
+ before :each do
+ SafeYAML::OPTIONS.merge!(default_options)
+ end
+
+ context "(for example, when symbol deserialization is enabled by default)" do
+ let(:default_options) { { :deserialize_symbols => true } }
+
+ it "goes with the default option when it is not overridden" do
+ silence_warnings do
+ YAML.load(":foo: bar").should == { :foo => "bar" }
+ end
+ end
+
+ it "allows the default option to be overridden on a per-call basis" do
+ silence_warnings do
+ YAML.load(":foo: bar", :deserialize_symbols => false).should == { ":foo" => "bar" }
+ YAML.load(":foo: bar", :deserialize_symbols => true).should == { :foo => "bar" }
+ end
+ end
+ end
+
+ context "(or, for example, when certain tags are whitelisted)" do
+ let(:default_options) {
+ {
+ :deserialize_symbols => true,
+ :whitelisted_tags => SafeYAML::YAML_ENGINE == "psych" ?
+ ["!ruby/object:OpenStruct"] :
+ ["tag:ruby.yaml.org,2002:object:OpenStruct"]
+ }
+ }
+
+ it "goes with the default option when it is not overridden" do
+ result = safe_load_round_trip(OpenStruct.new(:foo => "bar"))
+ result.should be_a(OpenStruct)
+ result.foo.should == "bar"
+ end
+
+ it "allows the default option to be overridden on a per-call basis" do
+ result = safe_load_round_trip(OpenStruct.new(:foo => "bar"), :whitelisted_tags => [])
+ result.should == { "table" => { :foo => "bar" } }
+
+ result = safe_load_round_trip(OpenStruct.new(:foo => "bar"), :deserialize_symbols => false, :whitelisted_tags => [])
+ result.should == { "table" => { ":foo" => "bar" } }
+ end
+ end
+ end
+ end
+
+ describe "unsafe_load_file" do
+ if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
+ it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
+ backdoor = YAML.unsafe_load_file "spec/exploit.1.9.3.yaml"
+ backdoor.should be_exploited_through_setter
+ end
+ end
+
+ if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.2"
+ it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
+ backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
+ backdoor.should be_exploited_through_init_with
+ end
+ end
+
+ it "allows exploits through objects w/ sensitive instance variables defined in YAML w/ !ruby/object" do
+ backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
+ backdoor.should be_exploited_through_ivars
+ end
+ end
+
+ describe "safe_load_file" do
+ it "does NOT allow exploits through objects defined in YAML w/ !ruby/hash" do
+ object = YAML.safe_load_file "spec/exploit.1.9.3.yaml"
+ object.should_not be_a(ExploitableBackDoor)
+ end
+
+ it "does NOT allow exploits through objects defined in YAML w/ !ruby/object" do
+ object = YAML.safe_load_file "spec/exploit.1.9.2.yaml"
+ object.should_not be_a(ExploitableBackDoor)
+ end
+ end
+
+ describe "load" do
+ let(:options) { {} }
+
+ let (:arguments) {
+ if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
+ ["foo: bar", nil, options]
+ else
+ ["foo: bar", options]
+ end
+ }
+
+ context "as long as a :default_mode has been specified" do
+ it "doesn't issue a warning for safe mode, since an explicit mode has been set" do
+ SafeYAML::OPTIONS[:default_mode] = :safe
+ Kernel.should_not_receive(:warn)
+ YAML.load(*arguments)
+ end
+
+ it "doesn't issue a warning for unsafe mode, since an explicit mode has been set" do
+ SafeYAML::OPTIONS[:default_mode] = :unsafe
+ Kernel.should_not_receive(:warn)
+ YAML.load(*arguments)
+ end
+ end
+
+ context "when the :safe options is specified" do
+ let(:safe_mode) { true }
+ let(:options) { { :safe => safe_mode } }
+
+ it "doesn't issue a warning" do
+ Kernel.should_not_receive(:warn)
+ YAML.load(*arguments)
+ end
+
+ it "calls #safe_load if the :safe option is set to true" do
+ YAML.should_receive(:safe_load)
+ YAML.load(*arguments)
+ end
+
+ context "when the :safe option is set to false" do
+ let(:safe_mode) { false }
+
+ it "calls #unsafe_load if the :safe option is set to false" do
+ YAML.should_receive(:unsafe_load)
+ YAML.load(*arguments)
+ end
+ end
+ end
+
+ it "issues a warning when the :safe option is omitted" do
+ silence_warnings do
+ Kernel.should_receive(:warn)
+ YAML.load(*arguments)
+ end
+ end
+
+ it "only issues a warning once (to avoid spamming an app's output)" do
+ silence_warnings do
+ Kernel.should_receive(:warn).once
+ 2.times { YAML.load(*arguments) }
+ end
+ end
+
+ it "defaults to safe mode if the :safe option is omitted" do
+ silence_warnings do
+ YAML.should_receive(:safe_load)
+ YAML.load(*arguments)
+ end
+ end
+
+ context "with the default mode set to :unsafe" do
+ before :each do
+ SafeYAML::OPTIONS[:default_mode] = :unsafe
+ end
+
+ it "defaults to unsafe mode if the :safe option is omitted" do
+ silence_warnings do
+ YAML.should_receive(:unsafe_load)
+ YAML.load(*arguments)
+ end
+ end
+
+ it "calls #safe_load if the :safe option is set to true" do
+ YAML.should_receive(:safe_load)
+ YAML.load(*(arguments + [{ :safe => true }]))
+ end
+ end
+ end
+
+ describe "load_file" do
+ let(:filename) { "spec/exploit.1.9.2.yaml" } # doesn't really matter
+
+ it "issues a warning if the :safe option is omitted" do
+ silence_warnings do
+ Kernel.should_receive(:warn)
+ YAML.load_file(filename)
+ end
+ end
+
+ it "doesn't issue a warning as long as the :safe option is specified" do
+ Kernel.should_not_receive(:warn)
+ YAML.load_file(filename, :safe => true)
+ end
+
+ it "defaults to safe mode if the :safe option is omitted" do
+ silence_warnings do
+ YAML.should_receive(:safe_load_file)
+ YAML.load_file(filename)
+ end
+ end
+
+ it "calls #safe_load_file if the :safe option is set to true" do
+ YAML.should_receive(:safe_load_file)
+ YAML.load_file(filename, :safe => true)
+ end
+
+ it "calls #unsafe_load_file if the :safe option is set to false" do
+ YAML.should_receive(:unsafe_load_file)
+ YAML.load_file(filename, :safe => false)
+ end
+
+ context "with arbitrary object deserialization enabled by default" do
+ before :each do
+ SafeYAML::OPTIONS[:default_mode] = :unsafe
+ end
+
+ it "defaults to unsafe mode if the :safe option is omitted" do
+ silence_warnings do
+ YAML.should_receive(:unsafe_load_file)
+ YAML.load_file(filename)
+ end
+ end
+
+ it "calls #safe_load if the :safe option is set to true" do
+ YAML.should_receive(:safe_load_file)
+ YAML.load_file(filename, :safe => true)
+ end
+ end
+ end
+
+ describe "whitelist!" do
+ context "not a class" do
+ it "should raise" do
+ expect { SafeYAML::whitelist! :foo }.to raise_error(/not a Class/)
+ SafeYAML::OPTIONS[:whitelisted_tags].should be_empty
+ end
+ end
+
+ context "anonymous class" do
+ it "should raise" do
+ expect { SafeYAML::whitelist! Class.new }.to raise_error(/cannot be anonymous/)
+ SafeYAML::OPTIONS[:whitelisted_tags].should be_empty
+ end
+ end
+
+ context "with a Class as its argument" do
+ it "should configure correctly" do
+ expect { SafeYAML::whitelist! OpenStruct }.to_not raise_error
+ SafeYAML::OPTIONS[:whitelisted_tags].grep(/OpenStruct\Z/).should_not be_empty
+ end
+
+ it "successfully deserializes the specified class" do
+ SafeYAML.whitelist!(OpenStruct)
+
+ # necessary for properly assigning OpenStruct attributes
+ SafeYAML::OPTIONS[:deserialize_symbols] = true
+
+ result = safe_load_round_trip(OpenStruct.new(:foo => "bar"))
+ result.should be_a(OpenStruct)
+ result.foo.should == "bar"
+ end
+
+ it "works for ranges" do
+ SafeYAML.whitelist!(Range)
+ safe_load_round_trip(1..10).should == (1..10)
+ end
+
+ it "works for regular expressions" do
+ SafeYAML.whitelist!(Regexp)
+ safe_load_round_trip(/foo/).should == /foo/
+ end
+
+ it "works for multiple classes" do
+ SafeYAML.whitelist!(Range, Regexp)
+ safe_load_round_trip([(1..10), /bar/]).should == [(1..10), /bar/]
+ end
+
+ it "works for arbitrary Exception subclasses" do
+ class CustomException < Exception
+ attr_reader :custom_message
+
+ def initialize(custom_message)
+ @custom_message = custom_message
+ end
+ end
+
+ SafeYAML.whitelist!(CustomException)
+
+ ex = safe_load_round_trip(CustomException.new("blah"))
+ ex.should be_a(CustomException)
+ ex.custom_message.should == "blah"
+ end
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/spec/spec_helper.rb b/lib/puppet/vendor/safe_yaml/spec/spec_helper.rb
new file mode 100644
index 0000000..17c849d
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/spec/spec_helper.rb
@@ -0,0 +1,18 @@
+HERE = File.dirname(__FILE__) unless defined?(HERE)
+ROOT = File.join(HERE, "..") unless defined?(ROOT)
+
+$LOAD_PATH << File.join(ROOT, "lib")
+$LOAD_PATH << File.join(HERE, "support")
+
+require "yaml"
+if ENV["YAMLER"] && defined?(YAML::ENGINE)
+ YAML::ENGINE.yamler = ENV["YAMLER"]
+ puts "Running specs in Ruby #{RUBY_VERSION} with '#{YAML::ENGINE.yamler}' YAML engine."
+end
+
+require "safe_yaml"
+require "ostruct"
+require "hashie"
+require "heredoc_unindent"
+
+require File.join(HERE, "resolver_specs")
diff --git a/lib/puppet/vendor/safe_yaml/spec/support/exploitable_back_door.rb b/lib/puppet/vendor/safe_yaml/spec/support/exploitable_back_door.rb
new file mode 100644
index 0000000..48754b4
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/spec/support/exploitable_back_door.rb
@@ -0,0 +1,29 @@
+class ExploitableBackDoor
+ def exploited?
+ @exploited_through_setter || @exploited_through_init_with || @exploited_through_ivars
+ end
+
+ def exploited_through_setter?
+ @exploited_through_setter
+ end
+
+ def exploited_through_init_with?
+ @exploited_through_init_with
+ end
+
+ def exploited_through_ivars?
+ self.instance_variables.any?
+ end
+
+ def init_with(command)
+ # Note: this is how bad this COULD be.
+ # system("#{command}")
+ @exploited_through_init_with = true
+ end
+
+ def []=(command, arguments)
+ # Note: this is how bad this COULD be.
+ # system("#{command} #{arguments}")
+ @exploited_through_setter = true
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/spec/syck_resolver_spec.rb b/lib/puppet/vendor/safe_yaml/spec/syck_resolver_spec.rb
new file mode 100644
index 0000000..a9c5421
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/spec/syck_resolver_spec.rb
@@ -0,0 +1,10 @@
+require File.join(File.dirname(__FILE__), "spec_helper")
+
+if SafeYAML::YAML_ENGINE == "syck"
+ require "safe_yaml/syck_resolver"
+
+ describe SafeYAML::SyckResolver do
+ include ResolverSpecs
+ let(:resolver) { SafeYAML::SyckResolver.new }
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/spec/transform/base64_spec.rb b/lib/puppet/vendor/safe_yaml/spec/transform/base64_spec.rb
new file mode 100644
index 0000000..da872fb
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/spec/transform/base64_spec.rb
@@ -0,0 +1,11 @@
+require File.join(File.dirname(__FILE__), "..", "spec_helper")
+
+describe SafeYAML::Transform do
+ it "should return the same encoding when decoding Base64" do
+ value = "c3VyZS4="
+ decoded = SafeYAML::Transform.to_proper_type(value, false, "!binary")
+
+ decoded.should == "sure."
+ decoded.encoding.should == value.encoding if decoded.respond_to?(:encoding)
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/spec/transform/to_date_spec.rb b/lib/puppet/vendor/safe_yaml/spec/transform/to_date_spec.rb
new file mode 100644
index 0000000..4c5429e
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/spec/transform/to_date_spec.rb
@@ -0,0 +1,34 @@
+require File.join(File.dirname(__FILE__), "..", "spec_helper")
+
+describe SafeYAML::Transform::ToDate do
+ it "returns true when the value matches a valid Date" do
+ subject.transform?("2013-01-01").should == [true, Date.parse("2013-01-01")]
+ end
+
+ it "returns false when the value does not match a valid Date" do
+ subject.transform?("foobar").should be_false
+ end
+
+ it "returns false when the value does not end with a Date" do
+ subject.transform?("2013-01-01\nNOT A DATE").should be_false
+ end
+
+ it "returns false when the value does not begin with a Date" do
+ subject.transform?("NOT A DATE\n2013-01-01").should be_false
+ end
+
+ it "correctly parses the remaining formats of the YAML spec" do
+ equivalent_values = [
+ "2001-12-15T02:59:43.1Z", # canonical
+ "2001-12-14t21:59:43.10-05:00", # iso8601
+ "2001-12-14 21:59:43.10 -5", # space separated
+ "2001-12-15 2:59:43.10" # no time zone (Z)
+ ]
+
+ equivalent_values.each do |value|
+ success, result = subject.transform?(value)
+ success.should be_true
+ result.should == Time.utc(2001, 12, 15, 2, 59, 43, 100000)
+ end
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/spec/transform/to_float_spec.rb b/lib/puppet/vendor/safe_yaml/spec/transform/to_float_spec.rb
new file mode 100644
index 0000000..9f56685
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/spec/transform/to_float_spec.rb
@@ -0,0 +1,42 @@
+require File.join(File.dirname(__FILE__), "..", "spec_helper")
+
+describe SafeYAML::Transform::ToFloat do
+ it "returns true when the value matches a valid Float" do
+ subject.transform?("20.00").should == [true, 20.0]
+ end
+
+ it "returns false when the value does not match a valid Float" do
+ subject.transform?("foobar").should be_false
+ end
+
+ it "returns false when the value spans multiple lines" do
+ subject.transform?("20.00\nNOT A FLOAT").should be_false
+ end
+
+ it "correctly parses all formats in the YAML spec" do
+ # canonical
+ subject.transform?("6.8523015e+5").should == [true, 685230.15]
+
+ # exponentioal
+ subject.transform?("685.230_15e+03").should == [true, 685230.15]
+
+ # fixed
+ subject.transform?("685_230.15").should == [true, 685230.15]
+
+ # sexagesimal
+ subject.transform?("190:20:30.15").should == [true, 685230.15]
+
+ # infinity
+ subject.transform?("-.inf").should == [true, (-1.0 / 0.0)]
+
+ # not a number
+ # NOTE: can't use == here since NaN != NaN
+ success, result = subject.transform?(".NaN")
+ success.should be_true; result.should be_nan
+ end
+
+ # issue 29
+ it "returns false for the string '.'" do
+ subject.transform?(".").should be_false
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/spec/transform/to_integer_spec.rb b/lib/puppet/vendor/safe_yaml/spec/transform/to_integer_spec.rb
new file mode 100644
index 0000000..8423417
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/spec/transform/to_integer_spec.rb
@@ -0,0 +1,59 @@
+require File.join(File.dirname(__FILE__), "..", "spec_helper")
+
+describe SafeYAML::Transform::ToInteger do
+ it "returns true when the value matches a valid Integer" do
+ subject.transform?("10").should == [true, 10]
+ end
+
+ it "returns false when the value does not match a valid Integer" do
+ subject.transform?("foobar").should be_false
+ end
+
+ it "returns false when the value spans multiple lines" do
+ subject.transform?("10\nNOT AN INTEGER").should be_false
+ end
+
+ it "allows commas in the number" do
+ subject.transform?("1,000").should == [true, 1000]
+ end
+
+ it "correctly parses numbers in octal format" do
+ subject.transform?("010").should == [true, 8]
+ end
+
+ it "correctly parses numbers in hexadecimal format" do
+ subject.transform?("0x1FF").should == [true, 511]
+ end
+
+ it "defaults to a string for a number that resembles octal format but is not" do
+ subject.transform?("09").should be_false
+ end
+
+ it "correctly parses 0 in decimal" do
+ subject.transform?("0").should == [true, 0]
+ end
+
+ it "defaults to a string for a number that resembles hexadecimal format but is not" do
+ subject.transform?("0x1G").should be_false
+ end
+
+ it "correctly parses all formats in the YAML spec" do
+ # canonical
+ subject.transform?("685230").should == [true, 685230]
+
+ # decimal
+ subject.transform?("+685_230").should == [true, 685230]
+
+ # octal
+ subject.transform?("02472256").should == [true, 685230]
+
+ # hexadecimal:
+ subject.transform?("0x_0A_74_AE").should == [true, 685230]
+
+ # binary
+ subject.transform?("0b1010_0111_0100_1010_1110").should == [true, 685230]
+
+ # sexagesimal
+ subject.transform?("190:20:30").should == [true, 685230]
+ end
+end
diff --git a/lib/puppet/vendor/safe_yaml/spec/transform/to_symbol_spec.rb b/lib/puppet/vendor/safe_yaml/spec/transform/to_symbol_spec.rb
new file mode 100644
index 0000000..aaa3339
--- /dev/null
+++ b/lib/puppet/vendor/safe_yaml/spec/transform/to_symbol_spec.rb
@@ -0,0 +1,49 @@
+require File.join(File.dirname(__FILE__), "..", "spec_helper")
+
+describe SafeYAML::Transform::ToSymbol do
+ def with_symbol_deserialization_value(value)
+ symbol_deserialization_flag = SafeYAML::OPTIONS[:deserialize_symbols]
+ SafeYAML::OPTIONS[:deserialize_symbols] = value
+
+ yield
+
+ ensure
+ SafeYAML::OPTIONS[:deserialize_symbols] = symbol_deserialization_flag
+ end
+
+ def with_symbol_deserialization(&block)
+ with_symbol_deserialization_value(true, &block)
+ end
+
+ def without_symbol_deserialization(&block)
+ with_symbol_deserialization_value(false, &block)
+ end
+
+ it "returns true when the value matches a valid Symbol" do
+ with_symbol_deserialization { subject.transform?(":foo")[0].should be_true }
+ end
+
+ it "returns true when the value matches a valid String+Symbol" do
+ with_symbol_deserialization { subject.transform?(':"foo"')[0].should be_true }
+ end
+
+ it "returns false when symbol deserialization is disabled" do
+ without_symbol_deserialization { subject.transform?(":foo").should be_false }
+ end
+
+ it "returns false when the value does not match a valid Symbol" do
+ with_symbol_deserialization { subject.transform?("foo").should be_false }
+ end
+
+ it "returns false when the symbol does not begin the line" do
+ with_symbol_deserialization do
+ subject.transform?("NOT A SYMBOL\n:foo").should be_false
+ end
+ end
+
+ it "returns false when the symbol does not end the line" do
+ with_symbol_deserialization do
+ subject.transform?(":foo\nNOT A SYMBOL").should be_false
+ end
+ end
+end
diff --git a/spec/integration/indirector/report/rest_spec.rb b/spec/integration/indirector/report/rest_spec.rb
index d78608a..624bdbe 100644
--- a/spec/integration/indirector/report/rest_spec.rb
+++ b/spec/integration/indirector/report/rest_spec.rb
@@ -62,7 +62,7 @@ describe "Report REST Terminus" do
end
it "should be able to send a report to the server" do
- @report.expects(:save)
+ @report.expects(:save).returns []
report = Puppet::Transaction::Report.new("apply")
diff --git a/spec/unit/file_serving/metadata_spec.rb b/spec/unit/file_serving/metadata_spec.rb
index 728e6cd..de07ede 100755
--- a/spec/unit/file_serving/metadata_spec.rb
+++ b/spec/unit/file_serving/metadata_spec.rb
@@ -33,13 +33,6 @@ describe Puppet::FileServing::Metadata do
before do
@metadata = Puppet::FileServing::Metadata.new("/foo/bar")
end
- it "should perform pson serialization by calling to_pson on it's pson_data_hash" do
- pdh = mock "data hash"
- pdh_as_pson = mock "data as pson"
- @metadata.expects(:to_pson_data_hash).returns pdh
- pdh.expects(:to_pson).returns pdh_as_pson
- @metadata.to_pson.should == pdh_as_pson
- end
it "should serialize as FileMetadata" do
@metadata.to_pson_data_hash['document_type'].should == "FileMetadata"
diff --git a/spec/unit/indirector/report/rest_spec.rb b/spec/unit/indirector/report/rest_spec.rb
index d0d29f8..d8d42e0 100755
--- a/spec/unit/indirector/report/rest_spec.rb
+++ b/spec/unit/indirector/report/rest_spec.rb
@@ -25,4 +25,45 @@ describe Puppet::Transaction::Report::Rest do
Puppet::Transaction::Report::Rest.server.should_not be_nil
Puppet::Transaction::Report::Rest.port.should_not be_nil
end
+
+ let(:model) { Puppet::Transaction::Report }
+ let(:terminus_class) { Puppet::Transaction::Report::Rest }
+ let(:terminus) { model.indirection.terminus(:rest) }
+ let(:indirection) { model.indirection }
+
+ before(:each) do
+ Puppet::Transaction::Report.indirection.terminus_class = :rest
+ end
+
+ def mock_response(code, body, content_type='text/plain', encoding=nil)
+ obj = stub('http 200 ok', :code => code.to_s, :body => body)
+ obj.stubs(:[]).with('content-type').returns(content_type)
+ obj.stubs(:[]).with('content-encoding').returns(encoding)
+ obj
+ end
+
+ def save_request(key, instance)
+ Puppet::Indirector::Request.new(:report, :find, key, instance)
+ end
+
+ describe "#save" do
+ let(:http_method) { :put }
+ let(:response) { mock_response(200, 'body') }
+ let(:connection) { stub('mock http connection', :put => response, :verify_callback= => nil) }
+ let(:instance) { model.new('the thing', 'some contents') }
+ let(:request) { save_request(instance.name, instance) }
+
+ before :each do
+ terminus.stubs(:network).returns(connection)
+ end
+
+ it "deserializes the response as an array of report processor names" do
+ processors = ["store", "http"]
+ body = YAML.dump(processors)
+ response = mock_response('200', body, 'text/yaml')
+ connection.expects(:put).returns response
+
+ terminus.save(request).should == ["store", "http"]
+ end
+ end
end
diff --git a/spec/unit/indirector/rest_spec.rb b/spec/unit/indirector/rest_spec.rb
index 7bc1cc5..1c38e05 100755
--- a/spec/unit/indirector/rest_spec.rb
+++ b/spec/unit/indirector/rest_spec.rb
@@ -1,185 +1,176 @@
-#!/usr/bin/env ruby
-
-require File.dirname(__FILE__) + '/../../spec_helper'
+#!/usr/bin/env rspec
+require 'spec_helper'
+require 'puppet/indirector'
+require 'puppet/indirector/errors'
require 'puppet/indirector/rest'
-shared_examples_for "a REST http call" do
- it "should accept a path" do
- lambda { @search.send(@method, *@arguments) }.should_not raise_error(ArgumentError)
- end
-
- it "should require a path" do
- lambda { @searcher.send(@method) }.should raise_error(ArgumentError)
- end
+# Just one from each category since the code makes no real distinctions
+HTTP_ERROR_CODES = [300, 400, 500]
+shared_examples_for "a REST terminus method" do |terminus_method|
+ HTTP_ERROR_CODES.each do |code|
+ describe "when the response code is #{code}" do
+ let(:response) { mock_response(code, 'error messaged!!!') }
+
+ it "raises an http error with the body of the response" do
+ expect {
+ terminus.send(terminus_method, request)
+ }.to raise_error(Net::HTTPError, "Error #{code} on SERVER: #{response.body}")
+ end
- it "should return the results of deserializing the response to the request" do
- conn = mock 'connection'
- conn.stubs(:put).returns @response
- conn.stubs(:delete).returns @response
- conn.stubs(:get).returns @response
- Puppet::Network::HttpPool.stubs(:http_instance).returns conn
+ it "does not attempt to deserialize the response" do
+ model.expects(:convert_from).never
- @searcher.expects(:deserialize).with(@response).returns "myobject"
+ expect {
+ terminus.send(terminus_method, request)
+ }.to raise_error(Net::HTTPError)
+ end
- @searcher.send(@method, *@arguments).should == 'myobject'
- end
-end
+ # I'm not sure what this means or if it's used
+ it "if the body is empty raises an http error with the response header" do
+ response.stubs(:body).returns ""
+ response.stubs(:message).returns "fhqwhgads"
-describe Puppet::Indirector::REST do
- before do
- Puppet::Indirector::Terminus.stubs(:register_terminus_class)
- @model = stub('model', :supported_formats => %w{}, :convert_from => nil)
- @instance = stub('model instance', :name= => nil)
- @indirection = stub('indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model)
- Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection)
-
- @rest_class = Class.new(Puppet::Indirector::REST) do
- def self.to_s
- "This::Is::A::Test::Class"
+ expect {
+ terminus.send(terminus_method, request)
+ }.to raise_error(Net::HTTPError, "Error #{code} on SERVER: #{response.message}")
end
- end
- @response = stub('mock response', :body => 'result', :code => "200")
- @response.stubs(:[]).with('content-type').returns "text/plain"
- @response.stubs(:[]).with('content-encoding').returns nil
-
- @searcher = @rest_class.new
- @searcher.stubs(:model).returns @model
- end
+ describe "and the body is compressed" do
+ it "raises an http error with the decompressed body of the response" do
+ uncompressed_body = "why"
+ compressed_body = Zlib::Deflate.deflate(uncompressed_body)
- it "should include the v1 REST API module" do
- Puppet::Indirector::REST.ancestors.should be_include(Puppet::Network::HTTP::API::V1)
- end
+ response = mock_response(code, compressed_body, 'text/plain', 'deflate')
+ connection.expects(http_method).returns(response)
- it "should have a method for specifying what setting a subclass should use to retrieve its server" do
- @rest_class.should respond_to(:use_server_setting)
+ expect {
+ terminus.send(terminus_method, request)
+ }.to raise_error(Net::HTTPError, "Error #{code} on SERVER: #{uncompressed_body}")
+ end
+ end
+ end
end
+end
- it "should use any specified setting to pick the server" do
- @rest_class.expects(:server_setting).returns :servset
- Puppet.settings.expects(:value).with(:servset).returns "myserver"
- @rest_class.server.should == "myserver"
+shared_examples_for "a deserializing terminus method" do |terminus_method|
+ describe "when the response has no content-type" do
+ let(:response) { mock_response(200, "body", nil, nil) }
+ it "raises an error" do
+ expect {
+ terminus.send(terminus_method, request)
+ }.to raise_error(RuntimeError, "No content type in http response; cannot parse")
+ end
end
- it "should default to :server for the server setting" do
- @rest_class.expects(:server_setting).returns nil
- Puppet.settings.expects(:value).with(:server).returns "myserver"
- @rest_class.server.should == "myserver"
- end
+ it "doesn't catch errors in deserialization" do
+ model.expects(:convert_from).raises(Puppet::Error, "Whoa there")
- it "should have a method for specifying what setting a subclass should use to retrieve its port" do
- @rest_class.should respond_to(:use_port_setting)
+ expect { terminus.send(terminus_method, request) }.to raise_error(Puppet::Error, "Whoa there")
end
+end
- it "should use any specified setting to pick the port" do
- @rest_class.expects(:port_setting).returns :servset
- Puppet.settings.expects(:value).with(:servset).returns "321"
- @rest_class.port.should == 321
- end
+describe Puppet::Indirector::REST do
+ before :all do
+ class Puppet::TestModel
+ extend Puppet::Indirector
+ indirects :test_model
+ attr_accessor :name, :data
+ def initialize(name = "name", data = '')
+ @name = name
+ @data = data
+ end
- it "should default to :port for the port setting" do
- @rest_class.expects(:port_setting).returns nil
- Puppet.settings.expects(:value).with(:masterport).returns "543"
- @rest_class.port.should == 543
- end
+ def self.convert_from(format, string)
+ new('', string)
+ end
- describe "when deserializing responses" do
- it "should return nil if the response code is 404" do
- response = mock 'response'
- response.expects(:code).returns "404"
+ def self.convert_from_multiple(format, string)
+ string.split(',').collect { |s| convert_from(format, s) }
+ end
- @searcher.deserialize(response).should be_nil
+ def ==(other)
+ other.is_a? Puppet::TestModel and other.name == name and other.data == data
+ end
end
- [300,400,403,405,500,501,502,503,504].each { |rc|
- describe "when the response code is #{rc}" do
- before :each do
- @model.expects(:convert_from).never
-
- @response = mock 'response'
- @response.stubs(:code).returns rc.to_s
- @response.stubs(:[]).with('content-encoding').returns nil
- @response.stubs(:message).returns "There was a problem (header)"
- end
-
- it "should fail" do
- @response.stubs(:body).returns nil
- lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError)
- end
-
- it "should take the error message from the body, if present" do
- @response.stubs(:body).returns "There was a problem (body)"
- lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (body)")
- end
+ # The subclass must not be all caps even though the superclass is
+ class Puppet::TestModel::Rest < Puppet::Indirector::REST
+ end
- it "should take the error message from the response header if the body is empty" do
- @response.stubs(:body).returns ""
- lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (header)")
- end
+ Puppet::TestModel.indirection.terminus_class = :rest
+ end
- it "should take the error message from the response header if the body is absent" do
- @response.stubs(:body).returns nil
- lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (header)")
- end
+ after :all do
+ # Remove the class, unlinking it from the rest of the system.
+ Puppet.send(:remove_const, :TestModel)
+ end
- describe "and with http compression" do
- it "should uncompress the body" do
- @response.stubs(:body).returns("compressed body")
- @searcher.expects(:uncompress_body).with(@response).returns("uncompressed")
- lambda { @searcher.deserialize(@response) }.should raise_error { |e| e.message =~ /uncompressed/ }
- end
- end
- end
- }
+ let(:terminus_class) { Puppet::TestModel::Rest }
+ let(:terminus) { Puppet::TestModel.indirection.terminus(:rest) }
+ let(:indirection) { Puppet::TestModel.indirection }
+ let(:model) { Puppet::TestModel }
- it "should return the results of converting from the format specified by the content-type header if the response code is in the 200s" do
- @model.expects(:convert_from).with("myformat", "mydata").returns "myobject"
+ def mock_response(code, body, content_type='text/plain', encoding=nil)
+ obj = stub('http 200 ok', :code => code.to_s, :body => body)
+ obj.stubs(:[]).with('content-type').returns(content_type)
+ obj.stubs(:[]).with('content-encoding').returns(encoding)
+ obj
+ end
- response = mock 'response'
- response.stubs(:[]).with("content-type").returns "myformat"
- response.stubs(:[]).with("content-encoding").returns nil
- response.stubs(:body).returns "mydata"
- response.stubs(:code).returns "200"
+ def find_request(key, options={})
+ Puppet::Indirector::Request.new(:test_model, :find, key, options)
+ end
- @searcher.deserialize(response).should == "myobject"
- end
+ def head_request(key, options={})
+ Puppet::Indirector::Request.new(:test_model, :head, key, options)
+ end
- it "should convert and return multiple instances if the return code is in the 200s and 'multiple' is specified" do
- @model.expects(:convert_from_multiple).with("myformat", "mydata").returns "myobjects"
+ def search_request(key, options={})
+ Puppet::Indirector::Request.new(:test_model, :search, key, options)
+ end
- response = mock 'response'
- response.stubs(:[]).with("content-type").returns "myformat"
- response.stubs(:[]).with("content-encoding").returns nil
- response.stubs(:body).returns "mydata"
- response.stubs(:code).returns "200"
+ def delete_request(key, options={})
+ Puppet::Indirector::Request.new(:test_model, :destroy, key, options)
+ end
- @searcher.deserialize(response, true).should == "myobjects"
- end
+ def save_request(key, instance)
+ Puppet::Indirector::Request.new(:test_model, :save, key, instance)
+ end
- it "should strip the content-type header to keep only the mime-type" do
- @model.expects(:convert_from).with("text/plain", "mydata").returns "myobject"
+ it "should include the v1 REST API module" do
+ Puppet::Indirector::REST.ancestors.should be_include(Puppet::Network::HTTP::API::V1)
+ end
- response = mock 'response'
- response.stubs(:[]).with("content-type").returns "text/plain; charset=utf-8"
- response.stubs(:[]).with("content-encoding").returns nil
- response.stubs(:body).returns "mydata"
- response.stubs(:code).returns "200"
+ it "should have a method for specifying what setting a subclass should use to retrieve its server" do
+ terminus_class.should respond_to(:use_server_setting)
+ end
- @searcher.deserialize(response)
- end
+ it "should use any specified setting to pick the server" do
+ terminus_class.expects(:server_setting).returns :inventory_server
+ Puppet[:inventory_server] = "myserver"
+ terminus_class.server.should == "myserver"
+ end
- it "should uncompress the body" do
- @model.expects(:convert_from).with("myformat", "uncompressed mydata").returns "myobject"
+ it "should default to :server for the server setting" do
+ terminus_class.expects(:server_setting).returns nil
+ Puppet[:server] = "myserver"
+ terminus_class.server.should == "myserver"
+ end
- response = mock 'response'
- response.stubs(:[]).with("content-type").returns "myformat"
- response.stubs(:body).returns "compressed mydata"
- response.stubs(:code).returns "200"
+ it "should have a method for specifying what setting a subclass should use to retrieve its port" do
+ terminus_class.should respond_to(:use_port_setting)
+ end
- @searcher.expects(:uncompress_body).with(response).returns("uncompressed mydata")
+ it "should use any specified setting to pick the port" do
+ terminus_class.expects(:port_setting).returns :ca_port
+ Puppet[:ca_port] = "321"
+ terminus_class.port.should == 321
+ end
- @searcher.deserialize(response).should == "myobject"
- end
+ it "should default to :port for the port setting" do
+ terminus_class.expects(:port_setting).returns nil
+ Puppet[:masterport] = "543"
+ terminus_class.port.should == 543
end
describe "when creating an HTTP client" do
@@ -189,303 +180,294 @@ describe Puppet::Indirector::REST do
it "should use the class's server and port if the indirection request provides neither" do
@request = stub 'request', :key => "foo", :server => nil, :port => nil
- @searcher.class.expects(:port).returns 321
- @searcher.class.expects(:server).returns "myserver"
+ terminus.class.expects(:port).returns 321
+ terminus.class.expects(:server).returns "myserver"
Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn"
- @searcher.network(@request).should == "myconn"
+ terminus.network(@request).should == "myconn"
end
it "should use the server from the indirection request if one is present" do
@request = stub 'request', :key => "foo", :server => "myserver", :port => nil
- @searcher.class.stubs(:port).returns 321
+ terminus.class.stubs(:port).returns 321
Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn"
- @searcher.network(@request).should == "myconn"
+ terminus.network(@request).should == "myconn"
end
it "should use the port from the indirection request if one is present" do
@request = stub 'request', :key => "foo", :server => nil, :port => 321
- @searcher.class.stubs(:server).returns "myserver"
+ terminus.class.stubs(:server).returns "myserver"
Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn"
- @searcher.network(@request).should == "myconn"
+ terminus.network(@request).should == "myconn"
end
end
- describe "when doing a find" do
- before :each do
- @connection = stub('mock http connection', :get => @response)
- @searcher.stubs(:network).returns(@connection) # neuter the network connection
+ describe "#find" do
+ let(:http_method) { :get }
+ let(:response) { mock_response(200, 'body') }
+ let(:connection) { stub('mock http connection', :get => response, :verify_callback= => nil) }
+ let(:request) { find_request('foo') }
- # Use a key with spaces, so we can test escaping
- @request = Puppet::Indirector::Request.new(:foo, :find, "foo bar")
+ before :each do
+ terminus.stubs(:network).returns(connection)
end
- it "should call the GET http method on a network connection" do
- @searcher.expects(:network).returns @connection
- @connection.expects(:get).returns @response
- @searcher.find(@request)
- end
+ it_behaves_like 'a REST terminus method', :find
+ it_behaves_like 'a deserializing terminus method', :find
- it "should deserialize and return the http response, setting name" do
- @connection.expects(:get).returns @response
+ describe "with no parameters" do
+ it "calls get on the connection" do
+ request = find_request('foo bar')
- instance = stub 'object'
- instance.expects(:name=)
- @searcher.expects(:deserialize).with(@response).returns instance
+ connection.expects(:get).with('/production/test_model/foo%20bar', anything).returns(mock_response('200', 'response body'))
- @searcher.find(@request).should == instance
+ terminus.find(request).should == model.new('foo bar', 'response body')
+ end
end
- it "should deserialize and return the http response, and not require name=" do
- @connection.expects(:get).returns @response
+ it "returns nil on 404" do
+ response = mock_response('404', nil)
- instance = stub 'object'
- @searcher.expects(:deserialize).with(@response).returns instance
+ connection.expects(:get).returns(response)
- @searcher.find(@request).should == instance
+ terminus.find(request).should == nil
end
+ it "asks the model to deserialize the response body and sets the name on the resulting object to the find key" do
+ connection.expects(:get).returns response
- it "should use the URI generated by the Handler module" do
- @searcher.expects(:indirection2uri).with(@request).returns "/my/uri"
- @connection.expects(:get).with { |path, args| path == "/my/uri" }.returns(@response)
- @searcher.find(@request)
+ model.expects(:convert_from).with(response['content-type'], response.body).returns(
+ model.new('overwritten', 'decoded body')
+ )
+
+ terminus.find(request).should == model.new('foo', 'decoded body')
end
- it "should provide an Accept header containing the list of supported formats joined with commas" do
- @connection.expects(:get).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
+ it "doesn't require the model to support name=" do
+ connection.expects(:get).returns response
+ instance = model.new('name', 'decoded body')
- @searcher.model.expects(:supported_formats).returns %w{supported formats}
- @searcher.find(@request)
+ model.expects(:convert_from).with(response['content-type'], response.body).returns(instance)
+ instance.expects(:respond_to?).with(:name=).returns(false)
+ instance.expects(:name=).never
+
+ terminus.find(request).should == model.new('name', 'decoded body')
end
- it "should add Accept-Encoding header" do
- @searcher.expects(:add_accept_encoding).returns({"accept-encoding" => "gzip"})
+ it "provides an Accept header containing the list of supported formats joined with commas" do
+ connection.expects(:get).with(anything, has_entry("Accept" => "supported, formats")).returns(response)
- @connection.expects(:get).with { |path, args| args["accept-encoding"] == "gzip" }.returns(@response)
- @searcher.find(@request)
+ terminus.model.expects(:supported_formats).returns %w{supported formats}
+ terminus.find(request)
end
- it "should deserialize and return the network response" do
- @searcher.expects(:deserialize).with(@response).returns @instance
- @searcher.find(@request).should equal(@instance)
- end
+ it "adds an Accept-Encoding header" do
+ terminus.expects(:add_accept_encoding).returns({"accept-encoding" => "gzip"})
- it "should set the name of the resulting instance to the asked-for name" do
- @searcher.expects(:deserialize).with(@response).returns @instance
- @instance.expects(:name=).with "foo bar"
- @searcher.find(@request)
- end
+ connection.expects(:get).with(anything, has_entry("accept-encoding" => "gzip")).returns(response)
- it "should generate an error when result data deserializes fails" do
- @searcher.expects(:deserialize).raises(ArgumentError)
- lambda { @searcher.find(@request) }.should raise_error(ArgumentError)
+ terminus.find(request)
end
- end
- describe "when doing a head" do
- before :each do
- @connection = stub('mock http connection', :head => @response)
- @searcher.stubs(:network).returns(@connection)
+ it "uses only the mime-type from the content-type header when asking the model to deserialize" do
+ response = mock_response('200', 'mydata', "text/plain; charset=utf-8")
+ connection.expects(:get).returns(response)
+
+ model.expects(:convert_from).with("text/plain", "mydata").returns "myobject"
- # Use a key with spaces, so we can test escaping
- @request = Puppet::Indirector::Request.new(:foo, :head, "foo bar")
+ terminus.find(request).should == "myobject"
end
- it "should call the HEAD http method on a network connection" do
- @searcher.expects(:network).returns @connection
- @connection.expects(:head).returns @response
- @searcher.head(@request)
+ it "decompresses the body before passing it to the model for deserialization" do
+ uncompressed_body = "Why hello there"
+ compressed_body = Zlib::Deflate.deflate(uncompressed_body)
+
+ response = mock_response('200', compressed_body, 'text/plain', 'deflate')
+ connection.expects(:get).returns(response)
+
+ model.expects(:convert_from).with("text/plain", uncompressed_body).returns "myobject"
+
+ terminus.find(request).should == "myobject"
end
+ end
- it "should return true if there was a successful http response" do
- @connection.expects(:head).returns @response
- @response.stubs(:code).returns "200"
+ describe "#head" do
+ let(:http_method) { :head }
+ let(:response) { mock_response(200, nil) }
+ let(:connection) { stub('mock http connection', :head => response, :verify_callback= => nil) }
+ let(:request) { head_request('foo') }
- @searcher.head(@request).should == true
+ before :each do
+ terminus.stubs(:network).returns(connection)
end
- it "should return false if there was a successful http response" do
- @connection.expects(:head).returns @response
- @response.stubs(:code).returns "404"
+ it_behaves_like 'a REST terminus method', :head
+
+ it "returns true if there was a successful http response" do
+ connection.expects(:head).returns mock_response('200', nil)
- @searcher.head(@request).should == false
+ terminus.head(request).should == true
end
- it "should use the URI generated by the Handler module" do
- @searcher.expects(:indirection2uri).with(@request).returns "/my/uri"
- @connection.expects(:head).with { |path, args| path == "/my/uri" }.returns(@response)
- @searcher.head(@request)
+ it "returns false on a 404 response" do
+ connection.expects(:head).returns mock_response('404', nil)
+
+ terminus.head(request).should == false
end
end
- describe "when doing a search" do
- before :each do
- @connection = stub('mock http connection', :get => @response)
- @searcher.stubs(:network).returns(@connection) # neuter the network connection
-
- @model.stubs(:convert_from_multiple)
+ describe "#search" do
+ let(:http_method) { :get }
+ let(:response) { mock_response(200, 'data1,data2,data3') }
+ let(:connection) { stub('mock http connection', :get => response, :verify_callback= => nil) }
+ let(:request) { search_request('foo') }
- @request = Puppet::Indirector::Request.new(:foo, :search, "foo bar")
+ before :each do
+ terminus.stubs(:network).returns(connection)
end
+ it_behaves_like 'a REST terminus method', :search
+ it_behaves_like 'a deserializing terminus method', :search
+
it "should call the GET http method on a network connection" do
- @searcher.expects(:network).returns @connection
- @connection.expects(:get).returns @response
- @searcher.search(@request)
+ connection.expects(:get).with('/production/test_models/foo', has_key('Accept')).returns mock_response(200, 'data3, data4')
+
+ terminus.search(request)
end
- it "should deserialize as multiple instances and return the http response" do
- @connection.expects(:get).returns @response
- @searcher.expects(:deserialize).with(@response, true).returns "myobject"
+ it "returns an empty list on 404" do
+ response = mock_response('404', nil)
+
+ connection.expects(:get).returns(response)
- @searcher.search(@request).should == 'myobject'
+ terminus.search(request).should == []
end
- it "should use the URI generated by the Handler module" do
- @searcher.expects(:indirection2uri).with(@request).returns "/mys/uri"
- @connection.expects(:get).with { |path, args| path == "/mys/uri" }.returns(@response)
- @searcher.search(@request)
+ it "asks the model to deserialize the response body into multiple instances" do
+ terminus.search(request).should == [model.new('', 'data1'), model.new('', 'data2'), model.new('', 'data3')]
end
it "should provide an Accept header containing the list of supported formats joined with commas" do
- @connection.expects(:get).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
+ connection.expects(:get).with(anything, has_entry("Accept" => "supported, formats")).returns(mock_response(200, ''))
- @searcher.model.expects(:supported_formats).returns %w{supported formats}
- @searcher.search(@request)
+ terminus.model.expects(:supported_formats).returns %w{supported formats}
+ terminus.search(request)
end
it "should return an empty array if serialization returns nil" do
- @model.stubs(:convert_from_multiple).returns nil
+ model.stubs(:convert_from_multiple).returns nil
- @searcher.search(@request).should == []
- end
-
- it "should generate an error when result data deserializes fails" do
- @searcher.expects(:deserialize).raises(ArgumentError)
- lambda { @searcher.search(@request) }.should raise_error(ArgumentError)
+ terminus.search(request).should == []
end
end
- describe "when doing a destroy" do
- before :each do
- @connection = stub('mock http connection', :delete => @response)
- @searcher.stubs(:network).returns(@connection) # neuter the network connection
+ describe "#destroy" do
+ let(:http_method) { :delete }
+ let(:response) { mock_response(200, 'body') }
+ let(:connection) { stub('mock http connection', :delete => response, :verify_callback= => nil) }
+ let(:request) { delete_request('foo') }
- @request = Puppet::Indirector::Request.new(:foo, :destroy, "foo bar")
+ before :each do
+ terminus.stubs(:network).returns(connection)
end
+ it_behaves_like 'a REST terminus method', :destroy
+ it_behaves_like 'a deserializing terminus method', :destroy
+
it "should call the DELETE http method on a network connection" do
- @searcher.expects(:network).returns @connection
- @connection.expects(:delete).returns @response
- @searcher.destroy(@request)
+ connection.expects(:delete).with('/production/test_model/foo', has_key('Accept')).returns(response)
+
+ terminus.destroy(request)
end
it "should fail if any options are provided, since DELETE apparently does not support query options" do
- @request.stubs(:options).returns(:one => "two", :three => "four")
+ request = delete_request('foo', :one => "two", :three => "four")
- lambda { @searcher.destroy(@request) }.should raise_error(ArgumentError)
+ expect { terminus.destroy(request) }.to raise_error(ArgumentError)
end
it "should deserialize and return the http response" do
- @connection.expects(:delete).returns @response
- @searcher.expects(:deserialize).with(@response).returns "myobject"
-
- @searcher.destroy(@request).should == 'myobject'
- end
+ connection.expects(:delete).returns response
- it "should use the URI generated by the Handler module" do
- @searcher.expects(:indirection2uri).with(@request).returns "/my/uri"
- @connection.expects(:delete).with { |path, args| path == "/my/uri" }.returns(@response)
- @searcher.destroy(@request)
+ terminus.destroy(request).should == model.new('', 'body')
end
- it "should not include the query string" do
- @connection.stubs(:delete).returns @response
- @searcher.destroy(@request)
- end
+ it "returns nil on 404" do
+ response = mock_response('404', nil)
- it "should provide an Accept header containing the list of supported formats joined with commas" do
- @connection.expects(:delete).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
+ connection.expects(:delete).returns(response)
- @searcher.model.expects(:supported_formats).returns %w{supported formats}
- @searcher.destroy(@request)
+ terminus.destroy(request).should == nil
end
- it "should deserialize and return the network response" do
- @searcher.expects(:deserialize).with(@response).returns @instance
- @searcher.destroy(@request).should equal(@instance)
- end
+ it "should provide an Accept header containing the list of supported formats joined with commas" do
+ connection.expects(:delete).with(anything, has_entry("Accept" => "supported, formats")).returns(response)
- it "should generate an error when result data deserializes fails" do
- @searcher.expects(:deserialize).raises(ArgumentError)
- lambda { @searcher.destroy(@request) }.should raise_error(ArgumentError)
+ terminus.model.expects(:supported_formats).returns %w{supported formats}
+ terminus.destroy(request)
end
end
- describe "when doing a save" do
- before :each do
- @connection = stub('mock http connection', :put => @response)
- @searcher.stubs(:network).returns(@connection) # neuter the network connection
+ describe "#save" do
+ let(:http_method) { :put }
+ let(:response) { mock_response(200, 'body') }
+ let(:connection) { stub('mock http connection', :put => response, :verify_callback= => nil) }
+ let(:instance) { model.new('the thing', 'some contents') }
+ let(:request) { save_request(instance.name, instance) }
- @instance = stub 'instance', :render => "mydata", :mime => "mime"
- @request = Puppet::Indirector::Request.new(:foo, :save, "foo bar")
- @request.instance = @instance
+ before :each do
+ terminus.stubs(:network).returns(connection)
end
- it "should call the PUT http method on a network connection" do
- @searcher.expects(:network).returns @connection
- @connection.expects(:put).returns @response
- @searcher.save(@request)
- end
+ it_behaves_like 'a REST terminus method', :save
- it "should fail if any options are provided, since DELETE apparently does not support query options" do
- @request.stubs(:options).returns(:one => "two", :three => "four")
+ it "should call the PUT http method on a network connection" do
+ connection.expects(:put).with('/production/test_model/the%20thing', anything, has_key("Content-Type")).returns response
- lambda { @searcher.save(@request) }.should raise_error(ArgumentError)
+ terminus.save(request)
end
- it "should use the URI generated by the Handler module" do
- @searcher.expects(:indirection2uri).with(@request).returns "/my/uri"
- @connection.expects(:put).with { |path, args| path == "/my/uri" }.returns(@response)
- @searcher.save(@request)
+ it "should fail if any options are provided, since PUT apparently does not support query options" do
+ request = save_request(instance, :one => "two", :three => "four")
+
+ expect { terminus.save(request) }.to raise_error(ArgumentError)
end
it "should serialize the instance using the default format and pass the result as the body of the request" do
- @instance.expects(:render).returns "serial_instance"
- @connection.expects(:put).with { |path, data, args| data == "serial_instance" }.returns @response
+ instance.expects(:render).returns "serial_instance"
+ connection.expects(:put).with(anything, "serial_instance", anything).returns response
- @searcher.save(@request)
+ terminus.save(request)
end
- it "should deserialize and return the http response" do
- @connection.expects(:put).returns @response
- @searcher.expects(:deserialize).with(@response).returns "myobject"
+ it "returns nil on 404" do
+ response = mock_response('404', nil)
- @searcher.save(@request).should == 'myobject'
+ connection.expects(:put).returns(response)
+
+ terminus.save(request).should == nil
end
- it "should provide an Accept header containing the list of supported formats joined with commas" do
- @connection.expects(:put).with { |path, data, args| args["Accept"] == "supported, formats" }.returns(@response)
+ it "returns nil" do
+ connection.expects(:put).returns response
- @searcher.model.expects(:supported_formats).returns %w{supported formats}
- @searcher.save(@request)
+ terminus.save(request).should be_nil
end
- it "should provide a Content-Type header containing the mime-type of the sent object" do
- @connection.expects(:put).with { |path, data, args| args['Content-Type'] == "mime" }.returns(@response)
+ it "should provide an Accept header containing the list of supported formats joined with commas" do
+ connection.expects(:put).with(anything, anything, has_entry("Accept" => "supported, formats")).returns(response)
- @instance.expects(:mime).returns "mime"
- @searcher.save(@request)
- end
+ instance.expects(:render).returns('')
+ model.expects(:supported_formats).returns %w{supported formats}
+ instance.expects(:mime).returns "supported"
- it "should deserialize and return the network response" do
- @searcher.expects(:deserialize).with(@response).returns @instance
- @searcher.save(@request).should equal(@instance)
+ terminus.save(request)
end
- it "should generate an error when result data deserializes fails" do
- @searcher.expects(:deserialize).raises(ArgumentError)
- lambda { @searcher.save(@request) }.should raise_error(ArgumentError)
+ it "should provide a Content-Type header containing the mime-type of the sent object" do
+ instance.expects(:mime).returns "mime"
+ connection.expects(:put).with(anything, anything, has_entry('Content-Type' => "mime")).returns(response)
+
+ terminus.save(request)
end
end
end
diff --git a/spec/unit/network/formats_spec.rb b/spec/unit/network/formats_spec.rb
index c6f2b32..ea8d92a 100755
--- a/spec/unit/network/formats_spec.rb
+++ b/spec/unit/network/formats_spec.rb
@@ -56,16 +56,29 @@ describe "Puppet Network Format" do
@yaml.render_multiple(instances).should == "foo"
end
- it "should intern by calling 'YAML.load'" do
- text = "foo"
- YAML.expects(:load).with("foo").returns "bar"
- @yaml.intern(String, text).should == "bar"
+ it "should deserialize YAML" do
+ @yaml.intern(String, YAML.dump("foo")).should == "foo"
end
- it "should intern multiples by calling 'YAML.load'" do
- text = "foo"
- YAML.expects(:load).with("foo").returns "bar"
- @yaml.intern_multiple(String, text).should == "bar"
+ it "should deserialize symbols as strings" do
+ @yaml.intern(String, YAML.dump(:foo)).should == "foo"
+ end
+
+ it "should fail when type does not match deserialized form and has no from_pson" do
+ expect do
+ @yaml.intern(Hash, YAML.dump("foo"))
+ end.to raise_error(NoMethodError)
+ end
+
+ it "should load from yaml when deserializing an array" do
+ text = YAML.dump(["foo"])
+ @yaml.intern_multiple(String, text).should == ["foo"]
+ end
+
+ it "should fail when one element does not have a from_pson" do
+ expect do
+ @yaml.intern_multiple(Hash, YAML.dump(["foo"]))
+ end.to raise_error(NoMethodError)
end
end
@@ -109,29 +122,25 @@ describe "Puppet Network Format" do
@yaml.render(instances).should == "bar"
end
- it "should intern by calling decode" do
- text = "foo"
- @yaml.expects(:decode).with("foo").returns "bar"
- @yaml.intern(String, text).should == "bar"
+ it "should round trip data" do
+ @yaml.intern(String, @yaml.encode("foo")).should == "foo"
end
- it "should intern multiples by calling 'decode'" do
- text = "foo"
- @yaml.expects(:decode).with("foo").returns "bar"
- @yaml.intern_multiple(String, text).should == "bar"
+ it "should round trip multiple data elements" do
+ data = @yaml.render_multiple(["foo", "bar"])
+ @yaml.intern_multiple(String, data).should == ["foo", "bar"]
end
- it "should decode by base64 decoding, uncompressing and Yaml loading" do
- Base64.expects(:decode64).with("zorg").returns "foo"
- Zlib::Inflate.expects(:inflate).with("foo").returns "baz"
- YAML.expects(:load).with("baz").returns "bar"
- @yaml.decode("zorg").should == "bar"
+ it "should intern by base64 decoding, uncompressing and safely Yaml loading" do
+ input = Base64.encode64(Zlib::Deflate.deflate(YAML.dump("data in")))
+
+ @yaml.intern(String, input).should == "data in"
end
- it "should encode by compressing and base64 encoding" do
- Zlib::Deflate.expects(:deflate).with("foo", Zlib::BEST_COMPRESSION).returns "bar"
- Base64.expects(:encode64).with("bar").returns "baz"
- @yaml.encode("foo").should == "baz"
+ it "should render by compressing and base64 encoding" do
+ output = @yaml.render("foo")
+
+ YAML.load(Zlib::Inflate.inflate(Base64.decode64(output))).should == "foo"
end
describe "when zlib is disabled" do
@@ -144,11 +153,11 @@ describe "Puppet Network Format" do
end
it "should refuse to encode" do
- lambda{ @yaml.encode("foo") }.should raise_error
+ expect { @yaml.render("foo") }.to raise_error(Puppet::Error, /zlib library is not installed/)
end
it "should refuse to decode" do
- lambda{ @yaml.decode("foo") }.should raise_error
+ expect { @yaml.intern(String, "foo") }.to raise_error(Puppet::Error, /zlib library is not installed/)
end
end
diff --git a/spec/unit/network/http/handler_spec.rb b/spec/unit/network/http/handler_spec.rb
index 7c79854..27eff58 100755
--- a/spec/unit/network/http/handler_spec.rb
+++ b/spec/unit/network/http/handler_spec.rb
@@ -139,6 +139,22 @@ describe Puppet::Network::HTTP::Handler do
@handler.request_format(@request).should == "s"
end
+ it "should deserialize YAML parameters" do
+ params = {'my_param' => [1,2,3].to_yaml}
+
+ decoded_params = @handler.send(:decode_params, params)
+
+ decoded_params.should == {:my_param => [1,2,3]}
+ end
+
+ it "should ignore tags on YAML parameters" do
+ params = {'my_param' => "--- !ruby/object:Array {}"}
+
+ decoded_params = @handler.send(:decode_params, params)
+
+ decoded_params[:my_param].should be_a(Hash)
+ end
+
describe "when finding a model instance" do
before do
@irequest = stub 'indirection_request', :method => :find, :indirection_name => "my_handler", :to_hash => {}, :key => "my_result", :model => @model_class
diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb
index 2fcde8f..43aada1 100755
--- a/spec/unit/node_spec.rb
+++ b/spec/unit/node_spec.rb
@@ -36,6 +36,20 @@ describe Puppet::Node do
node.environment.name.should == :bar
end
end
+
+ it "can survive a round-trip through YAML" do
+ facts = Puppet::Node::Facts.new("hello", "one" => "c", "two" => "b")
+ node = Puppet::Node.new("hello",
+ :environment => 'kjhgrg',
+ :classes => ['erth', 'aiu'],
+ :parameters => {"hostname"=>"food"}
+ )
+ new_node = Puppet::Node.convert_from('yaml', node.render('yaml'))
+ new_node.environment.should == node.environment
+ new_node.parameters.should == node.parameters
+ new_node.classes.should == node.classes
+ new_node.name.should == node.name
+ end
end
describe Puppet::Node, "when initializing" do
diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb
index 345ccd0..12bceb9 100755
--- a/spec/unit/resource_spec.rb
+++ b/spec/unit/resource_spec.rb
@@ -447,44 +447,16 @@ describe Puppet::Resource do
@resource["two"] = "other"
end
- it "should be able to be dumped to yaml" do
- proc { YAML.dump(@resource) }.should_not raise_error
- end
-
it "should produce an equivalent yaml object" do
- text = YAML.dump(@resource)
+ text = @resource.render('yaml')
- newresource = YAML.load(text)
- newresource.title.should == @resource.title
- newresource.type.should == @resource.type
- %w{one two}.each do |param|
- newresource[param].should == @resource[param]
+ newresource = Puppet::Resource.convert_from('yaml', text)
+ newresource.instance_variables.each do |attr|
+ newresource.instance_variable_get(attr).should == @resource.instance_variable_get(attr)
end
end
end
- describe "when loading 0.25.x storedconfigs YAML" do
- before :each do
- @old_storedconfig_yaml = %q{--- !ruby/object:Puppet::Resource::Reference
-builtin_type:
-title: /tmp/bar
-type: File
-}
- end
-
- it "should deserialize a Puppet::Resource::Reference without exceptions" do
- lambda { YAML.load(@old_storedconfig_yaml) }.should_not raise_error
- end
-
- it "should deserialize as a Puppet::Resource::Reference as a Puppet::Resource" do
- YAML.load(@old_storedconfig_yaml).class.should == Puppet::Resource
- end
-
- it "should to_hash properly" do
- YAML.load(@old_storedconfig_yaml).to_hash.should == { :path => "/tmp/bar" }
- end
- end
-
describe "when converting to a RAL resource" do
it "should use the resource type's :new method to create the resource if the resource is of a builtin type" do
resource = Puppet::Resource.new("file", @basepath+"/my/file")
@@ -664,13 +636,13 @@ type: File
resource = Puppet::Resource.new("File", "/foo")
resource.exported = true
- Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).exported.should be_true
+ Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).exported?.should be_true
end
it "should set 'exported' to false if no value is set" do
resource = Puppet::Resource.new("File", "/foo")
- Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).exported.should be_false
+ Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).exported?.should be_false
end
it "should set all of its parameters as the 'parameters' entry" do
diff --git a/spec/unit/run_spec.rb b/spec/unit/run_spec.rb
index 58a16d2..bffbbf9 100755
--- a/spec/unit/run_spec.rb
+++ b/spec/unit/run_spec.rb
@@ -117,21 +117,35 @@ describe Puppet::Run do
end
describe ".from_pson" do
- it "should accept a hash of options, and pass them with symbolified keys to new" do
+ it "should read from a hash that represents the 'options' to initialize" do
options = {
"tags" => "whatever",
"background" => true,
+ "ignoreschedules" => false,
}
+ run = Puppet::Run.from_pson(options)
-
- Puppet::Run.expects(:new).with(
- {
+ run.options.should == {
:tags => "whatever",
- :background => true,
-
- })
+ :ignoreschedules => false,
+ }
+ run.background.should be_true
+ end
- Puppet::Run.from_pson(options)
+ it "should read from a hash that follows the actual object structure" do
+ hash = {"background" => true,
+ "options" => {
+ "tags" => [],
+ "ignoreschedules" => false},
+ "status" => "success"}
+ run = Puppet::Run.from_pson(hash)
+
+ run.options.should == {
+ :tags => [],
+ :ignoreschedules => false
+ }
+ run.background.should be_true
+ run.status.should == 'success'
end
end
end
diff --git a/spec/unit/status_spec.rb b/spec/unit/status_spec.rb
index 71bfa4a..d8937e4 100644
--- a/spec/unit/status_spec.rb
+++ b/spec/unit/status_spec.rb
@@ -28,4 +28,12 @@ describe Puppet::Status do
it "should allow a name to be set" do
Puppet::Status.new.name = "status"
end
+
+ it "can do a round-trip serialization via YAML" do
+ status = Puppet::Status.new
+ new_status = Puppet::Status.convert_from('yaml', status.render('yaml'))
+ new_status.instance_variables.each do |attr|
+ new_status.instance_variable_get(attr).should == status.instance_variable_get(attr)
+ end
+ end
end
diff --git a/test/network/handler/report.rb b/test/network/handler/report.rb
index 590dcdb..914ccae 100755
--- a/test/network/handler/report.rb
+++ b/test/network/handler/report.rb
@@ -31,42 +31,6 @@ class TestReportServer < Test::Unit::TestCase
client
end
- def test_process
- server = Puppet::Network::Handler.report.new
-
- # We have to run multiple reports to make sure there's no conflict
- reports = []
- $run = []
- 2.times do |i|
- name = "processtest#{i}"
- reports << name
-
- Report.newreport(name) do
- def process
- $run << self.report_name
- end
- end
- end
- Puppet[:reports] = reports.collect { |r| r.to_s }.join(",")
-
- report = fakereport
-
- retval = nil
- assert_nothing_raised {
- retval = server.send(:process, YAML.dump(report))
- }
-
- reports.each do |name|
- assert($run.include?(name.intern), "Did not run #{name}")
- end
-
- # Now make sure our server doesn't die on missing reports
- Puppet[:reports] = "fakereport"
- assert_nothing_raised {
- retval = server.send(:process, YAML.dump(report))
- }
- end
-
def test_reports
Puppet[:reports] = "myreport"
--
1.8.2.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment