diff --git a/Gemfile b/Gemfile index 824660a6b..94120da85 100644 --- a/Gemfile +++ b/Gemfile @@ -1,80 +1,80 @@ source ENV['GEM_SOURCE'] || "https://rubygems.org" def location_for(place, fake_version = nil) if place =~ /^(git[:@][^#]*)#(.*)/ [fake_version, { :git => $1, :branch => $2, :require => false }].compact elsif place =~ /^file:\/\/(.*)/ ['>= 0', { :path => File.expand_path($1), :require => false }] else [place, { :require => false }] end end # C Ruby (MRI) or Rubinius, but NOT Windows platforms :ruby do gem 'pry', :group => :development gem 'yard', :group => :development gem 'redcarpet', '~> 2.0', :group => :development gem "racc", "1.4.9", :group => :development end gem "puppet", :path => File.dirname(__FILE__), :require => false gem "facter", *location_for(ENV['FACTER_LOCATION'] || '~> 1.6') gem "hiera", *location_for(ENV['HIERA_LOCATION'] || '~> 1.0') gem "rake", :require => false gem "rgen", "0.6.5", :require => false group(:development, :test) do # Jenkins workers may be using RSpec 2.9, so RSpec 2.11 syntax # (like `expect(value).to eq matcher`) should be avoided. gem "rspec", "~> 2.11.0", :require => false # Mocha is not compatible across minor version changes; because of this only # versions matching ~> 0.10.5 are supported. All other versions are unsupported # and can be expected to fail. gem "mocha", "~> 0.10.5", :require => false gem "yarjuf", "~> 1.0" # json-schema does not support windows, so use the 'ruby' platform to exclude it on windows platforms :ruby do # json-schema uses multi_json, but chokes with multi_json 1.7.9, so prefer 1.7.7 gem "multi_json", "1.7.7", :require => false gem "json-schema", "2.1.1", :require => false end end group(:extra) do gem "rack", "~> 1.4", :require => false - gem "activerecord", '~> 3.0.7', :require => false + gem "activerecord", '~> 3.2', :require => false gem "couchrest", '~> 1.0', :require => false gem "net-ssh", '~> 2.1', :require => false gem "puppetlabs_spec_helper", :require => false gem "sqlite3", :require => false gem "stomp", :require => false gem "tzinfo", :require => false gem "msgpack", :require => false end require 'yaml' data = YAML.load_file(File.join(File.dirname(__FILE__), 'ext', 'project_data.yaml')) bundle_platforms = data['bundle_platforms'] data['gem_platform_dependencies'].each_pair do |gem_platform, info| if bundle_deps = info['gem_runtime_dependencies'] bundle_platform = bundle_platforms[gem_platform] or raise "Missing bundle_platform" platform(bundle_platform.intern) do bundle_deps.each_pair do |name, version| gem(name, version, :require => false) end end end end if File.exists? "#{__FILE__}.local" eval(File.read("#{__FILE__}.local"), binding) end # vim:filetype=ruby diff --git a/acceptance/tests/store_configs/enc_provides_node_when_storeconfigs_enabled.rb b/acceptance/tests/store_configs/enc_provides_node_when_storeconfigs_enabled.rb index daf283f51..0d8e16785 100644 --- a/acceptance/tests/store_configs/enc_provides_node_when_storeconfigs_enabled.rb +++ b/acceptance/tests/store_configs/enc_provides_node_when_storeconfigs_enabled.rb @@ -1,93 +1,93 @@ test_name "ENC node information is used when store configs enabled (#16698)" confine :except, :platform => 'solaris' confine :except, :platform => 'windows' confine :except, :platform => 'el-6' confine :except, :platform => 'lucid' testdir = master.tmpdir('use_enc') create_remote_file master, "#{testdir}/enc.rb", < [], 'parameters' => { 'data' => 'data from enc' }, }.to_yaml) END on master, "chmod 755 #{testdir}/enc.rb" create_remote_file(master, "#{testdir}/site.pp", 'notify { $data: }') on master, "chown -R #{master['user']}:#{master['group']} #{testdir}" on master, "chmod -R g+rwX #{testdir}" create_remote_file master, "#{testdir}/setup.pp", < $lsbmajdistrelease ? { 5 => '2.2.3', - default => '3.0.20', + default => '3.2.16', }, - default => '3.0.20', + default => '3.2.16', } package { rubygems: ensure => present; activerecord: ensure => $active_record_version, provider => 'gem', require => Package[rubygems] } if $osfamily == "Debian" { package { sqlite3: ensure => present; libsqlite3-ruby: ensure => present, require => Package[sqlite3] } } elsif $osfamily == "RedHat" { $sqlite_gem_pkg_name = $operatingsystem ? { "Fedora" => "rubygem-sqlite3", default => "rubygem-sqlite3-ruby" } package { sqlite: ensure => present; $sqlite_gem_pkg_name: ensure => present, require => Package[sqlite] } } else { fail "Unknown OS $osfamily" } END on master, puppet_apply("#{testdir}/setup.pp") master_opts = { 'master' => { 'node_terminus' => 'exec', 'external_nodes' => "#{testdir}/enc.rb", 'storeconfigs' => true, 'dbadapter' => 'sqlite3', 'dblocation' => "#{testdir}/store_configs.sqlite3", 'manifest' => "#{testdir}/site.pp" } } with_puppet_running_on master, master_opts, testdir do agents.each do |agent| run_agent_on(agent, "--no-daemonize --onetime --server #{master} --verbose") assert_match(/data from enc/, stdout) end end diff --git a/lib/puppet/network/http/connection.rb b/lib/puppet/network/http/connection.rb index e7468562e..a2ce6eef3 100644 --- a/lib/puppet/network/http/connection.rb +++ b/lib/puppet/network/http/connection.rb @@ -1,254 +1,254 @@ require 'net/https' require 'puppet/ssl/host' require 'puppet/ssl/configuration' require 'puppet/ssl/validator' require 'puppet/network/authentication' require 'uri' module Puppet::Network::HTTP # This will be raised if too many redirects happen for a given HTTP request class RedirectionLimitExceededException < Puppet::Error ; end # This class provides simple methods for issuing various types of HTTP # requests. It's interface is intended to mirror Ruby's Net::HTTP # object, but it provides a few important bits of additional # functionality. Notably: # # * Any HTTPS requests made using this class will use Puppet's SSL # certificate configuration for their authentication, and # * Provides some useful error handling for any SSL errors that occur # during a request. # @api public class Connection include Puppet::Network::Authentication OPTION_DEFAULTS = { :use_ssl => true, :verify => nil, :redirect_limit => 10, } @@openssl_initialized = false # Creates a new HTTP client connection to `host`:`port`. # @param host [String] the host to which this client will connect to # @param port [Fixnum] the port to which this client will connect to # @param options [Hash] options influencing the properties of the created # connection, # @option options [Boolean] :use_ssl true to connect with SSL, false # otherwise, defaults to true # @option options [#setup_connection] :verify An object that will configure # any verification to do on the connection # @option options [Fixnum] :redirect_limit the number of allowed # redirections, defaults to 10 passing any other option in the options # hash results in a Puppet::Error exception # # @note the HTTP connection itself happens lazily only when {#request}, or # one of the {#get}, {#post}, {#delete}, {#head} or {#put} is called # @note The correct way to obtain a connection is to use one of the factory # methods on {Puppet::Network::HttpPool} # @api private def initialize(host, port, options = {}) @host = host @port = port unknown_options = options.keys - OPTION_DEFAULTS.keys raise Puppet::Error, "Unrecognized option(s): #{unknown_options.map(&:inspect).sort.join(', ')}" unless unknown_options.empty? options = OPTION_DEFAULTS.merge(options) @use_ssl = options[:use_ssl] @verify = options[:verify] @redirect_limit = options[:redirect_limit] end # @!macro [new] common_options # @param options [Hash] options influencing the request made # @option options [Hash{Symbol => String}] :basic_auth The basic auth # :username and :password to use for the request # @param path [String] # @param headers [Hash{String => String}] # @!macro common_options # @api public def get(path, headers = {}, options = {}) request_with_redirects(Net::HTTP::Get.new(path, headers), options) end # @param path [String] # @param data [String] # @param headers [Hash{String => String}] # @!macro common_options # @api public def post(path, data, headers = nil, options = {}) request = Net::HTTP::Post.new(path, headers) request.body = data request_with_redirects(request, options) end # @param path [String] # @param headers [Hash{String => String}] # @!macro common_options # @api public def head(path, headers = {}, options = {}) request_with_redirects(Net::HTTP::Head.new(path, headers), options) end # @param path [String] # @param headers [Hash{String => String}] # @!macro common_options # @api public def delete(path, headers = {'Depth' => 'Infinity'}, options = {}) request_with_redirects(Net::HTTP::Delete.new(path, headers), options) end # @param path [String] # @param data [String] # @param headers [Hash{String => String}] # @!macro common_options # @api public def put(path, data, headers = nil, options = {}) request = Net::HTTP::Put.new(path, headers) request.body = data request_with_redirects(request, options) end def request(method, *args) self.send(method, *args) end # TODO: These are proxies for the Net::HTTP#request_* methods, which are # almost the same as the "get", "post", etc. methods that we've ported above, # but they are able to accept a code block and will yield to it. For now # we're not funneling these proxy implementations through our #request # method above, so they will not inherit the same error handling. In the # future we may want to refactor these so that they are funneled through # that method and do inherit the error handling. def request_get(*args, &block) connection.request_get(*args, &block) end def request_head(*args, &block) connection.request_head(*args, &block) end def request_post(*args, &block) connection.request_post(*args, &block) end # end of Net::HTTP#request_* proxies def address connection.address end def port connection.port end def use_ssl? connection.use_ssl? end private def request_with_redirects(request, options) current_request = request @redirect_limit.times do |redirection| apply_options_to(current_request, options) response = execute_request(current_request) return response unless [301, 302, 307].include?(response.code.to_i) # handle the redirection location = URI.parse(response['location']) @connection = initialize_connection(location.host, location.port, location.scheme == 'https') # update to the current request path current_request = current_request.class.new(location.path) current_request.body = request.body request.each do |header, value| current_request[header] = value end # and try again... end raise RedirectionLimitExceededException, "Too many HTTP redirections for #{@host}:#{@port}" end def apply_options_to(request, options) if options[:basic_auth] request.basic_auth(options[:basic_auth][:user], options[:basic_auth][:password]) end end def connection @connection || initialize_connection(@host, @port, @use_ssl) end def execute_request(request) response = connection.request(request) # Check the peer certs and warn if they're nearing expiration. warn_if_near_expiration(*@verify.peer_certs) response rescue OpenSSL::SSL::SSLError => error if error.message.include? "certificate verify failed" msg = error.message msg << ": [" + @verify.verify_errors.join('; ') + "]" raise Puppet::Error, msg, error.backtrace - elsif error.message =~ /hostname (\w+ )?not match/ + elsif error.message =~ /hostname.*not match.*server certificate/ leaf_ssl_cert = @verify.peer_certs.last valid_certnames = [leaf_ssl_cert.name, *leaf_ssl_cert.subject_alt_names].uniq msg = valid_certnames.length > 1 ? "one of #{valid_certnames.join(', ')}" : valid_certnames.first msg = "Server hostname '#{connection.address}' did not match server certificate; expected #{msg}" raise Puppet::Error, msg, error.backtrace else raise end end def initialize_connection(host, port, use_ssl) args = [host, port] if Puppet[:http_proxy_host] == "none" args << nil << nil else args << Puppet[:http_proxy_host] << Puppet[:http_proxy_port] end @connection = create_connection(*args) # Pop open the http client a little; older versions of Net::HTTP(s) didn't # give us a reader for ca_file... Grr... class << @connection; attr_accessor :ca_file; end @connection.use_ssl = use_ssl # Use configured timeout (#1176) @connection.read_timeout = Puppet[:configtimeout] @connection.open_timeout = Puppet[:configtimeout] cert_setup @connection end # Use cert information from a Puppet client to set up the http object. def cert_setup # PUP-1411, make sure that openssl is initialized before we try to connect if ! @@openssl_initialized OpenSSL::SSL::SSLContext.new @@openssl_initialized = true end @verify.setup_connection(@connection) end # This method largely exists for testing purposes, so that we can # mock the actual HTTP connection. def create_connection(*args) Net::HTTP.new(*args) end end end diff --git a/lib/puppet/rails/resource.rb b/lib/puppet/rails/resource.rb index cded3f3c1..a2f732613 100644 --- a/lib/puppet/rails/resource.rb +++ b/lib/puppet/rails/resource.rb @@ -1,231 +1,235 @@ require 'puppet' require 'puppet/rails/param_name' require 'puppet/rails/param_value' require 'puppet/rails/puppet_tag' require 'puppet/rails/benchmark' require 'puppet/util/rails/collection_merger' class Puppet::Rails::Resource < ActiveRecord::Base include Puppet::Util::CollectionMerger include Puppet::Util::ReferenceSerializer include Puppet::Rails::Benchmark has_many :param_values, :dependent => :destroy, :class_name => "Puppet::Rails::ParamValue" has_many :param_names, :through => :param_values, :class_name => "Puppet::Rails::ParamName" has_many :resource_tags, :dependent => :destroy, :class_name => "Puppet::Rails::ResourceTag" has_many :puppet_tags, :through => :resource_tags, :class_name => "Puppet::Rails::PuppetTag" belongs_to :source_file belongs_to :host @tags = {} def self.tags @tags end # Determine the basic details on the resource. def self.rails_resource_initial_args(resource) result = [:type, :title, :line].inject({}) do |hash, param| # 'type' isn't a valid column name, so we have to use another name. to = (param == :type) ? :restype : param if value = resource.send(param) hash[to] = value end hash end # We always want a value here, regardless of what the resource has, # so we break it out separately. result[:exported] = resource.exported || false result end def add_resource_tag(tag) pt = Puppet::Rails::PuppetTag.accumulate_by_name(tag) resource_tags.build(:puppet_tag => pt) end def file (f = self.source_file) ? f.filename : nil end def file=(file) self.source_file = Puppet::Rails::SourceFile.find_or_create_by_filename(file) end def title unserialize_value(self[:title]) end def params_list @params_list ||= [] end def params_list=(params) @params_list = params end def add_param_to_list(param) params_list << param end def tags_list @tags_list ||= [] end def tags_list=(tags) @tags_list = tags end def add_tag_to_list(tag) tags_list << tag end def [](param) - super || parameter(param) + if param == 'id' + super + else + super || parameter(param) + end end # Make sure this resource is equivalent to the provided Parser resource. def merge_parser_resource(resource) accumulate_benchmark("Individual resource merger", :attributes) { merge_attributes(resource) } accumulate_benchmark("Individual resource merger", :parameters) { merge_parameters(resource) } accumulate_benchmark("Individual resource merger", :tags) { merge_tags(resource) } save end def merge_attributes(resource) args = self.class.rails_resource_initial_args(resource) args.each do |param, value| self[param] = value unless resource[param] == value end # Handle file specially self.file = resource.file if (resource.file and (!resource.file or self.file != resource.file)) end def merge_parameters(resource) catalog_params = {} resource.each do |param, value| catalog_params[param.to_s] = value end db_params = {} deletions = [] params_list.each do |value| # First remove any parameters our catalog resource doesn't have at all. deletions << value['id'] and next unless catalog_params.include?(value['name']) # Now store them for later testing. db_params[value['name']] ||= [] db_params[value['name']] << value end # Now get rid of any parameters whose value list is different. # This might be extra work in cases where an array has added or lost # a single value, but in the most common case (a single value has changed) # this makes sense. db_params.each do |name, value_hashes| values = value_hashes.collect { |v| v['value'] } value_hashes.each { |v| deletions << v['id'] } unless value_compare(catalog_params[name], values) end # Perform our deletions. Puppet::Rails::ParamValue.delete(deletions) unless deletions.empty? # Lastly, add any new parameters. catalog_params.each do |name, value| next if db_params.include?(name) && ! db_params[name].find{ |val| deletions.include?( val["id"] ) } values = value.is_a?(Array) ? value : [value] values.each do |v| param_values.build(:value => serialize_value(v), :line => resource.line, :param_name => Puppet::Rails::ParamName.accumulate_by_name(name)) end end end # Make sure the tag list is correct. def merge_tags(resource) in_db = [] deletions = [] resource_tags = resource.tags tags_list.each do |tag| deletions << tag['id'] and next unless resource_tags.include?(tag['name']) in_db << tag['name'] end Puppet::Rails::ResourceTag.delete(deletions) unless deletions.empty? (resource_tags - in_db).each do |tag| add_resource_tag(tag) end end def value_compare(v,db_value) v = [v] unless v.is_a?(Array) v == db_value end def name ref end def parameter(param) if pn = param_names.find_by_name(param) return (pv = param_values.find(:first, :conditions => [ 'param_name_id = ?', pn])) ? pv.value : nil end end def ref(dummy_argument=:work_arround_for_ruby_GC_bug) "#{self[:restype].split("::").collect { |s| s.capitalize }.join("::")}[#{self.title}]" end # Returns a hash of parameter names and values, no ActiveRecord instances. def to_hash Puppet::Rails::ParamValue.find_all_params_from_resource(self).inject({}) do |hash, value| hash[value['name']] ||= [] hash[value['name']] << value.value hash end end # Convert our object to a resource. Do not retain whether the object # is exported, though, since that would cause it to get stripped # from the configuration. def to_resource(scope) hash = self.attributes hash["type"] = hash["restype"] hash.delete("restype") # FIXME At some point, we're going to want to retain this information # for logging and auditing. hash.delete("host_id") hash.delete("updated_at") hash.delete("source_file_id") hash.delete("created_at") hash.delete("id") hash.each do |p, v| hash.delete(p) if v.nil? end hash[:scope] = scope hash[:source] = scope.source hash[:parameters] = [] names = [] self.param_names.each do |pname| # We can get the same name multiple times because of how the # db layout works. next if names.include?(pname.name) names << pname.name hash[:parameters] << pname.to_resourceparam(self, scope.source) end obj = Puppet::Parser::Resource.new(hash.delete("type"), hash.delete("title"), hash) # Store the ID, so we can check if we're re-collecting the same resource. obj.collector_id = self.id obj end end