ridley-5.1.1/0000755000004100000410000000000013123557300013035 5ustar www-datawww-dataridley-5.1.1/Thorfile0000644000004100000410000000234313123557300014536 0ustar www-datawww-data# encoding: utf-8 $:.push File.expand_path("../lib", __FILE__) require 'bundler' require 'bundler/setup' require 'buff/ruby_engine' require 'ridley' class Default < Thor extend Buff::RubyEngine unless jruby? require 'thor/rake_compat' include Thor::RakeCompat Bundler::GemHelper.install_tasks desc "build", "Build ridley-#{Ridley::VERSION}.gem into the pkg directory" def build Rake::Task["build"].invoke end desc "install", "Build and install ridley-#{Ridley::VERSION}.gem into system gems" def install Rake::Task["install"].invoke end desc "release", "Create tag v#{Ridley::VERSION} and build and push ridley-#{Ridley::VERSION}.gem to Rubygems" def release Rake::Task["release"].invoke end end class Spec < Thor namespace :spec default_task :all desc "all", "run all tests" def all exec "rspec --color --format=documentation spec" end desc "unit", "run only unit tests" def unit exec "rspec --color --format=documentation spec --tag ~type:acceptance" end desc "acceptance", "run only acceptance tests" def acceptance exec "rspec --color --format=documentation spec --tag type:acceptance" end end end ridley-5.1.1/Rakefile0000644000004100000410000000003413123557300014477 0ustar www-datawww-datarequire 'bundler/gem_tasks' ridley-5.1.1/.gitattributes0000644000004100000410000000012313123557300015724 0ustar www-datawww-data* text=auto *.h text eol=lf *.erb text eol=lf *.txt text eol=lf *.json text eol=lf ridley-5.1.1/Gemfile0000644000004100000410000000214413123557300014331 0ustar www-datawww-datasource 'https://rubygems.org' gemspec platforms :jruby do gem 'jruby-openssl' end group :development do gem 'yard' gem 'spork' gem 'guard', '>= 1.5.0' gem 'guard-yard' gem 'guard-rspec' gem 'guard-spork', platforms: :ruby gem 'coolline' gem 'redcarpet', platforms: :ruby gem 'kramdown', platforms: :jruby require 'rbconfig' if RbConfig::CONFIG['target_os'] =~ /darwin/i gem 'growl', require: false gem 'rb-fsevent', require: false if `uname`.strip == 'Darwin' && `sw_vers -productVersion`.strip >= '10.8' gem 'terminal-notifier-guard', '~> 1.5.3', require: false end rescue Errno::ENOENT elsif RbConfig::CONFIG['target_os'] =~ /linux/i gem 'libnotify', '~> 0.8.0', require: false gem 'rb-inotify', require: false elsif RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i gem 'win32console', require: false gem 'rb-notifu', '>= 0.0.4', require: false gem 'wdm', require: false end end group :test do gem 'thor' gem 'rake', '>= 0.9.2.2' gem 'rspec', '~> 3.0' gem 'fuubar' gem 'json_spec' gem 'webmock' gem 'chef-zero', '~> 1.5.0' end ridley-5.1.1/appveyor.yml0000644000004100000410000000165413123557300015433 0ustar www-datawww-dataversion: "master-{build}" os: Windows Server 2012 R2 platform: - x64 environment: matrix: - ruby_version: "23-x64" - ruby_version: "23" clone_depth: 1 skip_tags: true skip_branch_with_pr: true branches: only: - master install: - SET PATH=C:\Ruby%ruby_version%\bin;%PATH% - ruby --version - gem update --system || gem update --system || gem update --system - gem install bundler --quiet --no-ri --no-rdoc || gem install bundler --quiet --no-ri --no-rdoc || gem install bundler --quiet --no-ri --no-rdoc - update_rubygems - gem --version - bundler --version - SET BUNDLE_IGNORE_CONFIG=true - SET CI=true - SET BUNDLE_WITHOUT=development:guard:maintenance:tools:integration:changelog:docgen:travis:style:omnibus_package:aix:bsd:linux:mac_os_x:solaris build_script: - bundle install || bundle install || bundle install test_script: - SET SPEC_OPTS=--format progress - bundle exec thor spec:all ridley-5.1.1/ridley.gemspec0000644000004100000410000000320113123557300015666 0ustar www-datawww-data# -*- encoding: utf-8 -*- require File.expand_path('../lib/ridley/version', __FILE__) Gem::Specification.new do |s| s.authors = ["Jamie Winsor", "Kyle Allan"] s.email = ["jamie@vialstudios.com", "kallan@riotgames.com"] s.description = %q{A reliable Chef API client with a clean syntax} s.summary = s.description s.homepage = "https://github.com/berkshelf/ridley" s.license = "Apache 2.0" s.files = `git ls-files`.split($\) s.executables = Array.new s.test_files = s.files.grep(%r{^(spec)/}) s.name = "ridley" s.require_paths = ["lib"] s.version = Ridley::VERSION s.required_ruby_version = ">= 2.2" s.add_dependency 'addressable' s.add_dependency 'varia_model', '~> 0.6' s.add_dependency 'buff-config', '~> 2.0' s.add_dependency 'buff-extensions', '~> 2.0' s.add_dependency 'buff-ignore', '~> 1.2' s.add_dependency 'buff-shell_out', '~> 1.0' s.add_dependency 'celluloid', '~> 0.16.0' s.add_dependency 'celluloid-io', '~> 0.16.1' s.add_dependency 'chef-config', '>= 12.5.0' s.add_dependency 'erubis' s.add_dependency 'faraday', '~> 0.9' s.add_dependency 'hashie', '>= 2.0.2', '< 4.0.0' s.add_dependency 'httpclient', '~> 2.7' s.add_dependency 'json', '>= 1.7.7' s.add_dependency 'mixlib-authentication', '>= 1.3.0' s.add_dependency 'retryable', '~> 2.0' s.add_dependency 'semverse', '~> 2.0' s.add_development_dependency 'buff-ruby_engine', '~> 0.1' end ridley-5.1.1/spec/0000755000004100000410000000000013123557300013767 5ustar www-datawww-dataridley-5.1.1/spec/spec_helper.rb0000644000004100000410000000223013123557300016602 0ustar www-datawww-datarequire 'rubygems' require 'bundler' require 'buff/ruby_engine' def setup_rspec require 'rspec' require 'json_spec' require 'webmock/rspec' Dir[File.join(File.expand_path("../../spec/support/**/*.rb", __FILE__))].each { |f| require f } RSpec.configure do |config| config.include Ridley::SpecHelpers config.include Ridley::RSpec::ChefServer config.include JsonSpec::Helpers config.mock_with :rspec config.treat_symbols_as_metadata_keys_with_true_values = true config.filter_run focus: true config.run_all_when_everything_filtered = true config.before(:suite) do WebMock.disable_net_connect!(allow_localhost: true, net_http_connect_on_start: true) Ridley::RSpec::ChefServer.start end config.before(:all) { Ridley.logger = Celluloid.logger = nil } config.before(:each) do Celluloid.shutdown Celluloid.boot clean_tmp_path Ridley::RSpec::ChefServer.server.clear_data end end end if Buff::RubyEngine.mri? && ENV['CI'] != 'true' require 'spork' Spork.prefork do setup_rspec end Spork.each_run do require 'ridley' end else require 'ridley' setup_rspec end ridley-5.1.1/spec/fixtures/0000755000004100000410000000000013123557300015640 5ustar www-datawww-dataridley-5.1.1/spec/fixtures/reset.pem0000644000004100000410000000321713123557300017470 0ustar www-datawww-data-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAyyUMqrTh1IzKOyE0fvXEWC7m0AdMI8/dr9JJMUKtK9vhhP0w rm6m95GoybFM2IRryukFsAxpcir3M1ungTU3Smq4MshhMJ7H9FbvZVfQoknTbCsR w6scg2fBepxT2+fcGRufr8nAh92M3uUkN9bMMTAkt18D4br6035YvdmvHDJERxYq ByA/720AdI9VNSIvw+x8oqsIkXLEdF6dgT9MpG5iWZT66pbFsnNZpRrd4/bFNWBY +13aOqdmjiTL08/EdgQFKMT5qimpos1TuQhA7mwInOjQgzVu9uCDkMiYejaLbUz0 lGyS8y4uxu6z2hA900Jg/z+JJuXymH5QAX3GZQIDAQABAoIBAQCtFXkwbYPI1Nht /wG6du5+8B9K+hy+mppY9wPTy+q+Zs9Ev3Fd/fuXDm1QxBckl9c8AMUO1dR2KPOM t7gFl/DvH/SnmCFvCqp1nijFIUgrLlnMXPn6zG0z7RBlxpKQ2IGohufNIEpBuNwR Ag2U4hgChPGTp4ooJ2cVEh7MS5AupYPDbC62dWEdW68aRTWhh2BCGAWBb6s16yl9 aZ7+OcxW2eeRJVbRfLkLQEDutJZi5TfOEn5QPc86ZgxcCmnvwulnpnhpz6QCkgQt OP/+KRqDhWSDVCFREVT30fUIj1EWvK7NFWASZQxueZStuIvMEKeFebYfrbHxRFzJ UmaxJnWVAoGBAPbKLpeky6ClccBaHHrCgjzakoDfGgyNKDQ9g753lJxB8nn7d9X4 HQpkWpfqAGFRZp1hI2H+VxyUXLh2Ob5OUeTm0OZJll35vycOaQEtfgIScXTcvzn0 16J9eX2YY4wIHEEMh85nKk8BEGgiNP5nuEviHocCeYXoi/Zq3+qj6v63AoGBANK5 4nyi6LBQFs1CUc7Sh7vjtOE3ia7KeRmOr7gS6QhS3iK3Oa8FzBLJ6ETjN2a9Bw8N cF7I/+cr4s7DUJjxdb53D/J6TVSYORNNCUVnpF/uB2LqqdXDYmpO0PvFkXFoYTnJ kaLAN8uCoLKr6JH9tq3DfXIfDIHiZ+BOIvI070fDAoGBAMDyzEDFmGruTyRLj66u +rJnVVmqlKwxhLhrS+CTj74nlVOnt0a0KMhiM65IRqnPwcHUG5zXBPaUTHXwAS93 /nFPwQ37hLPOupPnoVNJZRZrowbyPBQtCJbDMURv64ylHqoBCQDoCd0hANnZvMMX BrFVhfaaibaXXS542r6SD/27AoGAECadHE5kJTdOOBcwK/jo3Fa8g1J9Y/8yvum3 wBT69V9clS6T5j08geglvDnqAh7UzquKBEnFi1NKw+wmXkKLcrivaTdEfApavYb3 AfHKoGue907jC3Y5Mcquq81ds2J7qTEwz1eKLzfo1yjj32ShvrmwALIuhDn1GjUC 6qtx938CgYEApEqvu0nocR1jmVVlLe5uKQBj949dh6NGq0R5Lztz6xufaTYzMC3d AZG9XPPjRqSLs+ylSXJpwHEwoeyLFDaJcO+GgW1/ut4MC2HppOx6aImwDdXMHUWR KYGIFF4AU/IYoBcanAm4s078EH/Oz01B2c7tR2TqabisPgLYe7PXSCw= -----END RSA PRIVATE KEY----- ridley-5.1.1/spec/fixtures/recipe_two.rb0000644000004100000410000000001413123557300020320 0ustar www-datawww-datatestfile tworidley-5.1.1/spec/fixtures/recipe_one.rb0000644000004100000410000000001013123557300020264 0ustar www-datawww-datatestfileridley-5.1.1/spec/fixtures/chefignore0000644000004100000410000000005713123557300017676 0ustar www-datawww-dataREADME.md Guardfile ignores/*.rb ignores/*.erb ridley-5.1.1/spec/fixtures/encrypted_data_bag_secret0000644000004100000410000000125413123557300022731 0ustar www-datawww-dataNTE5C5gzbe3F7fGBlrT/YTuMQuHlxaOWhY81KsgYXJ0mzDsDMFvCpfi4CUXu5M7n/Umgsf8jdHw/IIkJLZcXgk+Ll75kDU/VI5FyRzib0U0SX4JB8JLM7wRFgRpuk3GD27LnYR1APmLncE7R6ZSJc6iaFHcEL2MdR+hv0nhUZPUqITxYHyrYvqNSfroyxldQ/cvnrIMBS8JrpjIsLdYhcgL6mPJiakl4fM36cFk6Wl2Mxw7vOvGXRSR5l+t42pGDhtOjE3os5reLVoWkYoiQ1fpx3NrOdxsVuz17+3jMLBlmni2SGf2wncui2r9PqCrVbUbaCi6aNV1+SRbeh5kxBxjWSzw59BNXtna4vSK6hFPsT6tfXlOi67Q2vwjjAqltAVStGas/VZyU7DRzxMkbnPPtue+7Pajqe/TfSNWA5SX2cHtkG2X3EqZ8ftOa9p+b/VJlUnnnV2ilVfgjCW2q6XXMbC0C5yIbrDZm+aCJyhueA0j+ZHWM4k07OAuB7FRcuJJBs8H2StEx2o22OdAYUBcN5PRGlOAEBemL+sZAztbex2NjjOYV90806UFvSJLPixVWJgDTSA5OvXtNvUQYDYSGRQ/BmH86aA5gJ60AM9vEVB0BfPi946m9D4LZ/2uK6fqq3zVIV1s0EgfYHYUVz0oaf3srofc5YUAP3Ge/VLE=ridley-5.1.1/spec/fixtures/example_cookbook/0000755000004100000410000000000013123557300021161 5ustar www-datawww-dataridley-5.1.1/spec/fixtures/example_cookbook/definitions/0000755000004100000410000000000013123557300023474 5ustar www-datawww-dataridley-5.1.1/spec/fixtures/example_cookbook/definitions/bad_def.rb0000644000004100000410000000015413123557300025365 0ustar www-datawww-data# Definition: bad_def # # Copyright 2012, YOUR_COMPANY_NAME # # All rights reserved - Do Not Redistribute # ridley-5.1.1/spec/fixtures/example_cookbook/files/0000755000004100000410000000000013123557300022263 5ustar www-datawww-dataridley-5.1.1/spec/fixtures/example_cookbook/files/default/0000755000004100000410000000000013123557300023707 5ustar www-datawww-dataridley-5.1.1/spec/fixtures/example_cookbook/files/default/file.h0000644000004100000410000000001713123557300024775 0ustar www-datawww-data# file.h hello ridley-5.1.1/spec/fixtures/example_cookbook/files/ubuntu/0000755000004100000410000000000013123557300023605 5ustar www-datawww-dataridley-5.1.1/spec/fixtures/example_cookbook/files/ubuntu/file.h0000644000004100000410000000002713123557300024674 0ustar www-datawww-data# file.h hello, ubuntu ridley-5.1.1/spec/fixtures/example_cookbook/resources/0000755000004100000410000000000013123557300023173 5ustar www-datawww-dataridley-5.1.1/spec/fixtures/example_cookbook/resources/defresource.rb0000644000004100000410000000015613123557300026030 0ustar www-datawww-data# Resource: defresource # # Copyright 2012, YOUR_COMPANY_NAME # # All rights reserved - Do Not Redistribute # ridley-5.1.1/spec/fixtures/example_cookbook/templates/0000755000004100000410000000000013123557300023157 5ustar www-datawww-dataridley-5.1.1/spec/fixtures/example_cookbook/templates/default/0000755000004100000410000000000013123557300024603 5ustar www-datawww-dataridley-5.1.1/spec/fixtures/example_cookbook/templates/default/temp.txt.erb0000644000004100000410000000001613123557300027055 0ustar www-datawww-data<%= 'hello' %>ridley-5.1.1/spec/fixtures/example_cookbook/metadata.rb0000644000004100000410000000105513123557300023267 0ustar www-datawww-dataname "example_cookbook" maintainer "Jamie Winsor" maintainer_email "jamie@vialstudios.com" license "Apache 2.0" description "Installs/Configures example_cookbook" long_description IO.read(File.join(File.dirname(__FILE__), "README.md")) version "0.1.0" attribute "example_cookbook/test", :display_name => "Test", :description => "Test Attribute", :choice => [ "test1", "test2" ], :type => "string", :required => "recommended", :recipes => [ 'example_cookbook::default' ], :default => "test1" ridley-5.1.1/spec/fixtures/example_cookbook/providers/0000755000004100000410000000000013123557300023176 5ustar www-datawww-dataridley-5.1.1/spec/fixtures/example_cookbook/providers/defprovider.rb0000644000004100000410000000015613123557300026036 0ustar www-datawww-data# Provider: defprovider # # Copyright 2012, YOUR_COMPANY_NAME # # All rights reserved - Do Not Redistribute # ridley-5.1.1/spec/fixtures/example_cookbook/attributes/0000755000004100000410000000000013123557300023347 5ustar www-datawww-dataridley-5.1.1/spec/fixtures/example_cookbook/attributes/default.rb0000644000004100000410000000015413123557300025320 0ustar www-datawww-data# Attribute:: default # # Copyright 2012, YOUR_COMPANY_NAME # # All rights reserved - Do Not Redistribute # ridley-5.1.1/spec/fixtures/example_cookbook/libraries/0000755000004100000410000000000013123557300023135 5ustar www-datawww-dataridley-5.1.1/spec/fixtures/example_cookbook/libraries/my_lib.rb0000644000004100000410000000015013123557300024731 0ustar www-datawww-data# Library: my_lib # # Copyright 2012, YOUR_COMPANY_NAME # # All rights reserved - Do Not Redistribute # ridley-5.1.1/spec/fixtures/example_cookbook/README.md0000644000004100000410000000012713123557300022440 0ustar www-datawww-dataDescription =========== Requirements ============ Attributes ========== Usage ===== ridley-5.1.1/spec/fixtures/example_cookbook/Guardfile0000644000004100000410000000004713123557300023007 0ustar www-datawww-data# This should be ignored by chefignore ridley-5.1.1/spec/fixtures/example_cookbook/ignores/0000755000004100000410000000000013123557300022627 5ustar www-datawww-dataridley-5.1.1/spec/fixtures/example_cookbook/ignores/magic.rb0000644000004100000410000000011713123557300024233 0ustar www-datawww-data# I should never see this file # It has bad ruby in it... this is not { valid ridley-5.1.1/spec/fixtures/example_cookbook/ignores/magic.erb0000644000004100000410000000012613123557300024400 0ustar www-datawww-data# I should never see this file # It has bad ruby in it... <%= this is not { valid %> ridley-5.1.1/spec/fixtures/example_cookbook/ignores/ok.txt0000644000004100000410000000003113123557300023773 0ustar www-datawww-dataThis one is okay, though ridley-5.1.1/spec/fixtures/example_cookbook/recipes/0000755000004100000410000000000013123557300022613 5ustar www-datawww-dataridley-5.1.1/spec/fixtures/example_cookbook/recipes/default.rb0000644000004100000410000000015013123557300024560 0ustar www-datawww-data# Recipe: default # # Copyright 2012, YOUR_COMPANY_NAME # # All rights reserved - Do Not Redistribute # ridley-5.1.1/spec/unit/0000755000004100000410000000000013123557300014746 5ustar www-datawww-dataridley-5.1.1/spec/unit/ridley_spec.rb0000644000004100000410000000613313123557300017600 0ustar www-datawww-datarequire 'spec_helper' describe Ridley do let(:config) { double("config") } describe "ClassMethods" do subject { Ridley } describe "::new" do it "creates a new Ridley::Connection" do client = double('client') expect(Ridley::Client).to receive(:new).with(config).and_return(client) expect(subject.new(config)).to eql(client) end end describe "from_chef_config" do let(:chef_config) do %( node_name "username" client_key "username.pem" validation_client_name "validator" validation_key "validator.pem" chef_server_url "https://api.opscode.com" cache_options(:path => "~/.chef/checksums") syntax_check_cache_path "/foo/bar" ssl_verify_mode :verify_none ) end let(:client) { double('client') } let(:path) { tmp_path.join('config.rb').to_s } before do allow(Ridley::Client).to receive(:new).and_return(client) File.open(path, 'w') { |f| f.write(chef_config) } end it "creates a Ridley connection from the Chef config" do expect(Ridley::Client).to receive(:new).with(hash_including( client_key: 'username.pem', client_name: 'username', validator_client: 'validator', validator_path: 'validator.pem', server_url: 'https://api.opscode.com', syntax_check_cache_path: "/foo/bar", cache_options: { path: "~/.chef/checksums" }, ssl: {verify: false}, )).and_return(nil) subject.from_chef_config(path) end it "allows the user to override attributes" do expect(Ridley::Client).to receive(:new).with(hash_including( client_key: 'bacon.pem', client_name: 'bacon', validator_client: 'validator', validator_path: 'validator.pem', server_url: 'https://api.opscode.com', syntax_check_cache_path: "/foo/bar", cache_options: { path: "~/.chef/checksums" }, ssl: {verify: false}, )) subject.from_chef_config(path, client_key: 'bacon.pem', client_name: 'bacon') end context "when the config location isn't explicitly specified" do before do dot_chef = tmp_path.join('.chef') knife_rb = dot_chef.join('knife.rb') FileUtils.mkdir_p(dot_chef) File.open(knife_rb, 'w') { |f| f.write(chef_config) } end it "does a knife.rb search" do expect(Ridley::Client).to receive(:new).with(hash_including( client_key: 'username.pem', client_name: 'username', validator_client: 'validator', validator_path: 'validator.pem', server_url: 'https://api.opscode.com', syntax_check_cache_path: "/foo/bar", cache_options: { path: "~/.chef/checksums" }, )).and_return(nil) Dir.chdir(tmp_path) do ENV['PWD'] = Dir.pwd subject.from_chef_config end end end end end end ridley-5.1.1/spec/unit/ridley/0000755000004100000410000000000013123557300016236 5ustar www-datawww-dataridley-5.1.1/spec/unit/ridley/middleware/0000755000004100000410000000000013123557300020353 5ustar www-datawww-dataridley-5.1.1/spec/unit/ridley/middleware/chef_response_spec.rb0000644000004100000410000001336513123557300024545 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::Middleware::ChefResponse do let(:server_url) { "https://api.opscode.com/organizations/vialstudios/" } describe "ClassMethods" do subject { Ridley::Middleware::ChefResponse } describe "::success?" do let(:env) { double('env') } it "returns true if response status between 200 and 210" do (200..210).each do |code| expect(env).to receive(:[]).with(:status).and_return(code) expect(subject.success?(env)).to be_truthy end end it "returns false if response status is in the 300 range" do (300..399).each do |code| expect(env).to receive(:[]).with(:status).and_return(code) expect(subject.success?(env)).to be_falsey end end it "returns false if response status is in the 400 range" do (400..499).each do |code| expect(env).to receive(:[]).with(:status).and_return(code) expect(subject.success?(env)).to be_falsey end end it "returns false if response status is in the 500 range" do (500..599).each do |code| expect(env).to receive(:[]).with(:status).and_return(code) expect(subject.success?(env)).to be_falsey end end end end subject do Faraday.new(server_url) do |conn| conn.response :chef_response conn.response :parse_json conn.adapter Faraday.default_adapter end end let(:chef_ok) do { status: 200, body: generate_normalized_json(name: "reset-role", description: "a new role"), headers: { content_type: "application/json; charset=utf8" } } end let(:chef_bad_request) do { status: 400, body: generate_normalized_json(error: "400 - Bad Request: Valid X-CHEF-VERSION header is required."), headers: { content_type: "application/json; charset=utf8" } } end let(:chef_unauthorized) do { status: 401, body: generate_normalized_json(error: "401 - Unauthorized. You must properly authenticate your API requests!"), headers: { content_type: "application/json; charset=utf8" } } end let(:chef_forbidden) do { status: 403, body: generate_normalized_json(error: "403 - Forbidden."), headers: { content_type: "application/json; charset=utf8" } } end let(:chef_not_found) do { status: 404, body: generate_normalized_json(error: [ "No routes match the request: /organizations/vialstudios/cookbookss/not_existant" ]), headers: { content_type: "application/json; charset=utf8" } } end let(:chef_conflict) do { status: 409, body: generate_normalized_json(error: "409 - Conflict."), headers: { content_type: "application/json; charset=utf8" } } end describe "400 Bad Request" do before(:each) do stub_request(:get, File.join(server_url, 'cookbooks')).to_return(chef_bad_request) end it "raises a Ridley::Errors::HTTPBadRequest" do expect { subject.get('cookbooks') }.to raise_error(Ridley::Errors::HTTPBadRequest) end it "should have the body of the response as the error's message" do expect { subject.get('cookbooks') }.to raise_error("errors: '400 - Bad Request: Valid X-CHEF-VERSION header is required.'") end end describe "401 Unauthorized" do before(:each) do stub_request(:get, File.join(server_url, 'cookbooks')).to_return(chef_unauthorized) end it "raises a Ridley::Errors::HTTPUnauthorized" do expect { subject.get('cookbooks') }.to raise_error(Ridley::Errors::HTTPUnauthorized) end it "should have the body of the response as the error's message" do expect { subject.get('cookbooks') }.to raise_error("errors: '401 - Unauthorized. You must properly authenticate your API requests!'") end end describe "403 Forbidden" do before(:each) do stub_request(:get, File.join(server_url, 'cookbooks')).to_return(chef_forbidden) end it "raises a Ridley::Errors::HTTPForbidden" do expect { subject.get('cookbooks') }.to raise_error(Ridley::Errors::HTTPForbidden) end it "should have the body of the response as the error's message" do expect { subject.get('cookbooks') }.to raise_error("errors: '403 - Forbidden.'") end end describe "404 Not Found" do before(:each) do stub_request(:get, File.join(server_url, 'not_existant_route')).to_return(chef_not_found) end it "raises a Ridley::Errors::HTTPNotFound" do expect { subject.get('not_existant_route') }.to raise_error(Ridley::Errors::HTTPNotFound) end it "should have the body of the response as the error's message" do expect { subject.get('not_existant_route') }.to raise_error(Ridley::Errors::HTTPNotFound, "errors: 'No routes match the request: /organizations/vialstudios/cookbookss/not_existant'") end end describe "409 Conflict" do before(:each) do stub_request(:get, File.join(server_url, 'cookbooks')).to_return(chef_conflict) end it "raises a Ridley::Errors::HTTPForbidden" do expect { subject.get('cookbooks') }.to raise_error(Ridley::Errors::HTTPConflict) end it "should have the body of the response as the error's message" do expect { subject.get('cookbooks') }.to raise_error("errors: '409 - Conflict.'") end end describe "200 OK" do before(:each) do stub_request(:get, File.join(server_url, 'roles/reset')).to_return(chef_ok) end it "returns a body containing a hash" do expect(subject.get('roles/reset').env[:body]).to be_a(Hash) end end end ridley-5.1.1/spec/unit/ridley/middleware/chef_auth_spec.rb0000644000004100000410000000241213123557300023637 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::Middleware::ChefAuth do let(:server_url) { "https://api.opscode.com/organizations/vialstudios/" } describe "ClassMethods" do subject { described_class } describe "#authentication_headers" do let(:client_name) { "reset" } let(:client_key) { fixtures_path.join("reset.pem") } it "returns a Hash of authentication headers" do options = { http_method: "GET", host: "https://api.opscode.com", path: "/something.file" } expect(subject.authentication_headers(client_name, client_key, options)).to be_a(Hash) end context "when the :client_key is an actual key" do let(:client_key) { File.read(fixtures_path.join("reset.pem")) } it "returns a Hash of authentication headers" do options = { http_method: "GET", host: "https://api.opscode.com", path: "/something.file" } expect(subject.authentication_headers(client_name, client_key, options)).to be_a(Hash) end end end end subject do Faraday.new(server_url) do |conn| conn.request :chef_auth, "reset", "/Users/reset/.chef/reset.pem" conn.adapter Faraday.default_adapter end end end ridley-5.1.1/spec/unit/ridley/middleware/parse_json_spec.rb0000644000004100000410000000463613123557300024066 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::Middleware::ParseJson do let(:server_url) { "https://api.opscode.com/organizations/vialstudios/" } describe "ClassMethods" do subject { Ridley::Middleware::ParseJson } describe "::response_type" do it "returns the first element of the response content-type" do env = double('env') allow(env).to receive(:[]).with(:response_headers).and_return( 'content-type' => 'text/html; charset=utf-8' ) expect(subject.response_type(env)).to eql("text/html") end end describe "::json_response?" do it "returns true if the value of content-type includes 'application/json' and the body looks like JSON" do env = double('env') allow(env).to receive(:[]).with(:response_headers).and_return( 'content-type' => 'application/json; charset=utf8' ) expect(subject).to receive(:looks_like_json?).with(env).and_return(true) expect(subject.json_response?(env)).to be_truthy end it "returns false if the value of content-type includes 'application/json' but the body does not look like JSON" do env = double('env') allow(env).to receive(:[]).with(:response_headers).and_return( 'content-type' => 'application/json; charset=utf8' ) expect(subject).to receive(:looks_like_json?).with(env).and_return(false) expect(subject.json_response?(env)).to be_falsey end it "returns false if the value of content-type does not include 'application/json'" do env = double('env') allow(env).to receive(:[]).with(:response_headers).and_return( 'content-type' => 'text/plain' ) expect(subject.json_response?(env)).to be_falsey end end describe "::looks_like_json?" do let(:env) { double('env') } it "returns true if the given string contains JSON brackets" do allow(env).to receive(:[]).with(:body).and_return("{\"name\":\"jamie\"}") expect(subject.looks_like_json?(env)).to be_truthy end it "returns false if the given string does not contain JSON brackets" do allow(env).to receive(:[]).with(:body).and_return("name") expect(subject.looks_like_json?(env)).to be_falsey end end end subject do Faraday.new(server_url) do |conn| conn.response :json conn.adapter Faraday.default_adapter end end end ridley-5.1.1/spec/unit/ridley/resource_spec.rb0000644000004100000410000001022013123557300021417 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::Resource do let(:representation) do Class.new(Ridley::ChefObject) do set_chef_id "id" set_chef_type "thing" set_chef_json_class "Chef::Thing" end end let(:resource_class) do Class.new(Ridley::Resource) do set_resource_path "rspecs" end end describe "ClassMethods" do subject { resource_class } describe "::set_resource_path" do it "sets the resource_path attr on the class" do subject.set_resource_path("environments") expect(subject.resource_path).to eql("environments") end end describe "::resource_path" do context "when not explicitly set" do before { subject.set_resource_path(nil) } it "returns the representation's chef type" do expect(subject.resource_path).to eql(representation.chef_type) end end context "when explicitly set" do let(:set_path) { "hello" } before { subject.set_resource_path(set_path) } it "returns the set value" do expect(subject.resource_path).to eql(set_path) end end end end let(:connection) { double('chef-connection') } let(:response) { double('chef-response', body: Hash.new) } let(:resource_json) { '{"some":"valid json"}' } subject { resource_class.new(double('registry')) } before do resource_class.stub(representation: representation) subject.stub(connection: connection) end describe "::from_file" do it "reads the file and calls ::from_json with contents" do File.stub(:read) { resource_json } subject.should_receive(:from_json).with(resource_json) subject.from_file('/bogus/filename.json') end end describe "::from_json" do it "parses the argument and calls ::new with newly built hash" do hashed_json = JSON.parse(resource_json) subject.should_receive(:new).with(hashed_json).and_return representation subject.from_json(resource_json) end end describe "::all" do it "sends GET to /{resource_path}" do connection.should_receive(:get).with(subject.class.resource_path).and_return(response) subject.all end end describe "::find" do let(:id) { "some_id" } it "sends GET to /{resource_path}/{id} where {id} is the given ID" do connection.should_receive(:get).with("#{subject.class.resource_path}/#{id}").and_return(response) subject.find(id) end context "when the resource is not found" do before do connection.should_receive(:get).with("#{subject.class.resource_path}/#{id}"). and_raise(Ridley::Errors::HTTPNotFound.new({})) end it "returns nil" do expect(subject.find(id)).to be_nil end end end describe "::create" do let(:attrs) do { first_name: "jamie", last_name: "winsor" } end it "sends a post request to the given client using the includer's resource_path" do connection.should_receive(:post).with(subject.class.resource_path, duck_type(:to_json)).and_return(response) subject.create(attrs) end end describe "::delete" do it "sends a delete request to the given client using the includer's resource_path for the given string" do connection.should_receive(:delete).with("#{subject.class.resource_path}/ridley-test").and_return(response) subject.delete("ridley-test") end it "accepts an object that responds to 'chef_id'" do object = double("obj") object.stub(:chef_id) { "hello" } connection.should_receive(:delete).with("#{subject.class.resource_path}/#{object.chef_id}").and_return(response) subject.delete( object) end end describe "::delete_all" do it "sends a delete request for every object in the collection" do skip end end describe "::update" do it "sends a put request to the given client using the includer's resource_path with the given object" do object = subject.new(name: "hello") connection.should_receive(:put). with("#{subject.class.resource_path}/#{object.chef_id}", duck_type(:to_json)).and_return(response) subject.update(object) end end end ridley-5.1.1/spec/unit/ridley/mixins/0000755000004100000410000000000013123557300017545 5ustar www-datawww-dataridley-5.1.1/spec/unit/ridley/mixins/from_file_spec.rb0000644000004100000410000000173513123557300023054 0ustar www-datawww-datarequire 'spec_helper' module Ridley describe Mixin::FromFile do describe '.from_file' do context 'when there is bad Ruby in the file' do let(:instance) { Class.new { include Ridley::Mixin::FromFile }.new } before do allow(File).to receive(:exists?).and_return(true) allow(File).to receive(:readable?).and_return(true) allow(IO).to receive(:read).and_return('invalid Ruby code') end it 'raises a FromFileParserError' do expect { instance.from_file('/path') }.to raise_error(Errors::FromFileParserError) end it 'includes the backtrace from the original error' do expect { instance.from_file('/path') }.to raise_error { |error| expect(error.message).to include("undefined local variable or method `code' for") expect(error.backtrace).to include("/path:1:in `block in from_file'") } end end end end end ridley-5.1.1/spec/unit/ridley/chef_objects/0000755000004100000410000000000013123557300020654 5ustar www-datawww-dataridley-5.1.1/spec/unit/ridley/chef_objects/client_object_spec.rb0000644000004100000410000000022413123557300025015 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::ClientObject do describe "#to_json" do skip end describe "#regenerate_key" do skip end end ridley-5.1.1/spec/unit/ridley/chef_objects/environment_object_spec.rb0000644000004100000410000000765013123557300026115 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::EnvironmentObject do subject { described_class.new(double('registry')) } describe "#set_override_attribute" do it "sets an override node attribute at the nested path" do subject.set_override_attribute('deep.nested.item', true) expect(subject.override_attributes).to have_key("deep") expect(subject.override_attributes["deep"]).to have_key("nested") expect(subject.override_attributes["deep"]["nested"]).to have_key("item") expect(subject.override_attributes["deep"]["nested"]["item"]).to be_truthy end context "when the override attribute is already set" do it "test" do subject.override_attributes = { deep: { nested: { item: false } } } subject.set_override_attribute('deep.nested.item', true) expect(subject.override_attributes["deep"]["nested"]["item"]).to be_truthy end end end describe "#set_default_attribute" do it "sets an override node attribute at the nested path" do subject.set_default_attribute('deep.nested.item', true) expect(subject.default_attributes).to have_key("deep") expect(subject.default_attributes["deep"]).to have_key("nested") expect(subject.default_attributes["deep"]["nested"]).to have_key("item") expect(subject.default_attributes["deep"]["nested"]["item"]).to be_truthy end context "when the override attribute is already set" do it "test" do subject.default_attributes = { deep: { nested: { item: false } } } subject.set_default_attribute('deep.nested.item', true) expect(subject.default_attributes["deep"]["nested"]["item"]).to be_truthy end end shared_examples_for "attribute deleter" do let(:precedence) { raise "You must provide the precedence level (let(:precedence) { \"default\" } in the shared example context" } let(:delete_attribute) { subject.send(:"delete_#{precedence}_attribute", delete_attribute_key) } let(:set_attribute_value) { true } let(:attributes) { { "hello" => { "world" => set_attribute_value } } } let(:delete_attribute_key) { "hello.world" } before do subject.send(:"#{precedence}_attributes=", attributes) end it "removes the attribute" do delete_attribute expect(subject.send(:"#{precedence}_attributes")[:hello][:world]).to be_nil end context "when the attribute does not exist" do let(:delete_attribute_key) { "not.existing" } it "does not delete anything" do delete_attribute expect(subject.send(:"#{precedence}_attributes")[:hello][:world]).to eq(set_attribute_value) end end context "when an internal hash is nil" do let(:delete_attribute_key) { "never.not.existing" } before do subject.send(:"#{precedence}_attributes=", Hash.new) end it "does not delete anything" do delete_attribute expect(subject.send(:"#{precedence}_attributes")).to be_empty end end ["string", true, :symbol, ["array"], Object.new].each do |nonattrs| context "when the attribute chain is partially set, interrupted by a #{nonattrs.class}" do let(:attributes) { { "hello" => set_attribute_value } } let(:set_attribute_value) { nonattrs } it "leaves the attributes unchanged" do expect(subject.send(:"unset_#{precedence}_attribute", delete_attribute_key).to_hash).to eq(attributes) end end end end describe "#delete_default_attribute" do it_behaves_like "attribute deleter" do let(:precedence) { "default" } end end describe "#delete_override_attribute" do it_behaves_like "attribute deleter" do let(:precedence) { "override" } end end end end ridley-5.1.1/spec/unit/ridley/chef_objects/data_bag_object_spec.rb0000644000004100000410000000054613123557300025270 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::DataBagObject do let(:item_resource) { double('item-resource') } let(:resource) { double('db-resource', item_resource: item_resource) } subject { described_class.new(resource) } describe '#item' do subject { super().item } it { is_expected.to be_a(Ridley::DataBagObject::DataBagItemProxy) } end end ridley-5.1.1/spec/unit/ridley/chef_objects/node_object_spec.rb0000644000004100000410000002124213123557300024467 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::NodeObject do let(:resource) { double('resource') } let(:instance) { described_class.new(resource) } subject { instance } describe "#chef_attributes" do subject { instance.chef_attributes } it "returns a Hashie::Mash" do expect(subject).to be_a(Hashie::Mash) end it "includes default attributes" do instance.default = default = { "default" => { "one" => "val", "two" => "val" } } expect(subject.to_hash).to include(default) end it "includes normal attributes" do instance.normal = normal = { "normal" => { "one" => "val", "two" => "val" } } expect(subject.to_hash).to include(normal) end it "includes override attributes" do instance.override = override = { "override" => { "one" => "val", "two" => "val" } } expect(subject.to_hash).to include(override) end it "includes automatic attributes" do instance.automatic = automatic = { "automatic" => { "one" => "val", "two" => "val" } } expect(subject.to_hash).to include(automatic) end it "overrides default attributes with normal attributes" do instance.default = default = { one: "old", two: "old" } instance.normal = normal = { one: "new" } expect(subject[:one]).to eql("new") expect(subject[:two]).to eql("old") end it "overrides normal attributes with override attributes" do instance.normal = normal = { one: "old", two: "old" } instance.override = override = { one: "new" } expect(subject[:one]).to eql("new") expect(subject[:two]).to eql("old") end it "overrides override attributes with automatic attributes" do instance.override = override = { one: "old", two: "old" } instance.automatic = automatic = { one: "new" } expect(subject[:one]).to eql("new") expect(subject[:two]).to eql("old") end end describe "#set_chef_attribute" do it "sets a normal node attribute at the nested path" do subject.set_chef_attribute('deep.nested.item', true) expect(subject.normal).to have_key("deep") expect(subject.normal["deep"]).to have_key("nested") expect(subject.normal["deep"]["nested"]).to have_key("item") expect(subject.normal["deep"]["nested"]["item"]).to be_truthy end context "when the normal attribute is already set" do it "test" do subject.normal = { deep: { nested: { item: false } } } subject.set_chef_attribute('deep.nested.item', true) expect(subject.normal["deep"]["nested"]["item"]).to be_truthy end end end describe "#unset_chef_attribute" do context "when the attribute is set" do before do subject.normal = { foo: { bar: { baz: true } } } end it "unsets a normal node attribute at the nested path" do subject.unset_chef_attribute("foo.bar.baz") expect(subject.normal[:foo][:bar][:baz]).to be_nil end end ["string", true, :symbol, ["array"], Object.new].each do |nonattrs| context "when the attribute chain is partially set, interrupted by a #{nonattrs.class}" do let(:attributes) { { 'foo' => { 'bar' => nonattrs } } } before do subject.normal = attributes end it "leaves the attributes unchanged" do expect(subject.unset_chef_attribute("foo.bar.baz").to_hash).to eq(attributes) end end end context "when the attribute is not set" do let(:attributes) { { 'bizz' => { 'bar' => { 'baz' => true } } } } before do subject.normal = attributes end it "leaves the attributes unchanged" do expect(subject.unset_chef_attribute("foo.bar.baz").to_hash).to eq(attributes) end end end describe "#cloud?" do it "returns true if the cloud automatic attribute is set" do subject.automatic = { "cloud" => Hash.new } expect(subject.cloud?).to be_truthy end it "returns false if the cloud automatic attribute is not set" do subject.automatic.delete(:cloud) expect(subject.cloud?).to be_falsey end end describe "#eucalyptus?" do it "returns true if the node is a cloud node using the eucalyptus provider" do subject.automatic = { "cloud" => { "provider" => "eucalyptus" } } expect(subject.eucalyptus?).to be_truthy end it "returns false if the node is not a cloud node" do subject.automatic.delete(:cloud) expect(subject.eucalyptus?).to be_falsey end it "returns false if the node is a cloud node but not using the eucalyptus provider" do subject.automatic = { "cloud" => { "provider" => "ec2" } } expect(subject.eucalyptus?).to be_falsey end end describe "#ec2?" do it "returns true if the node is a cloud node using the ec2 provider" do subject.automatic = { "cloud" => { "provider" => "ec2" } } expect(subject.ec2?).to be_truthy end it "returns false if the node is not a cloud node" do subject.automatic.delete(:cloud) expect(subject.ec2?).to be_falsey end it "returns false if the node is a cloud node but not using the ec2 provider" do subject.automatic = { "cloud" => { "provider" => "rackspace" } } expect(subject.ec2?).to be_falsey end end describe "#rackspace?" do it "returns true if the node is a cloud node using the rackspace provider" do subject.automatic = { "cloud" => { "provider" => "rackspace" } } expect(subject.rackspace?).to be_truthy end it "returns false if the node is not a cloud node" do subject.automatic.delete(:cloud) expect(subject.rackspace?).to be_falsey end it "returns false if the node is a cloud node but not using the rackspace provider" do subject.automatic = { "cloud" => { "provider" => "ec2" } } expect(subject.rackspace?).to be_falsey end end describe "#cloud_provider" do it "returns the cloud provider if the node is a cloud node" do subject.automatic = { "cloud" => { "provider" => "ec2" } } expect(subject.cloud_provider).to eql("ec2") end it "returns nil if the node is not a cloud node" do subject.automatic.delete(:cloud) expect(subject.cloud_provider).to be_nil end end describe "#public_ipv4" do it "returns the public ipv4 address if the node is a cloud node" do subject.automatic = { "cloud" => { "provider" => "ec2", "public_ipv4" => "10.0.0.1" } } expect(subject.public_ipv4).to eql("10.0.0.1") end it "returns the ipaddress if the node is not a cloud node" do subject.automatic = { "ipaddress" => "192.168.1.1" } subject.automatic.delete(:cloud) expect(subject.public_ipv4).to eql("192.168.1.1") end end describe "#public_hostname" do it "returns the public hostname if the node is a cloud node" do subject.automatic = { "cloud" => { "public_hostname" => "reset.cloud.riotgames.com" } } expect(subject.public_hostname).to eql("reset.cloud.riotgames.com") end it "returns the FQDN if the node is not a cloud node" do subject.automatic = { "fqdn" => "reset.internal.riotgames.com" } subject.automatic.delete(:cloud) expect(subject.public_hostname).to eql("reset.internal.riotgames.com") end end describe "#merge_data" do before(:each) { subject.name = "reset.riotgames.com" } it "appends items to the run_list" do subject.merge_data(run_list: ["cook::one", "cook::two"]) expect(subject.run_list).to match_array(["cook::one", "cook::two"]) end it "ensures the run_list is unique if identical items are given" do subject.run_list = [ "cook::one" ] subject.merge_data(run_list: ["cook::one", "cook::two"]) expect(subject.run_list).to match_array(["cook::one", "cook::two"]) end it "deep merges attributes into the normal attributes" do subject.normal = { one: { two: { four: :deep } } } subject.merge_data(attributes: { one: { two: { three: :deep } } }) expect(subject.normal[:one][:two]).to have_key(:four) expect(subject.normal[:one][:two][:four]).to eql(:deep) expect(subject.normal[:one][:two]).to have_key(:three) expect(subject.normal[:one][:two][:three]).to eql(:deep) end end end ridley-5.1.1/spec/unit/ridley/chef_objects/sandbox_object_spec.rb0000644000004100000410000000355713123557300025211 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::SandboxObject do let(:resource) { double('chef-resource') } subject do described_class.new(double('registry'), "uri" => "https://api.opscode.com/organizations/vialstudios/sandboxes/bd091b150b0a4578b97771af6abf3e05", "checksums" => { "385ea5490c86570c7de71070bce9384a" => { "url" => "https://s3.amazonaws.com/opscode-platform-production-data/organization", "needs_upload" => true }, "f6f73175e979bd90af6184ec277f760c" => { "url" => "https://s3.amazonaws.com/opscode-platform-production-data/organization", "needs_upload" => true }, "2e03dd7e5b2e6c8eab1cf41ac61396d5" => { "url" => "https://s3.amazonaws.com/opscode-platform-production-data/organization", "needs_upload" => true }, }, "sandbox_id" => "bd091b150b0a4578b97771af6abf3e05" ) end before { allow(subject).to receive_messages(resource: resource) } describe "#checksums" do skip end describe "#commit" do let(:response) { { is_completed: nil} } before { expect(resource).to receive(:commit).with(subject).and_return(response) } context "when the commit is successful" do before { response[:is_completed] = true } it "has an 'is_completed' value of true" do subject.commit expect(subject.is_completed).to be_truthy end end context "when the commit is a failure" do before { response[:is_completed] = false } it "has an 'is_completed' value of false" do subject.commit expect(subject.is_completed).to be_falsey end end end describe "#upload" do it "delegates to resource#upload" do checksums = double('checksums') expect(resource).to receive(:upload).with(subject, checksums) subject.upload(checksums) end end end ridley-5.1.1/spec/unit/ridley/chef_objects/data_bag_item_object_spec.rb0000644000004100000410000000515713123557300026311 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::DataBagItemObject do let(:resource) { double('chef-resource') } let(:data_bag) { double('data-bag') } subject { described_class.new(resource, data_bag) } describe "#from_hash" do context "when JSON has a 'raw_data' field" do let(:response) do { "name" => "data_bag_item_ridley-test_appconfig", "raw_data" => { "id" => "appconfig", "host" => "host.local" }, "json_class" => "Chef::DataBagItem", "data_bag" => "ridley-test", "chef_type" => "data_bag_item" } end it "returns a new object from attributes in the 'raw_data' field" do expect(subject.from_hash(response).attributes).to eql(response["raw_data"]) end end context "when JSON does not contain a 'raw_data' field" do let(:response) do { "id" => "appconfig", "host" => "host.local" } end it "returns a new object from the hash" do expect(subject.from_hash(response).attributes).to eql(response) end end end describe "#decrypt" do before(:each) do allow(resource).to receive_messages(encrypted_data_bag_secret: File.read(fixtures_path.join("encrypted_data_bag_secret").to_s)) end it "decrypts an encrypted v0 value" do subject.attributes[:test] = "Xk0E8lV9r4BhZzcg4wal0X4w9ZexN3azxMjZ9r1MCZc=" subject.decrypt expect(subject.attributes[:test][:database][:username]).to eq("test") end it "decrypts an encrypted v1 value" do subject.attributes[:password] = Hashie::Mash.new subject.attributes[:password][:version] = 1 subject.attributes[:password][:cipher] = "aes-256-cbc" subject.attributes[:password][:encrypted_data] = "zG+tTjtwOWA4vEYDoUwPYreXLZ1pFyKoWDGezEejmKs=" subject.attributes[:password][:iv] = "URVhHxv/ZrnABJBvl82qsg==" subject.decrypt expect(subject.attributes[:password]).to eq("password123") end it "does not decrypt the id field" do id = "dbi_id" subject.attributes[:id] = id subject.decrypt expect(subject.attributes[:id]).to eq(id) end end describe "#decrypt_value" do context "when no encrypted_data_bag_secret has been configured" do before do allow(resource).to receive_messages(encrypted_data_bag_secret: nil) end it "raises an EncryptedDataBagSecretNotSet error" do expect { subject.decrypt_value("Xk0E8lV9r4BhZzcg4wal0X4w9ZexN3azxMjZ9r1MCZc=") }.to raise_error(Ridley::Errors::EncryptedDataBagSecretNotSet) end end end end ridley-5.1.1/spec/unit/ridley/chef_objects/cookbook_object_spec.rb0000644000004100000410000000621013123557300025346 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::CookbookObject do let(:connection) { double('connection') } let(:resource) { double('resource', connection: connection) } subject { described_class.new(resource) } describe "#download" do it "downloads each file" do allow(subject).to receive(:manifest) do { resources: [], providers: [], recipes: [ { checksum: "aa3505d3eb8ce328ea84a4333df05b07", name: "default.rb", path: "recipes/default.rb", specificity: "default", url: "https://chef.lax1.riotgames.com/organizations/reset/cookbooks/ohai/1.0.2/files/aa3505d3eb8ce328ea84a4333df05b07" } ], definitions: [], libraries: [], attributes: [], files: [ { checksum: "85bc3bb921efade3f2566a668ab4b639", name: "README", path: "files/default/plugins/README", specificity: "plugins", url: "https://chef.lax1.riotgames.com/organizations/reset/cookbooks/ohai/1.0.2/files/85bc3bb921efade3f2566a668ab4b639" } ], templates: [], root_files: [] } end expect(subject).to receive(:download_file).with(:recipes, "recipes/default.rb", anything) expect(subject).to receive(:download_file).with(:files, "files/default/plugins/README", anything) subject.download end end describe "#download_file" do let(:destination) { tmp_path.join('fake.file').to_s } before(:each) do allow(subject).to receive(:root_files) { [ { path: 'metadata.rb', url: "http://test.it/file" } ] } end it "downloads the file from the file's url" do expect(connection).to receive(:stream).with("http://test.it/file", destination) subject.download_file(:root_file, "metadata.rb", destination) end context "when given an unknown filetype" do it "raises an UnknownCookbookFileType error" do expect { subject.download_file(:not_existant, "default.rb", destination) }.to raise_error(Ridley::Errors::UnknownCookbookFileType) end end context "when the cookbook doesn't have the specified file" do before(:each) do allow(subject).to receive(:root_files) { Array.new } end it "returns nil" do expect(subject.download_file(:root_file, "metadata.rb", destination)).to be_nil end end end describe "#manifest" do it "returns a Hash" do expect(subject.manifest).to be_a(Hash) end it "has a key for each item in FILE_TYPES" do expect(subject.manifest.keys).to match_array(described_class::FILE_TYPES) end it "contains an empty array for each key" do expect(subject.manifest).to each be_a(Array) expect(subject.manifest.values).to each be_empty end end describe "#reload" do it "returns the updated self" do other = subject.dup other.version = "1.2.3" expect(resource).to receive(:find).with(subject, subject.version).and_return(other) expect(subject.reload).to eq(other) end end end ridley-5.1.1/spec/unit/ridley/chef_objects/role_object_spec.rb0000644000004100000410000000346513123557300024512 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::RoleObject do subject { described_class.new(double('registry')) } describe "#set_override_attribute" do it "sets an override node attribute at the nested path" do subject.set_override_attribute('deep.nested.item', true) expect(subject.override_attributes).to have_key("deep") expect(subject.override_attributes["deep"]).to have_key("nested") expect(subject.override_attributes["deep"]["nested"]).to have_key("item") expect(subject.override_attributes["deep"]["nested"]["item"]).to be_truthy end context "when the override attribute is already set" do it "test" do subject.override_attributes = { deep: { nested: { item: false } } } subject.set_override_attribute('deep.nested.item', true) expect(subject.override_attributes["deep"]["nested"]["item"]).to be_truthy end end end describe "#set_default_attribute" do it "sets an override node attribute at the nested path" do subject.set_default_attribute('deep.nested.item', true) expect(subject.default_attributes).to have_key("deep") expect(subject.default_attributes["deep"]).to have_key("nested") expect(subject.default_attributes["deep"]["nested"]).to have_key("item") expect(subject.default_attributes["deep"]["nested"]["item"]).to be_truthy end context "when the override attribute is already set" do it "test" do subject.default_attributes = { deep: { nested: { item: false } } } subject.set_default_attribute('deep.nested.item', true) expect(subject.default_attributes["deep"]["nested"]["item"]).to be_truthy end end end end ridley-5.1.1/spec/unit/ridley/connection_spec.rb0000644000004100000410000000565313123557300021745 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::Connection do let(:server_url) { "https://api.opscode.com" } let(:client_name) { "reset" } let(:client_key) { fixtures_path.join("reset.pem").to_s } subject do described_class.new(server_url, client_name, client_key) end context " when proxy environment variables are set" do subject do described_class.new('http://127.0.0.1:8889', client_name, client_key) end it "fails with http_proxy set without no_proxy" do stub_const('ENV', ENV.to_hash.merge( 'http_proxy' => 'http://i.am.an.http.proxy') ) expect { subject.get('/nodes') }.to raise_error(SocketError) end it "works with http_proxy and no_proxy set" do stub_const('ENV', ENV.to_hash.merge( 'http_proxy' => 'http://i.am.an.http.proxy', 'no_proxy' => '127.0.0.1:8889') ) expect(subject.get('/nodes').status).to eq(200) end end describe "configurable retries" do before(:each) do stub_request(:get, "https://api.opscode.com/organizations/vialstudios").to_return(status: 500, body: "") end it "attempts five (5) retries by default" do expect { subject.get('organizations/vialstudios') }.to raise_error(Ridley::Errors::HTTPInternalServerError) expect(a_request(:get, "https://api.opscode.com/organizations/vialstudios")).to have_been_made.times(6) end context "given a configured count of two (2) retries" do subject do described_class.new(server_url, client_name, client_key, retries: 2) end it "attempts two (2) retries" do expect { subject.get('organizations/vialstudios') }.to raise_error(Ridley::Errors::HTTPInternalServerError) expect(a_request(:get, "https://api.opscode.com/organizations/vialstudios")).to have_been_made.times(3) end end end describe "#api_type" do it "returns :foss if the organization is not set" do subject.stub(:organization).and_return(nil) expect(subject.api_type).to eql(:foss) end it "returns :hosted if the organization is set" do subject.stub(:organization).and_return("vialstudios") expect(subject.api_type).to eql(:hosted) end end describe "#stream" do let(:target) { "http://test.it/file" } let(:destination) { tmp_path.join("test.file") } let(:contents) { "SOME STRING STUFF\nHERE.\n" } before(:each) do stub_request(:get, "http://test.it/file").to_return(status: 200, body: contents) end it "creates a destination file on disk" do subject.stream(target, destination) expect(File.exist?(destination)).to be_truthy end it "returns true when the file was copied" do expect(subject.stream(target, destination)).to be_truthy end it "contains the contents of the response body" do subject.stream(target, destination) expect(File.read(destination)).to include(contents) end end end ridley-5.1.1/spec/unit/ridley/resources/0000755000004100000410000000000013123557300020250 5ustar www-datawww-dataridley-5.1.1/spec/unit/ridley/resources/client_resource_spec.rb0000644000004100000410000000172713123557300025003 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::ClientResource do subject { described_class.new(double('registry')) } describe "#regenerate_key" do let(:client_id) { "rspec-client" } before { subject.stub(find: nil) } context "when a client with the given ID exists" do let(:client) { double('chef-client') } before { subject.should_receive(:find).with(client_id).and_return(client) } it "sets the private key to true and updates the client" do client.should_receive(:private_key=).with(true) subject.should_receive(:update).with(client) subject.regenerate_key(client_id) end end context "when a client with the given ID does not exist" do before { subject.should_receive(:find).with(client_id).and_return(nil) } it "raises a ResourceNotFound error" do expect { subject.regenerate_key(client_id) }.to raise_error(Ridley::Errors::ResourceNotFound) end end end end ridley-5.1.1/spec/unit/ridley/resources/environment_resource_spec.rb0000644000004100000410000000406713123557300026071 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::EnvironmentResource do let(:server_url) { Ridley::RSpec::ChefServer.server_url } let(:client_name) { "reset" } let(:client_key) { fixtures_path.join('reset.pem').to_s } let(:connection) { Ridley::Connection.new(server_url, client_name, client_key) } let(:resource) do resource = described_class.new(double('registry')) resource.stub(connection: connection) resource end subject { resource } describe "#cookbook_versions" do let(:name) { "rspec-test" } let(:run_list) { ["hello", "there"] } subject { resource.cookbook_versions(name, run_list) } context "when the chef server has the given cookbooks" do before do chef_environment("rspec-test") chef_cookbook("hello", "1.2.3") chef_cookbook("there", "1.0.0") end it "returns a Hash" do is_expected.to be_a(Hash) end it "contains a key for each cookbook" do expect(subject.keys.size).to eq(2) expect(subject).to have_key("hello") expect(subject).to have_key("there") end end context "when the chef server does not have the environment" do before do chef_cookbook("hello", "1.2.3") chef_cookbook("there", "1.0.0") end it "raises a ResourceNotFound error" do expect { subject }.to raise_error(Ridley::Errors::ResourceNotFound) end end context "when the chef server does not have one or more of the cookbooks" do it "raises a precondition failed error" do expect { subject }.to raise_error(Ridley::Errors::HTTPPreconditionFailed) end end end describe "#delete_all" do let(:default_env) { double(name: "_default") } let(:destroy_env) { double(name: "destroy_me") } before do subject.stub(all: [ default_env, destroy_env ]) end it "does not destroy the '_default' environment" do subject.stub(future: double('future', value: nil)) subject.should_not_receive(:future).with(:delete, default_env) subject.delete_all end end end ridley-5.1.1/spec/unit/ridley/resources/search_resource_spec.rb0000644000004100000410000002321413123557300024765 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::SearchResource do describe "ClassMethods" do subject { described_class } describe "::build_query" do let(:query_string) { "*:*" } let(:options) { Hash.new } it "contains a 'q' key/value" do result = subject.build_query(query_string, options) expect(result).to have_key(:q) expect(result[:q]).to eql(query_string) end context "when :sort option is set" do before { options[:sort] = "DESC" } it "contains a 'sort' key/value" do result = subject.build_query(query_string, options) expect(result).to have_key(:sort) expect(result[:sort]).to eql("DESC") end end context "when :start option is set" do before { options[:start] = 1 } it "contains a 'start' key/value" do result = subject.build_query(query_string, options) expect(result).to have_key(:start) expect(result[:start]).to eql(1) end end context "when :rows option is set" do before { options[:rows] = 1 } it "contains a 'rows' key/value" do result = subject.build_query(query_string, options) expect(result).to have_key(:rows) expect(result[:rows]).to eql(1) end end end describe "::build_param_string" do let(:query) { "*:*" } let(:options) { Hash.new } subject { described_class.build_param_string(query, options) } it "returns a string containing the query string" do expect(subject).to eq("?q=#{query}") end context "when the :start option is given" do let(:start) { 10 } let(:options) { { start: start } } it "contains the start query param" do expect(subject).to eq("?q=#{query}&start=#{start}") end end context "when the :sort option is given" do let(:sort) { "DESC" } let(:options) { { sort: sort } } it "contains the sort query param" do expect(subject).to eq("?q=#{query}&sort=#{sort}") end end context "when the :rows option is given" do let(:rows) { 20 } let(:options) { { rows: rows } } it "contains the rows query param" do expect(subject).to eq("?q=#{query}&rows=#{rows}") end end end describe "::query_uri" do it "returns a URI path containing the search resource path and index" do expect(subject.query_uri(:nodes)).to eql("search/nodes") end end end let(:connection) { double('chef-connection') } subject { described_class.new(double('registry')) } before { subject.stub(connection: connection) } describe "#indexes" do let(:response) do double(body: { node: "http://localhost:4000/search/node", role: "http://localhost:4000/search/role", client: "http://localhost:4000/search/client", users: "http://localhost:4000/search/users" }) end before do connection.stub(:get).with(described_class.resource_path).and_return(response) end it "performs a GET to the search resource_path" do connection.should_receive(:get).with(described_class.resource_path).and_return(response) subject.indexes end it "contains a key for each index" do expect(subject.indexes.size).to eq(4) end end describe "#run" do let(:index) { :role } let(:query_string) { "*:*" } let(:options) { Hash.new } let(:response) do double(body: { rows: Array.new, total: 0, start: 0 }) end let(:registry) { double("registry", :[] => nil) } let(:run) { subject.run(index, query_string, registry) } before do connection.stub(:get).and_return(response) end it "builds a query and runs it against the index's resource path" do query = double('query') query_uri = double('query-uri') described_class.should_receive(:build_query).with(query_string, options).and_return(query) described_class.should_receive(:query_uri).with(index).and_return(query_uri) connection.should_receive(:get).with(query_uri, query).and_return(response) subject.run(index, query_string, options) end context "when :node is the given index" do let(:index) { :node } let(:response) do double(body: { rows: [ { chef_type: "node", json_class: "Chef::Node", name: "ridley-one", chef_environment: "_default", automatic: {}, normal: {}, default: {}, override: {}, run_list: [ "recipe[one]", "recipe[two]" ] } ], total: 1, start: 0 }) end it "returns an array of Ridley::NodeObject" do result = run expect(result).to be_a(Array) expect(result).to each be_a(Ridley::NodeObject) end context "after the search has executed and results are returned" do let(:search_results) { subject.run(index, query_string, registry) } it "Ridley::NodeObject instances contain the results" do first_result = search_results.first expect(first_result.name).to eq("ridley-one") end end end context "when :role is the given index" do let(:index) { :role } let(:response) do double(body: { rows: [ { chef_type: "role", json_class: "Chef::Role", name: "ridley-role-one", description: "", default_attributes: {}, override_attributes: {}, run_list: [], env_run_lists: {} } ], total: 1, start: 0 }) end it "returns an array of Ridley::RoleObject" do result = run expect(result).to be_a(Array) expect(result).to each be_a(Ridley::RoleObject) end context "after the search has executed and results are returned" do let(:search_results) { subject.run(index, query_string, registry) } it "Ridley::RoleObject instances contain the results" do first_result = search_results.first expect(first_result.name).to eq("ridley-role-one") end end end context "when :environment is the given index" do let(:index) { :environment } let(:response) do double(body: { rows: [ { chef_type: "environment", json_class: "Chef::Environment", name: "ridley-env-test", description: "ridley testing environment", default_attributes: {}, override_attributes: {}, cookbook_versions: {} } ], total: 1, start: 0 }) end it "returns an array of Ridley::EnvironmentObject" do result = run expect(result).to be_a(Array) expect(result).to each be_a(Ridley::EnvironmentObject) end context "after the search has executed and results are returned" do let(:search_results) { subject.run(index, query_string, registry) } it "Ridley::EnvironmentObject instances contain the results" do first_result = search_results.first expect(first_result.name).to eq("ridley-env-test") end end end context "when :client is the given index" do let(:index) { :client } let(:response) do double(body: { rows: [ { chef_type: "client", name: "ridley-client-test", admin: false, validator: false, certificate: "-----BEGIN CERTIFICATE-----\nMIIDOjCCAqOgAwIBAgIE47eOmDANBgkqhkiG9w0BAQUFADCBnjELMAkGA1UEBhMC\nVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxFjAUBgNV\nBAoMDU9wc2NvZGUsIEluYy4xHDAaBgNVBAsME0NlcnRpZmljYXRlIFNlcnZpY2Ux\nMjAwBgNVBAMMKW9wc2NvZGUuY29tL2VtYWlsQWRkcmVzcz1hdXRoQG9wc2NvZGUu\nY29tMCAXDTEyMTAwOTAwMTUxNVoYDzIxMDExMTA0MDAxNTE1WjCBnTEQMA4GA1UE\nBxMHU2VhdHRsZTETMBEGA1UECBMKV2FzaGluZ3RvbjELMAkGA1UEBhMCVVMxHDAa\nBgNVBAsTE0NlcnRpZmljYXRlIFNlcnZpY2UxFjAUBgNVBAoTDU9wc2NvZGUsIElu\nYy4xMTAvBgNVBAMUKFVSSTpodHRwOi8vb3BzY29kZS5jb20vR1VJRFMvY2xpZW50\nX2d1aWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqB9KEGzl7Wcm/\nwz/x8HByZANCn6WQC+R12qQso5I6nLbTNkRP668jXG3j0R5/F5i/KearAB9ePzL/\nQe3iHtwW6u1qLI1hVNFNB+I1fGu1p6fZyIOjnLn3bqsbOkBplHOIqHsp4GVSsHKb\nD32UXZDa9S9ZFXnR4iT6hUGm5895ReZG9TDiHvBpi9NJFDZXz+AQ6JuQY8UgYMMA\nm80KbO8/NJlXbRW+siRuvr+LIsi9Mx4i63pBWAN46my291rQU31PF3IB+btfGtR/\nyDWDgMSB37bTzZeOf1Dg9fpl2vIXyu3PoHER0oYmrMQbrdwAt7qCHZNuNWn51WPb\n1PHxXL1rAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAGnJUVAv951fUhGyPOrl+LbQG\nqgchMwIn7oDLE863e66BYTDj7koK3jjhx3EBkrT2vt/xS4yW0ZRV1BNqfnNKWbBq\nMNQiKkYdTr+oq2O3plOg/q/M1eG1B5pxGXqvH0O76DVWQcV/svO+HQEi1n8y5UQd\n+pBJCygpuv78wPCM+c4=\n-----END CERTIFICATE-----\n", public_key: nil, private_key: nil, orgname: "ridley" } ], total: 1, start: 0 }) end it "returns an array of Ridley::ClientObject" do result = run expect(result).to be_a(Array) expect(result).to each be_a(Ridley::ClientObject) end context "after the search has executed and results are returned" do let(:search_results) { subject.run(index, query_string, registry) } it "Ridley::ClientObject instances contain the results" do first_result = search_results.first expect(first_result.name).to eq("ridley-client-test") end end end end end ridley-5.1.1/spec/unit/ridley/resources/user_resource_spec.rb0000644000004100000410000000175213123557300024501 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::UserResource, type: 'wip' do subject { described_class.new(double('registry')) } let(:user_id) { "rspec-user" } let(:user_password) { "swordfish" } describe "#regenerate_key" do before { subject.stub(find: nil) } context "when a user with the given ID exists" do let(:user) { double('chef-user') } before { subject.should_receive(:find).with(user_id).and_return(user) } it "sets the private key to true and updates the user" do user.should_receive(:private_key=).with(true) subject.should_receive(:update).with(user) subject.regenerate_key(user_id) end end context "when a user with the given ID does not exist" do before { subject.should_receive(:find).with(user_id).and_return(nil) } it "raises a ResourceNotFound error" do expect { subject.regenerate_key(user_id) }.to raise_error(Ridley::Errors::ResourceNotFound) end end end end ridley-5.1.1/spec/unit/ridley/resources/data_bag_item_resource_spec.rb0000644000004100000410000000016513123557300026260 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::DataBagItemResource do subject { described_class.new(double) } skip end ridley-5.1.1/spec/unit/ridley/resources/cookbook_resource_spec.rb0000644000004100000410000001071613123557300025331 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::CookbookResource do let(:client_name) { "reset" } let(:client_key) { fixtures_path.join('reset.pem') } let(:connection) { Ridley::Connection.new("http://localhost:8889", "reset", fixtures_path.join("reset.pem").to_s) } subject { described_class.new(double('registry'), client_name, client_key) } before { subject.stub(connection: connection) } describe "#download" do let(:name) { "example_cookbook" } let(:version) { "0.1.0" } let(:destination) { tmp_path.join("example_cookbook-0.1.0").to_s } context "when the cookbook of the name/version is not found" do before { subject.should_receive(:find).with(name, version).and_return(nil) } it "raises a ResourceNotFound error" do expect { subject.download(name, version, destination) }.to raise_error(Ridley::Errors::ResourceNotFound) end end end describe "#latest_version" do let(:name) { "ant" } context "when the cookbook has no versions" do it "returns a ResourceNotFound error" do expect { subject.latest_version(name) }.to raise_error(Ridley::Errors::ResourceNotFound) end end context "when the cookbook has versions" do before do chef_cookbook(name, "1.0.0") chef_cookbook(name, "1.2.0") chef_cookbook(name, "3.0.0") end it "returns the latest version" do expect(subject.latest_version(name)).to eql("3.0.0") end end end describe "#versions" do let(:name) { "artifact" } context "when the cookbook has versions" do before do chef_cookbook(name, "1.0.0") chef_cookbook(name, "1.1.0") chef_cookbook(name, "1.2.0") end it "returns an array" do expect(subject.versions(name)).to be_a(Array) end it "contains a version string for each cookbook version available" do result = subject.versions(name) expect(result.size).to eq(3) expect(result).to include("1.0.0") expect(result).to include("1.1.0") expect(result).to include("1.2.0") end end context "when the cookbook has no versions" do it "raises a ResourceNotFound error" do expect { subject.versions(name) }.to raise_error(Ridley::Errors::ResourceNotFound) end end end describe "#satisfy" do let(:name) { "ridley_test" } context "when there is a solution" do before do chef_cookbook(name, "2.0.0") chef_cookbook(name, "3.0.0") end it "returns a CookbookObject" do expect(subject.satisfy(name, ">= 2.0.0")).to be_a(Ridley::CookbookObject) end it "is the best solution" do expect(subject.satisfy(name, ">= 2.0.0").version).to eql("3.0.0") end end context "when there is no solution" do before { chef_cookbook(name, "1.0.0") } it "returns nil" do expect(subject.satisfy(name, ">= 2.0.0")).to be_nil end end context "when the cookbook does not exist" do it "raises a ResourceNotFound error" do expect { subject.satisfy(name, ">= 1.2.3") }.to raise_error(Ridley::Errors::ResourceNotFound) end end end describe "#upload" do let(:name) { "upload_test" } let(:cookbook_path) { fixtures_path.join('example_cookbook') } let(:sandbox_resource) { double('sandbox_resource') } let(:sandbox) { double('sandbox', upload: nil, commit: nil) } before do subject.stub(:sandbox_resource).and_return(sandbox_resource) end it 'does not include files that are ignored' do # These are the MD5s for the files. It's not possible to check that # the ignored files weren't uploaded, so we just check that the # non-ignored files are the ONLY thing uploaded sandbox_resource.should_receive(:create).with([ "211a3a8798d4acd424af15ff8a2e28a5", "5f025b0817442ec087c4e0172a6d1e67", "75077ba33d2887cc1746d1ef716bf8b7", "7b1ebd2ff580ca9dc46fb27ec1653bf2", "84e12365e6f4ebe7db6a0e6a92473b16", "a39eb80def9804f4b118099697cc2cd2", "b70ba735f3af47e5d6fc71b91775b34c", "cafb6869fca13f5c36f24a60de8fb982", "dbf3a6c4ab68a86172be748aced9f46e", "dc6461b5da25775f3ef6a9cc1f6cff9f", "e9a2e24281cfbd6be0a6b1af3b6d277e" ]).and_return(sandbox) subject.upload(cookbook_path, validate: false) end end describe "#update" do skip end end ridley-5.1.1/spec/unit/ridley/resources/node_resource_spec.rb0000644000004100000410000000262313123557300024446 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::NodeResource do let(:instance) do inst = described_class.new(double) inst.stub(connection: chef_zero_connection) inst end describe "#merge_data" do let(:node_name) { "rspec-test" } let(:run_list) { [ "recipe[one]", "recipe[two]" ] } let(:attributes) { { deep: { two: "val" } } } subject(:result) { instance.merge_data(node_name, run_list: run_list, attributes: attributes) } context "when a node of the given name exists" do before do chef_node(node_name, run_list: [ "recipe[one]", "recipe[three]" ], normal: { deep: { one: "val" } } ) end it "returns a Ridley::NodeObject" do expect(result).to be_a(Ridley::NodeObject) end it "has a union between the run list of the original node and the new run list" do expect(result.run_list).to eql(["recipe[one]","recipe[three]","recipe[two]"]) end it "has a deep merge between the attributes of the original node and the new attributes" do expect(result.normal.to_hash).to eql("deep" => { "one" => "val", "two" => "val" }) end end context "when a node with the given name does not exist" do let(:node_name) { "does_not_exist" } it "raises a ResourceNotFound error" do expect { result }.to raise_error(Ridley::Errors::ResourceNotFound) end end end end ridley-5.1.1/spec/unit/ridley/resources/role_resource_spec.rb0000644000004100000410000000015613123557300024461 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::RoleResource do subject { described_class.new(double) } skip end ridley-5.1.1/spec/unit/ridley/resources/data_bag_resource_spec.rb0000644000004100000410000000101713123557300025237 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::DataBagResource do let(:secret) { "supersecretkey" } let(:instance) { described_class.new(double, secret) } describe "#item_resource" do subject { instance.item_resource } it "returns a DataBagItemResource" do expect(subject).to be_a(Ridley::DataBagItemResource) end describe '#encrypted_data_bag_secret' do subject { super().encrypted_data_bag_secret } it { is_expected.to eql(secret) } end end describe "#find" do skip end end ridley-5.1.1/spec/unit/ridley/resources/sandbox_resource_spec.rb0000644000004100000410000001201513123557300025153 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::SandboxResource do let(:client_name) { "reset" } let(:client_key) { fixtures_path.join('reset.pem') } let(:connection) { double('chef-connection') } subject { described_class.new(double, client_name, client_key) } before { subject.stub(connection: connection) } describe "#create" do let(:sandbox_id) { "bd091b150b0a4578b97771af6abf3e05" } let(:sandbox_uri) { "https://api.opscode.com/organizations/vialstudios/sandboxes/bd091b150b0a4578b97771af6abf3e05" } let(:checksums) { Hash.new } let(:response) do double(body: { uri: sandbox_uri, checksums: checksums, sandbox_id: sandbox_id }) end before(:each) do connection.stub(:post). with(subject.class.resource_path, JSON.fast_generate(checksums: checksums)). and_return(response) end it "returns a Ridley::SandboxObject" do expect(subject.create).to be_a(Ridley::SandboxObject) end it "has a value of 'false' for :is_completed" do expect(subject.create.is_completed).to be_falsey end it "has an empty Hash of checksums" do expect(subject.create.checksums).to be_a(Hash) expect(subject.create.checksums).to be_empty end it "has a value for :uri" do expect(subject.create.uri).to eql(sandbox_uri) end it "has a value for :sandbox_id" do expect(subject.create.sandbox_id).to eql(sandbox_id) end context "when given an array of checksums" do let(:checksums) do { "385ea5490c86570c7de71070bce9384a" => nil, "f6f73175e979bd90af6184ec277f760c" => nil, "2e03dd7e5b2e6c8eab1cf41ac61396d5" => nil } end let(:checksum_array) { checksums.keys } it "has a Hash of checksums with each of the given checksum ids" do expect(subject.create(checksum_array).checksums.size).to eq(checksum_array.length) end end end describe "#commit" do let(:sandbox_id) { "bd091b150b0a4578b97771af6abf3e05" } let(:sandbox_path) { "#{described_class.resource_path}/#{sandbox_id}" } let(:response) do double(body: { is_completed: true, _rev: "1-bbc8a96f7486aeba2b562d382142fd68", create_time: "2013-01-16T01:43:43+00:00", guid: "bd091b150b0a4578b97771af6abf3e05", json_class: "Chef::Sandbox", name: "bd091b150b0a4578b97771af6abf3e05", checksums: [], chef_type: "sandbox" }) end it "sends a /PUT to the sandbox resource with is_complete set to true" do connection.should_receive(:put).with(sandbox_path, JSON.fast_generate(is_completed: true)).and_return(response) subject.commit(sandbox_id) end context "when a sandbox of the given ID is not found" do before do connection.should_receive(:put).and_raise(Ridley::Errors::HTTPNotFound.new({})) end it "raises a ResourceNotFound error" do expect { subject.commit(sandbox_id) }.to raise_error(Ridley::Errors::ResourceNotFound) end end context "when the given sandbox contents are malformed" do before do connection.should_receive(:put).and_raise(Ridley::Errors::HTTPBadRequest.new({})) end it "raises a SandboxCommitError error" do expect { subject.commit(sandbox_id) }.to raise_error(Ridley::Errors::SandboxCommitError) end end context "when the user who made the request is not authorized" do it "raises a PermissionDenied error on unauthorized" do connection.should_receive(:put).and_raise(Ridley::Errors::HTTPUnauthorized.new({})) expect { subject.commit(sandbox_id) }.to raise_error(Ridley::Errors::PermissionDenied) end it "raises a PermissionDenied error on forbidden" do connection.should_receive(:put).and_raise(Ridley::Errors::HTTPForbidden.new({})) expect { subject.commit(sandbox_id) }.to raise_error(Ridley::Errors::PermissionDenied) end end end describe "#update" do it "is not a supported action" do expect { subject.update(anything) }.to raise_error(RuntimeError, "action not supported") end end describe "#update" do it "is not a supported action" do expect { subject.update }.to raise_error(RuntimeError, "action not supported") end end describe "#all" do it "is not a supported action" do expect { subject.all }.to raise_error(RuntimeError, "action not supported") end end describe "#find" do it "is not a supported action" do expect { subject.find }.to raise_error(RuntimeError, "action not supported") end end describe "#delete" do it "is not a supported action" do expect { subject.delete }.to raise_error(RuntimeError, "action not supported") end end describe "#delete_all" do it "is not a supported action" do expect { subject.delete_all }.to raise_error(RuntimeError, "action not supported") end end end ridley-5.1.1/spec/unit/ridley/chef_object_spec.rb0000644000004100000410000001514713123557300022040 0ustar www-datawww-datadescribe Ridley::ChefObject do let(:resource) { double('chef-resource') } describe "ClassMethods" do subject { Class.new(described_class) } describe "::new" do it "mass assigns the given attributes" do new_attrs = { name: "a name" } expect_any_instance_of(subject).to receive(:mass_assign).with(new_attrs) subject.new(resource, new_attrs) end end describe "::set_chef_type" do it "sets the chef_type attr on the class" do subject.set_chef_type("environment") expect(subject.chef_type).to eql("environment") end end describe "::set_chef_json_class" do it "sets the chef_json_class attr on the class" do subject.set_chef_json_class("Chef::Environment") expect(subject.chef_json_class).to eql("Chef::Environment") end end describe "::set_chef_id" do it "sets the chef_id attribute on the class" do subject.set_chef_id(:environment) expect(subject.chef_id).to eql(:environment) end end describe "::chef_type" do it "returns the underscored name of the including class if nothing is set" do expect(subject.chef_type).to eql(subject.class.name.underscore) end end describe "::chef_json_class" do it "returns the chef_json if nothing has been set" do expect(subject.chef_json_class).to be_nil end end describe "::chef_id" do it "returns nil if nothing is set" do expect(subject.chef_id).to be_nil end end end subject do Class.new(described_class).new(resource) end describe "#save" do context "when the object is valid" do before(:each) { allow(subject).to receive(:valid?).and_return(true) } it "sends a create message to the implementing class" do updated = double('updated') allow(updated).to receive(:_attributes_).and_return(Hash.new) expect(resource).to receive(:create).with(subject).and_return(updated) subject.save end context "when there is an HTTPConflict" do it "sends the update message to self" do updated = double('updated') allow(updated).to receive(:[]).and_return(Hash.new) allow(updated).to receive(:_attributes_).and_return(Hash.new) expect(resource).to receive(:create).and_raise(Ridley::Errors::HTTPConflict.new(updated)) expect(subject).to receive(:update).and_return(updated) subject.save end end end context "when the object is invalid" do before(:each) { allow(subject).to receive(:valid?).and_return(false) } it "raises an InvalidResource error" do expect { subject.save }.to raise_error(Ridley::Errors::InvalidResource) end end end describe "#update" do context "when the object is valid" do let(:updated) do updated = double('updated') allow(updated).to receive(:[]).and_return(Hash.new) allow(updated).to receive(:_attributes_).and_return(Hash.new) updated end before(:each) { allow(subject).to receive(:valid?).and_return(true) } it "sends an update message to the implementing class" do expect(resource).to receive(:update).with(subject).and_return(updated) subject.update end it "returns true" do expect(resource).to receive(:update).with(subject).and_return(updated) expect(subject.update).to eql(true) end end context "when the object is invalid" do before(:each) { allow(subject).to receive(:valid?).and_return(false) } it "raises an InvalidResource error" do expect { subject.update }.to raise_error(Ridley::Errors::InvalidResource) end end end describe "#chef_id" do it "returns the value of the chef_id attribute" do subject.class.attribute(:name) allow(subject.class).to receive(:chef_id) { :name } subject.mass_assign(name: "reset") expect(subject.chef_id).to eql("reset") end end describe "#reload" do let(:updated_subject) { double('updated_subject', _attributes_: { one: "val" }) } before(:each) do subject.class.attribute(:one) subject.class.attribute(:two) allow(resource).to receive(:find).with(subject).and_return(updated_subject) end it "returns itself" do expect(subject.reload).to eql(subject) end it "sets the attributes of self to equal those of the updated object" do subject.reload expect(subject.get_attribute(:one)).to eql("val") end it "does not include attributes not set by the updated object" do subject.two = "other" subject.reload expect(subject.two).to be_nil end end describe "comparable" do subject do Class.new(described_class) do set_chef_id "name" attribute "name" attribute "other_extra" attribute "extra" end end let(:one) { subject.new(resource) } let(:two) { subject.new(resource) } context "given two objects with the same value for their 'chef_id'" do before(:each) do one.mass_assign(name: "reset", other_extra: "stuff") two.mass_assign(name: "reset", extra: "stuff") end it "is equal" do expect(one).to be_eql(two) end end context "given two objects with different values for their 'chef_id'" do before(:each) do one.mass_assign(name: "jamie", other_extra: "stuff") two.mass_assign(name: "winsor", extra: "stuff") end it "is not equal" do expect(one).not_to be_eql(two) end end end describe "uniqueness" do subject do Class.new(described_class) do set_chef_id "name" attribute "name" attribute "other_extra" attribute "extra" end end let(:one) { subject.new(resource) } let(:two) { subject.new(resource) } context "given an array of objects with the same value for their 'chef_id'" do let(:nodes) do one.mass_assign(name: "reset", other_extra: "stuff") two.mass_assign(name: "reset", extra: "stuff") [ one, two ] end it "returns only one unique element" do expect(nodes.uniq.size).to eq(1) end end context "given an array of objects with different values for their 'chef_id'" do let(:nodes) do one.mass_assign(name: "jamie", other_extra: "stuff") two.mass_assign(name: "winsor", extra: "stuff") [ one, two ] end it "returns all of the elements" do expect(nodes.uniq.size).to eq(2) end end end end ridley-5.1.1/spec/unit/ridley/errors_spec.rb0000644000004100000410000000235513123557300021116 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::Errors do describe Ridley::Errors::HTTPError do describe "ClassMethods" do subject { Ridley::Errors::HTTPError } before(:each) do @original = Ridley::Errors::HTTPError.class_variable_get :@@error_map Ridley::Errors::HTTPError.class_variable_set :@@error_map, Hash.new end after(:each) do Ridley::Errors::HTTPError.class_variable_set :@@error_map, @original end describe "::register_error" do it "adds an item to the error map" do subject.register_error(400) expect(subject.error_map.size).to eq(1) end it "adds a key of the given status code with a value of the class inheriting from HTTPError" do class RidleyTestHTTPError < Ridley::Errors::HTTPError register_error(400) end expect(subject.error_map[400]).to eql(RidleyTestHTTPError) end end end context "with an HTML error payload" do subject { Ridley::Errors::HTTPError.new(:body => "

Redirected

") } it "has an HTML body" do expect(subject.message).to eq("

Redirected

") end end end end ridley-5.1.1/spec/unit/ridley/chef/0000755000004100000410000000000013123557300017143 5ustar www-datawww-dataridley-5.1.1/spec/unit/ridley/chef/cookbook/0000755000004100000410000000000013123557300020751 5ustar www-datawww-dataridley-5.1.1/spec/unit/ridley/chef/cookbook/syntax_check_spec.rb0000644000004100000410000001035513123557300024777 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::Chef::Cookbook::SyntaxCheck do let(:cookbook_dir) { fixtures_path.join('example_cookbook')} let(:chefignore) { Ridley::Chef::Chefignore.new(cookbook_dir) } let(:syntax_check) do described_class.new(fixtures_path, chefignore) end subject { syntax_check } before(:each) do allow(subject).to receive(:chefignore) { chefignore } end describe "#ruby_files" do it "lists the rb files in a cookbook" do expect(subject.ruby_files).to include(cookbook_dir.join("libraries/my_lib.rb").to_s) end it "does not list the rb files in a cookbook that are ignored" do expect(subject.ruby_files).not_to include(cookbook_dir.join("ignores/magic.rb").to_s) end end describe "#untested_ruby_files" do it "filters out validated rb files" do valid_ruby_file = cookbook_dir.join("libraries/my_lib.rb").to_s subject.validated(valid_ruby_file) expect(subject.untested_ruby_files).not_to include(valid_ruby_file) end end describe "#template_files" do it "lists the erb files in a cookbook" do expect(subject.template_files).to include(cookbook_dir.join("templates/default/temp.txt.erb").to_s) end it "does not list the erb files in a cookbook that are ignored" do expect(subject.template_files).not_to include(cookbook_dir.join("ignores/magic.erb").to_s) end end describe "#untested_template_files" do it "filters out validated erb files" do valid_template_file = cookbook_dir.join("templates/default/temp.txt.erb").to_s subject.validated(valid_template_file) expect(subject.untested_template_files).not_to include(valid_template_file) end end describe "#validated?" do it "checks if a file has already been validated" do valid_template_file = cookbook_dir.join("templates/default/temp.txt.erb").to_s subject.validated(valid_template_file) expect(subject.validated?(valid_template_file)).to be_truthy end end describe "#validated" do let(:validated_files) { double('validated_files') } before(:each) do allow(subject).to receive(:validated_files) { validated_files } end it "records a file as validated" do template_file = cookbook_dir.join("templates/default/temp.txt.erb").to_s file_checksum = Ridley::Chef::Digester.checksum_for_file(template_file) expect(validated_files).to receive(:add).with(file_checksum) expect(subject.validated(template_file)).to be_nil end end describe "#validate_ruby_files" do it "asks #untested_ruby_files for a list of files and calls #validate_ruby_file on each" do allow(subject).to receive(:validate_ruby_file).with(anything()).exactly(9).times { true } expect(subject.validate_ruby_files).to be_truthy end it "marks the successfully validated ruby files" do allow(subject).to receive(:validated).with(anything()).exactly(9).times expect(subject.validate_ruby_files).to be_truthy end it "returns false if any ruby file fails to validate" do allow(subject).to receive(:validate_ruby_file).with(/\.rb$/) { false } expect(subject.validate_ruby_files).to be_falsey end end describe "#validate_templates" do it "asks #untested_template_files for a list of erb files and calls #validate_template on each" do allow(subject).to receive(:validate_template).with(anything()).exactly(9).times { true } expect(subject.validate_templates).to be_truthy end it "marks the successfully validated erb files" do allow(subject).to receive(:validated).with(anything()).exactly(9).times expect(subject.validate_templates).to be_truthy end it "returns false if any erb file fails to validate" do allow(subject).to receive(:validate_template).with(/\.erb$/) { false } expect(subject.validate_templates).to be_falsey end end describe "#validate_template" do it "asks #shell_out to check the files syntax" end describe "#validate_ruby_file" do it "asks #shell_out to check the files syntax" end describe "without a chefignore" do let(:chefignore) { nil } it "the file listing still works" do expect(subject.ruby_files).to include(cookbook_dir.join("libraries/my_lib.rb").to_s) end end end ridley-5.1.1/spec/unit/ridley/chef/cookbook/metadata_spec.rb0000644000004100000410000000220613123557300024070 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::Chef::Cookbook::Metadata do let(:metadata) do described_class.new end before(:each) do subject { metadata } end describe "#validate_choice_array" do it "should limit the types allowed in the choice array." do options = { :type => "string", :choice => [ "test1", "test2" ], :default => "test1" } expect { subject.attribute("test_cookbook/test", options) }.not_to raise_error options = { :type => "boolean", :choice => [ true, false ], :default => true } expect { subject.attribute("test_cookbook/test", options) }.not_to raise_error options = { :type => "numeric", :choice => [ 1337, 420 ], :default => 1337 } expect { subject.attribute("test_cookbook/test", options) }.not_to raise_error options = { :type => "numeric", :choice => [ true, "false" ], :default => false } expect { subject.attribute("test_cookbook/test", options) }.to raise_error end end end ridley-5.1.1/spec/unit/ridley/chef/cookbook_spec.rb0000644000004100000410000004530713123557300022321 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::Chef::Cookbook do describe "ClassMethods" do subject { described_class } describe "::from_path" do let(:cookbook_path) { fixtures_path.join("example_cookbook") } it "returns an instance of Ridley::Chef::Cookbook" do expect(subject.from_path(cookbook_path)).to be_a(described_class) end it "has a cookbook_name attribute set to the value of the 'name' attribute in the metadata" do expect(subject.from_path(cookbook_path).cookbook_name).to eql("example_cookbook") end context "given a path that does not contain a metadata file" do it "raises an IOError" do expect { subject.from_path(Dir.mktmpdir) }.to raise_error(IOError) end end context "when the metadata does not contain a value for name and no value for :name option was given" do let(:cookbook_path) { tmp_path.join("directory_name").to_s } before do FileUtils.mkdir_p(cookbook_path) FileUtils.touch(File.join(cookbook_path, 'metadata.rb')) end it "raises an exception" do expect { subject.from_path(cookbook_path) }.to raise_error(Ridley::Errors::MissingNameAttribute) end end context "when a metadata.json is missing but metadata.rb is present" do let(:cookbook_path) { tmp_path.join("temp_cookbook").to_s } before do FileUtils.mkdir_p(cookbook_path) File.open(File.join(cookbook_path, 'metadata.rb'), 'w+') do |f| f.write <<-EOH name 'rspec_test' EOH end end it "sets the name of the cookbook from the metadata.rb" do expect(subject.from_path(cookbook_path).cookbook_name).to eql("rspec_test") end end context "when a metadata.json and metadata.rb are both present" do let(:cookbook_path) { tmp_path.join("temp_cookbook").to_s } before do FileUtils.mkdir_p(cookbook_path) File.open(File.join(cookbook_path, 'metadata.json'), 'w+') do |f| f.write JSON.fast_generate(name: "json_metadata") end File.open(File.join(cookbook_path, 'metadata.rb'), 'w+') do |f| f.write <<-EOH name 'ruby_metadata' EOH end end it "prefers the metadata.json" do expect(subject.from_path(cookbook_path).cookbook_name).to eql("json_metadata") end end end describe "::checksum" do it "delegates to Ridley::Chef::Digester.md5_checksum_for_file" do path = fixtures_path.join("example_cookbook", "metadata.rb") expect(Ridley::Chef::Digester).to receive(:md5_checksum_for_file).with(path) subject.checksum(path) end end end let(:cookbook) do described_class.from_path(fixtures_path.join('example_cookbook')) end subject { cookbook } describe "#checksums" do it "returns a Hash" do expect(subject.checksums).to be_a(Hash) end it "has a key value for every cookbook file" do expect(subject.checksums.size).to eq(subject.send(:files).length) end end describe "#compile_metadata" do let(:cookbook_path) { tmp_path.join("temp_cookbook").to_s } subject { described_class.from_path(cookbook_path) } before do FileUtils.mkdir_p(cookbook_path) File.open(File.join(cookbook_path, "metadata.rb"), "w+") do |f| f.write <<-EOH name "rspec_test" version "1.2.3" EOH end end it "compiles the raw metadata.rb into a metadata.json file in the path of the cookbook" do expect(subject.compiled_metadata?).to be_falsey subject.compile_metadata subject.reload expect(subject.compiled_metadata?).to be_truthy expect(subject.cookbook_name).to eql("rspec_test") expect(subject.version).to eql("1.2.3") end context "when given an output path to write the metadata to" do let(:out_path) { tmp_path.join("outpath") } before { FileUtils.mkdir_p(out_path) } it "writes the compiled metadata to a metadata.json file at the given out path" do subject.compile_metadata(out_path) expect(File.exist?(File.join(out_path, "metadata.json"))).to be_truthy end end end describe "#compiled_metadata?" do let(:cookbook_path) { tmp_path.join("temp_cookbook").to_s } subject { described_class.from_path(cookbook_path) } before do FileUtils.mkdir_p(cookbook_path) FileUtils.touch(File.join(cookbook_path, "metadata.rb")) end context "when a metadata.json file is present" do before do File.open(File.join(cookbook_path, 'metadata.json'), 'w+') do |f| f.write JSON.fast_generate(name: "json_metadata") end end describe '#compiled_metadata?' do subject { super().compiled_metadata? } it { is_expected.to be_truthy } end end context "when a metadata.json file is not present" do before do FileUtils.rm_f(File.join(cookbook_path, 'metadata.json')) File.open(File.join(cookbook_path, 'metadata.rb'), 'w+') do |f| f.write "name 'cookbook'" end end describe '#compiled_metadata?' do subject { super().compiled_metadata? } it { is_expected.to be_falsey } end end end describe "#manifest" do it "returns a Mash with a key for each cookbook file category" do [ :recipes, :definitions, :libraries, :attributes, :files, :templates, :resources, :providers, :root_files ].each do |category| expect(subject.manifest).to have_key(category) end end end describe "#validate" do let(:syntax_checker) { double('syntax_checker') } before(:each) do allow(subject).to receive(:syntax_checker) { syntax_checker } end it "asks the syntax_checker to validate the ruby and template files of the cookbook" do expect(syntax_checker).to receive(:validate_ruby_files).and_return(true) expect(syntax_checker).to receive(:validate_templates).and_return(true) subject.validate end it "raises CookbookSyntaxError if the cookbook contains invalid ruby files" do expect(syntax_checker).to receive(:validate_ruby_files).and_return(false) expect { subject.validate }.to raise_error(Ridley::Errors::CookbookSyntaxError) end it "raises CookbookSyntaxError if the cookbook contains invalid template files" do expect(syntax_checker).to receive(:validate_ruby_files).and_return(true) expect(syntax_checker).to receive(:validate_templates).and_return(false) expect { subject.validate }.to raise_error(Ridley::Errors::CookbookSyntaxError) end end describe "#file_metadata" do let(:file) { subject.path.join("files", "default", "file.h") } before(:each) { @metadata = subject.file_metadata(:file, file) } it "has a :path key whose value is a relative path from the CachedCookbook's path" do expect(@metadata).to have_key(:path) expect(@metadata[:path]).to be_relative_path expect(@metadata[:path]).to eql("files/default/file.h") end it "has a :name key whose value is the basename of the target file" do expect(@metadata).to have_key(:name) expect(@metadata[:name]).to eql("file.h") end it "has a :checksum key whose value is the checksum of the target file" do expect(@metadata).to have_key(:checksum) expect(@metadata[:checksum]).to eql("7b1ebd2ff580ca9dc46fb27ec1653bf2") end it "has a :specificity key" do expect(@metadata).to have_key(:specificity) end context "given a file or template in a 'default' directory" do let(:file) { subject.path.join("files", "default", "file.h") } before(:each) { @metadata = subject.file_metadata(:files, file) } it "has a specificity of 'default'" do expect(@metadata[:specificity]).to eql("default") end end context "given a file or template in a 'ubuntu' directory" do let(:file) { subject.path.join("files", "ubuntu", "file.h") } before(:each) { @metadata = subject.file_metadata(:files, file) } it "has a specificity of 'ubuntu'" do expect(@metadata[:specificity]).to eql("ubuntu") end end end describe "#file_specificity" do let(:category) { :templates } let(:relpath) { 'default.rb' } let(:file) { subject.path.join(category.to_s, relpath) } before(:each) { @specificity = subject.file_specificity(category, file) } context "given a recipe file" do let(:category) { :recipes } it "has a specificity of 'default'" do expect(@specificity).to eql("default") end end context "given a template 'default/config.erb'" do let(:relpath) { 'default/config.erb' } it "has a specificity of 'default'" do expect(@specificity).to eql("default") end end context "given a template 'centos/config.erb'" do let(:relpath) { 'centos/config.erb' } it "has a specificity of 'centos'" do expect(@specificity).to eql("centos") end end context "given a template 'config.erb'" do let(:relpath) { 'config.erb' } it "has a specificity of 'root_default'" do expect(@specificity).to eql("root_default") end end end describe "#to_hash" do subject { cookbook.to_hash } it "has a :frozen? flag" do expect(subject).to have_key(:frozen?) end it "has a :recipes key with a value of an Array Hashes" do expect(subject).to have_key(:recipes) expect(subject[:recipes]).to be_a(Array) subject[:recipes].each do |item| expect(item).to be_a(Hash) end end it "has a :name key value pair in a Hash of the :recipes Array of Hashes" do expect(subject[:recipes].first).to have_key(:name) end it "has a :path key value pair in a Hash of the :recipes Array of Hashes" do expect(subject[:recipes].first).to have_key(:path) end it "has a :checksum key value pair in a Hash of the :recipes Array of Hashes" do expect(subject[:recipes].first).to have_key(:checksum) end it "has a :specificity key value pair in a Hash of the :recipes Array of Hashes" do expect(subject[:recipes].first).to have_key(:specificity) end it "has a :definitions key with a value of an Array Hashes" do expect(subject).to have_key(:definitions) expect(subject[:definitions]).to be_a(Array) subject[:definitions].each do |item| expect(item).to be_a(Hash) end end it "has a :name key value pair in a Hash of the :definitions Array of Hashes" do expect(subject[:definitions].first).to have_key(:name) end it "has a :path key value pair in a Hash of the :definitions Array of Hashes" do expect(subject[:definitions].first).to have_key(:path) end it "has a :checksum key value pair in a Hash of the :definitions Array of Hashes" do expect(subject[:definitions].first).to have_key(:checksum) end it "has a :specificity key value pair in a Hash of the :definitions Array of Hashes" do expect(subject[:definitions].first).to have_key(:specificity) end it "has a :libraries key with a value of an Array Hashes" do expect(subject).to have_key(:libraries) expect(subject[:libraries]).to be_a(Array) subject[:libraries].each do |item| expect(item).to be_a(Hash) end end it "has a :name key value pair in a Hash of the :libraries Array of Hashes" do expect(subject[:libraries].first).to have_key(:name) end it "has a :path key value pair in a Hash of the :libraries Array of Hashes" do expect(subject[:libraries].first).to have_key(:path) end it "has a :checksum key value pair in a Hash of the :libraries Array of Hashes" do expect(subject[:libraries].first).to have_key(:checksum) end it "has a :specificity key value pair in a Hash of the :libraries Array of Hashes" do expect(subject[:libraries].first).to have_key(:specificity) end it "has a :attributes key with a value of an Array Hashes" do expect(subject).to have_key(:attributes) expect(subject[:attributes]).to be_a(Array) subject[:attributes].each do |item| expect(item).to be_a(Hash) end end it "has a :name key value pair in a Hash of the :attributes Array of Hashes" do expect(subject[:attributes].first).to have_key(:name) end it "has a :path key value pair in a Hash of the :attributes Array of Hashes" do expect(subject[:attributes].first).to have_key(:path) end it "has a :checksum key value pair in a Hash of the :attributes Array of Hashes" do expect(subject[:attributes].first).to have_key(:checksum) end it "has a :specificity key value pair in a Hash of the :attributes Array of Hashes" do expect(subject[:attributes].first).to have_key(:specificity) end it "has a :files key with a value of an Array Hashes" do expect(subject).to have_key(:files) expect(subject[:files]).to be_a(Array) subject[:files].each do |item| expect(item).to be_a(Hash) end end it "has a :name key value pair in a Hash of the :files Array of Hashes" do expect(subject[:files].first).to have_key(:name) end it "has a :path key value pair in a Hash of the :files Array of Hashes" do expect(subject[:files].first).to have_key(:path) end it "has a :checksum key value pair in a Hash of the :files Array of Hashes" do expect(subject[:files].first).to have_key(:checksum) end it "has a :specificity key value pair in a Hash of the :files Array of Hashes" do expect(subject[:files].first).to have_key(:specificity) end it "has a :templates key with a value of an Array Hashes" do expect(subject).to have_key(:templates) expect(subject[:templates]).to be_a(Array) subject[:templates].each do |item| expect(item).to be_a(Hash) end end it "has a :name key value pair in a Hash of the :templates Array of Hashes" do expect(subject[:templates].first).to have_key(:name) end it "has a :path key value pair in a Hash of the :templates Array of Hashes" do expect(subject[:templates].first).to have_key(:path) end it "has a :checksum key value pair in a Hash of the :templates Array of Hashes" do expect(subject[:templates].first).to have_key(:checksum) end it "has a :specificity key value pair in a Hash of the :templates Array of Hashes" do expect(subject[:templates].first).to have_key(:specificity) end it "has a :resources key with a value of an Array Hashes" do expect(subject).to have_key(:resources) expect(subject[:resources]).to be_a(Array) subject[:resources].each do |item| expect(item).to be_a(Hash) end end it "has a :name key value pair in a Hash of the :resources Array of Hashes" do expect(subject[:resources].first).to have_key(:name) end it "has a :path key value pair in a Hash of the :resources Array of Hashes" do expect(subject[:resources].first).to have_key(:path) end it "has a :checksum key value pair in a Hash of the :resources Array of Hashes" do expect(subject[:resources].first).to have_key(:checksum) end it "has a :specificity key value pair in a Hash of the :resources Array of Hashes" do expect(subject[:resources].first).to have_key(:specificity) end it "has a :providers key with a value of an Array Hashes" do expect(subject).to have_key(:providers) expect(subject[:providers]).to be_a(Array) subject[:providers].each do |item| expect(item).to be_a(Hash) end end it "has a :name key value pair in a Hash of the :providers Array of Hashes" do expect(subject[:providers].first).to have_key(:name) end it "has a :path key value pair in a Hash of the :providers Array of Hashes" do expect(subject[:providers].first).to have_key(:path) end it "has a :checksum key value pair in a Hash of the :providers Array of Hashes" do expect(subject[:providers].first).to have_key(:checksum) end it "has a :specificity key value pair in a Hash of the :providers Array of Hashes" do expect(subject[:providers].first).to have_key(:specificity) end it "has a :root_files key with a value of an Array Hashes" do expect(subject).to have_key(:root_files) expect(subject[:root_files]).to be_a(Array) subject[:root_files].each do |item| expect(item).to be_a(Hash) end end it "has a :name key value pair in a Hash of the :root_files Array of Hashes" do expect(subject[:root_files].first).to have_key(:name) end it "has a :path key value pair in a Hash of the :root_files Array of Hashes" do expect(subject[:root_files].first).to have_key(:path) end it "has a :checksum key value pair in a Hash of the :root_files Array of Hashes" do expect(subject[:root_files].first).to have_key(:checksum) end it "has a :specificity key value pair in a Hash of the :root_files Array of Hashes" do expect(subject[:root_files].first).to have_key(:specificity) end it "has a :cookbook_name key with a String value" do expect(subject).to have_key(:cookbook_name) expect(subject[:cookbook_name]).to be_a(String) end it "has a :metadata key with a Hashie::Mash value" do expect(subject).to have_key(:metadata) expect(subject[:metadata]).to be_a(Hashie::Mash) end it "has a :version key with a String value" do expect(subject).to have_key(:version) expect(subject[:version]).to be_a(String) end it "has a :name key with a String value" do expect(subject).to have_key(:name) expect(subject[:name]).to be_a(String) end it "has a value containing the cookbook name and version separated by a dash for :name" do name, version = subject[:name].split('-') expect(name).to eql(cookbook.cookbook_name) expect(version).to eql(cookbook.version) end it "has a :chef_type key with Cookbook::CHEF_TYPE as the value" do expect(subject).to have_key(:chef_type) expect(subject[:chef_type]).to eql(Ridley::Chef::Cookbook::CHEF_TYPE) end end describe "#to_json" do before(:each) do @json = subject.to_json end it "has a 'json_class' key with Cookbook::CHEF_JSON_CLASS as the value" do expect(@json).to have_json_path('json_class') expect(parse_json(@json)['json_class']).to eql(Ridley::Chef::Cookbook::CHEF_JSON_CLASS) end it "has a 'frozen?' flag" do expect(@json).to have_json_path('frozen?') end end end ridley-5.1.1/spec/unit/ridley/chef/digester_spec.rb0000644000004100000410000000141313123557300022307 0ustar www-datawww-data# Borrowed and modified from: {https://github.com/opscode/chef/blob/11.4.0/spec/unit/digester_spec.rb} require 'spec_helper' describe Ridley::Chef::Digester do before(:each) do @cache = described_class.instance end describe "when computing checksums of cookbook files and templates" do it "proxies the class method checksum_for_file to the instance" do expect(@cache).to receive(:checksum_for_file).with("a_file_or_a_fail") described_class.checksum_for_file("a_file_or_a_fail") end it "generates a checksum from a non-file IO object" do io = StringIO.new("riseofthemachines\nriseofthechefs\n") expected_md5 = '0e157ac1e2dd73191b76067fb6b4bceb' expect(@cache.generate_md5_checksum(io)).to eq(expected_md5) end end end ridley-5.1.1/spec/unit/ridley/chef/chefignore_spec.rb0000644000004100000410000000160213123557300022612 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::Chef::Chefignore do describe '.initialize' do let(:path) { tmp_path.join('chefignore-test') } before { FileUtils.mkdir_p(path) } it 'finds the nearest chefignore' do target = path.join('chefignore').to_s FileUtils.touch(target) expect(described_class.new(path).filepath).to eq(target) end it 'finds a chefignore in the `cookbooks` directory' do target = path.join('cookbooks', 'chefignore').to_s FileUtils.mkdir_p(path.join('cookbooks')) FileUtils.touch(target) expect(described_class.new(path).filepath).to eq(target) end it 'finds a chefignore in the `.chef` directory' do target = path.join('.chef', 'chefignore').to_s FileUtils.mkdir_p(path.join('.chef')) FileUtils.touch(target) expect(described_class.new(path).filepath).to eq(target) end end end ridley-5.1.1/spec/unit/ridley/client_spec.rb0000644000004100000410000001342213123557300021055 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::Client do let(:server_url) { "https://api.opscode.com" } let(:client_name) { "reset" } let(:client_key) { fixtures_path.join("reset.pem").to_s } let(:organization) { "vialstudios" } let(:encrypted_data_bag_secret_path) { fixtures_path.join("reset.pem").to_s } let(:chef_version) { "10.24.0-01" } let(:config) do { server_url: server_url, client_name: client_name, client_key: client_key, organization: organization, encrypted_data_bag_secret_path: encrypted_data_bag_secret_path, chef_version: chef_version } end describe "ClassMethods" do let(:options) do { server_url: "https://api.opscode.com/some_path", client_name: client_name, client_key: client_key } end describe "::initialize" do subject { described_class.new(options) } describe "parsing the 'server_url' option" do describe '#host' do subject { super().host } it { is_expected.to eql("api.opscode.com") } end describe '#scheme' do subject { super().scheme } it { is_expected.to eql("https") } end describe '#path_prefix' do subject { super().path_prefix } it { is_expected.to eql("/") } end end describe "with a server_url containing an organization" do before do options[:server_url] = "#{server_url}/organizations/#{organization}" end it "gets the host data from the server_url" do expect(subject.host).to eql("api.opscode.com") expect(subject.scheme).to eql("https") end it "takes the organization out of the server_url and assigns it to the organization reader" do expect(subject.organization).to eql(organization) end it "sets the 'path_prefix' of the connection the organization sub URI" do expect(subject.path_prefix).to eql("/organizations/#{organization}") end end it "raises 'ArgumentError' if a value for server_url is not given" do expect { described_class.new( client_name: client_name, client_key: client_key ) }.to raise_error(ArgumentError, "Missing required option(s): 'server_url'") end it "raises if a value for client_name is not given" do expect { described_class.new( server_url: server_url, client_key: client_key ) }.to raise_error(ArgumentError, "Missing required option(s): 'client_name'") end it "raises if a value for client_key is not given" do expect { described_class.new( server_url: server_url, client_name: client_name ) }.to raise_error(ArgumentError, "Missing required option(s): 'client_key'") end it "raises a ClientKeyFileNotFound if the client_key is not found or an invalid key" do config[:client_key] = "/tmp/nofile.xxsa" expect { described_class.new(config) }.to raise_error(Ridley::Errors::ClientKeyFileNotFoundOrInvalid) end it "expands the path of the client_key" do config[:client_key] = "spec/fixtures/reset.pem" expect(described_class.new(config).client_key[0..4]).not_to eq("spec/") end it "accepts a client key as a string" do key = File.read(fixtures_path.join("reset.pem").to_s) config[:client_key] = key.dup expect(described_class.new(config).client_key).to eq(key) end it "assigns a 'chef_version' attribute from the given 'chef_version' option" do expect(described_class.new(config).chef_version).to eql("10.24.0-01") end end describe "::open" do it "instantiates a new connection, yields to it, and terminates it" do new_instance = double(alive?: true) expect(described_class).to receive(:new).and_return(new_instance) expect(new_instance).to receive(:hello) expect(new_instance).to receive(:terminate) described_class.open do |f| f.hello end end end end let(:instance) { described_class.new(config) } subject { instance } describe '#client' do subject { super().client } it { is_expected.to be_a(Ridley::ClientResource) } end describe '#cookbook' do subject { super().cookbook } it { is_expected.to be_a(Ridley::CookbookResource) } end describe '#data_bag' do subject { super().data_bag } it { is_expected.to be_a(Ridley::DataBagResource) } end describe '#environment' do subject { super().environment } it { is_expected.to be_a(Ridley::EnvironmentResource) } end describe '#node' do subject { super().node } it { is_expected.to be_a(Ridley::NodeResource) } end describe '#role' do subject { super().role } it { is_expected.to be_a(Ridley::RoleResource) } end describe '#sandbox' do subject { super().sandbox } it { is_expected.to be_a(Ridley::SandboxResource) } end describe "#encrypted_data_bag_secret" do subject { instance.encrypted_data_bag_secret } it { is_expected.to be_a(String) } context "when a encrypted_data_bag_secret_path is not provided" do before(:each) do instance.stub(encrypted_data_bag_secret_path: nil) end it "returns nil" do expect(subject).to be_nil end end context "when the file is not found at the given encrypted_data_bag_secret_path" do before(:each) do instance.stub(encrypted_data_bag_secret_path: fixtures_path.join("not.txt").to_s) end it "raises an EncryptedDataBagSecretNotFound erorr" do expect { subject }.to raise_error(Ridley::Errors::EncryptedDataBagSecretNotFound) end end end end ridley-5.1.1/spec/unit/ridley/logger_spec.rb0000644000004100000410000000341513123557300021057 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::Logging::Logger do subject { described_class.new(File::NULL) } let(:message) { "my message" } let(:filtered_param) { "message" } describe "::initialize" do it "defaults to info" do expect(subject.level).to eq(Logger::WARN) end end describe "#info" do before do subject.level = Logger::INFO subject.filter_param filtered_param end it "supports filtering" do expect(subject).to receive(:filter).with("my message").and_return("my FILTERED") subject.info message end end describe "#filter_params" do it "returns an array" do expect(subject.filter_params).to be_a(Array) end end describe "#filter_param" do let(:param) { "hello" } before do subject.clear_filter_params end it "adds an element to the array" do subject.filter_param(param) expect(subject.filter_params).to include(param) expect(subject.filter_params.size).to eq(1) end context "when the element is already in the array" do before do subject.filter_param(param) end it "does not duplicate the element" do subject.filter_param(param) expect(subject.filter_params.size).to eq(1) end end end describe "#filter" do before do subject.filter_param(filtered_param) end it "replaces entries in filter_params" do expect(subject.filter(message)).to eq("my FILTERED") end context "when there are multiple filter_params" do before do subject.filter_param("fake param") subject.filter_param(filtered_param) end it "replaces only matching filter_params" do expect(subject.filter(message)).to eq("my FILTERED") end end end end ridley-5.1.1/spec/unit/ridley/sandbox_uploader_spec.rb0000644000004100000410000000574613123557300023142 0ustar www-datawww-datarequire 'spec_helper' describe Ridley::SandboxUploader do describe "ClassMethods" do subject { described_class } describe "::checksum" do let(:io) { StringIO.new("some long string") } subject { described_class.checksum(io) } it { is_expected.to eq("2fb66bbfb88cdf9e07a3f1d1dfad71ab") } end describe "::checksum64" do let(:io) { StringIO.new("some long string") } subject { described_class.checksum64(io) } it { is_expected.to eq("L7Zrv7iM354Ho/HR361xqw==") } end end let(:client_name) { "reset" } let(:client_key) { fixtures_path.join('reset.pem') } let(:connection) do double('connection', client_name: client_name, client_key: client_key, options: {} ) end let(:resource) { double('resource', connection: connection) } let(:checksums) do { "oGCPHrQ+5MylEL+V+NIJ9w==" => { needs_upload: true, url: "https://api.opscode.com/organizations/vialstudios/sandboxes/bd091b150b0a4578b97771af6abf3e05" } } end let(:sandbox) { Ridley::SandboxObject.new(resource, checksums: checksums) } subject { described_class.new(client_name, client_key, {}) } describe "#upload" do let(:chk_id) { "a0608f1eb43ee4cca510bf95f8d209f7" } let(:path) { fixtures_path.join('reset.pem').to_s } let(:different_path) { fixtures_path.join('recipe_one.rb').to_s } before { connection.stub(foss?: false) } context "when the checksum needs uploading" do let(:checksums) do { chk_id => { url: "https://api.opscode.com/organizations/vialstudios/sandboxes/bd091b150b0a4578b97771af6abf3e05", needs_upload: true } } end it "uploads each checksum to their target URL" do stub_request(:put, checksums[chk_id][:url]) subject.upload(sandbox, chk_id, path) end it "raises an exception when the calcuated checksum does not match the expected checksum" do expect { subject.upload(sandbox, chk_id, different_path) }.to raise_error(Ridley::Errors::ChecksumMismatch) end end context "when the checksum doesn't need uploading" do let(:checksums) do { chk_id => { needs_upload: false } } end it "returns nil" do expect(subject.upload(sandbox, chk_id, path)).to be_nil end end context "when the connection is an open source server connection with a non-80 port" do before do connection.stub(foss?: true, server_url: "http://localhost:8889") end let(:checksums) do { chk_id => { url: "http://localhost/sandboxes/bd091b150b0a4578b97771af6abf3e05", needs_upload: true } } end it "does not strip the port from the target to upload to" do stub_request(:put, "http://localhost:8889/sandboxes/bd091b150b0a4578b97771af6abf3e05") subject.upload(sandbox, chk_id, path) end end end end ridley-5.1.1/spec/support/0000755000004100000410000000000013123557300015503 5ustar www-datawww-dataridley-5.1.1/spec/support/filepath_matchers.rb0000644000004100000410000000063513123557300021516 0ustar www-datawww-datarequire 'pathname' RSpec::Matchers.define :be_relative_path do match do |given| if given.nil? false else Pathname.new(given).relative? end end failure_message do |given| "Expected '#{given}' to be a relative path but got an absolute path." end failure_message_when_negated do |given| "Expected '#{given}' to not be a relative path but got an absolute path." end end ridley-5.1.1/spec/support/actor_mocking.rb0000644000004100000410000000041213123557300020644 0ustar www-datawww-dataRSpec.configuration.before(:each) do class Celluloid::CellProxy unless @rspec_compatible @rspec_compatible = true undef_method :should_receive if method_defined?(:should_receive) undef_method :stub if method_defined?(:stub) end end end ridley-5.1.1/spec/support/chef_server.rb0000644000004100000410000000355713123557300020335 0ustar www-datawww-datarequire 'chef_zero/server' require_relative 'spec_helpers' module Ridley::RSpec module ChefServer class << self include Ridley::SpecHelpers def clear_request_log @request_log = Array.new end def request_log @request_log ||= Array.new end def server @server ||= ChefZero::Server.new(port: PORT, generate_real_keys: false) end def server_url (@server && @server.url) || "http://localhost/#{PORT}" end def start server.start_background server.on_response do |request, response| request_log << [ request, response ] end clear_request_log server end def stop @server.stop if @server end def running? @server && @server.running? end end include Ridley::SpecHelpers PORT = 8889 def chef_client(name, hash = Hash.new) load_data(:clients, name, hash) end def chef_cookbook(name, version, cookbook = Hash.new) ChefServer.server.load_data("cookbooks" => { "#{name}-#{version}" => cookbook }) end def chef_data_bag(name, hash = Hash.new) ChefServer.server.load_data({ 'data' => { name => hash }}) end def chef_environment(name, hash = Hash.new) load_data(:environments, name, hash) end def chef_node(name, hash = Hash.new) load_data(:nodes, name, hash) end def chef_role(name, hash = Hash.new) load_data(:roles, name, hash) end def chef_user(name, hash = Hash.new) load_data(:users, name, hash) end def chef_zero_connection Ridley::Connection.new(ChefServer.server_url, "reset", fixtures_path.join('reset.pem').to_s) end private def load_data(key, name, hash) ChefServer.server.load_data(key.to_s => { name => JSON.fast_generate(hash) }) end end end ridley-5.1.1/spec/support/shared_examples/0000755000004100000410000000000013123557300020647 5ustar www-datawww-dataridley-5.1.1/spec/support/shared_examples/ridley_resource.rb0000644000004100000410000001365313123557300024403 0ustar www-datawww-datashared_examples_for "a Ridley Resource" do |resource_klass| let(:connection) { double('connection', hosted?: true) } let(:client) { double('client', connection: connection) } let(:active_connection) { double('active-connection') } let(:response) { double('response') } describe "ClassMethods" do subject { resource_klass } describe "::all" do it "sends a get request for the class' resource_path using the given client" do allow(response).to receive(:body) { Hash.new } expect(client.connection).to receive(:get).with(subject.resource_path).and_return(response) subject.all(client) end end describe "::find" do it "sends a get request to the given client to the resource_path of the class for the given chef_id" do chef_id = "ridley_test" allow(response).to receive(:body) { Hash.new } expect(client.connection).to receive(:get).with("#{subject.resource_path}/#{chef_id}").and_return(response) subject.find(client, chef_id) end end describe "::create" do it "sends a post request to the given client using the includer's resource_path" do attrs = { first_name: "jamie", last_name: "winsor" } allow(response).to receive(:body) { attrs } expect(client.connection).to receive(:post).with(subject.resource_path, duck_type(:to_json)).and_return(response) subject.create(client, attrs) end end describe "::delete" do it "sends a delete request to the given client using the includer's resource_path for the given string" do allow(response).to receive(:body) { Hash.new } expect(client.connection).to receive(:delete).with("#{subject.resource_path}/ridley-test").and_return(response) subject.delete(client, "ridley-test") end it "accepts an object that responds to 'chef_id'" do object = double("obj") allow(object).to receive(:chef_id) { "hello" } allow(response).to receive(:body) { Hash.new } expect(client.connection).to receive(:delete).with("#{subject.resource_path}/#{object.chef_id}").and_return(response) subject.delete(client, object) end end describe "::delete_all" do it "sends a delete request for every object in the collection" do skip end end describe "::update" do it "sends a put request to the given client using the includer's resource_path with the given object" do allow(subject).to receive(:chef_id) { :name } subject.attribute(:name) object = subject.new(name: "hello") allow(response).to receive(:body) { Hash.new } expect(client.connection).to receive(:put).with("#{subject.resource_path}/#{object.chef_id}", duck_type(:to_json)).and_return(response) subject.update(client, object) end end end subject { resource_klass.new(client) } describe "#save" do context "when the object is valid" do before(:each) { allow(subject).to receive(:valid?).and_return(true) } it "sends a create message to the implementing class" do updated = double('updated') allow(updated).to receive(:_attributes_).and_return(Hash.new) expect(subject.class).to receive(:create).with(client, subject).and_return(updated) subject.save end context "when there is an HTTPConflict" do it "sends the update message to self" do updated = double('updated') allow(updated).to receive(:[]).and_return(Hash.new) allow(updated).to receive(:_attributes_).and_return(Hash.new) expect(subject.class).to receive(:create).and_raise(Ridley::Errors::HTTPConflict.new(updated)) expect(subject).to receive(:update).and_return(updated) subject.save end end end context "when the object is invalid" do before(:each) { allow(subject).to receive(:valid?).and_return(false) } it "raises an InvalidResource error" do expect { subject.save }.to raise_error(Ridley::Errors::InvalidResource) end end end describe "#update" do context "when the object is valid" do let(:updated) do updated = double('updated') allow(updated).to receive(:[]).and_return(Hash.new) allow(updated).to receive(:_attributes_).and_return(Hash.new) updated end before(:each) { allow(subject).to receive(:valid?).and_return(true) } it "sends an update message to the implementing class" do expect(subject.class).to receive(:update).with(anything, subject).and_return(updated) subject.update end it "returns true" do expect(subject.class).to receive(:update).with(anything, subject).and_return(updated) expect(subject.update).to eql(true) end end context "when the object is invalid" do before(:each) { allow(subject).to receive(:valid?).and_return(false) } it "raises an InvalidResource error" do expect { subject.update }.to raise_error(Ridley::Errors::InvalidResource) end end end describe "#chef_id" do it "returns the value of the chef_id attribute" do subject.class.attribute(:name) allow(subject.class).to receive(:chef_id) { :name } subject.mass_assign(name: "reset") expect(subject.chef_id).to eql("reset") end end describe "#reload" do let(:updated_subject) { double('updated_subject', _attributes_: { fake_attribute: "some_value" }) } before(:each) do subject.class.attribute(:fake_attribute) allow(subject.class).to receive(:find).with(client, subject).and_return(updated_subject) end it "returns itself" do expect(subject.reload).to eql(subject) end it "sets the attributes of self to include those of the reloaded object" do subject.reload expect(subject.get_attribute(:fake_attribute)).to eql("some_value") end end end ridley-5.1.1/spec/support/spec_helpers.rb0000644000004100000410000000056113123557300020506 0ustar www-datawww-datamodule Ridley module SpecHelpers def app_root_path Pathname.new(File.expand_path('../../../', __FILE__)) end def clean_tmp_path FileUtils.rm_rf(tmp_path) FileUtils.mkdir_p(tmp_path) end def fixtures_path app_root_path.join('spec/fixtures') end def tmp_path app_root_path.join('spec/tmp') end end end ridley-5.1.1/spec/support/each_matcher.rb0000644000004100000410000000040413123557300020431 0ustar www-datawww-dataRSpec::Matchers.define :each do |check| match do |actual| actual.each_with_index do |index, o| @object = o expect(index).to check end end failure_message do |actual| "at[#{@object}] #{check.failure_message_for_should}" end end ridley-5.1.1/spec/acceptance/0000755000004100000410000000000013123557300016055 5ustar www-datawww-dataridley-5.1.1/spec/acceptance/client_resource_spec.rb0000644000004100000410000000545413123557300022611 0ustar www-datawww-datarequire 'spec_helper' describe "Client API operations", type: "acceptance" do let(:server_url) { Ridley::RSpec::ChefServer.server_url } let(:client_name) { "reset" } let(:client_key) { fixtures_path.join('reset.pem').to_s } let(:connection) { Ridley.new(server_url: server_url, client_name: client_name, client_key: client_key) } describe "finding a client" do context "when the server has a client of the given name" do before { chef_client("reset", admin: false) } it "returns a ClientObject" do expect(connection.client.find("reset")).to be_a(Ridley::ClientObject) end end context "when the server does not have the client" do it "returns a nil value" do expect(connection.client.find("not_there")).to be_nil end end end describe "creating a client" do it "returns a Ridley::ClientObject" do expect(connection.client.create(name: "reset")).to be_a(Ridley::ClientObject) end it "adds a client to the chef server" do old = connection.client.all.length connection.client.create(name: "reset") expect(connection.client.all.size).to eq(old + 1) end it "has a value for #private_key" do expect(connection.client.create(name: "reset").private_key).not_to be_nil end end describe "deleting a client" do before { chef_client("reset", admin: false) } it "returns a Ridley::ClientObject object" do expect(connection.client.delete("reset")).to be_a(Ridley::ClientObject) end it "removes the client from the server" do connection.client.delete("reset") expect(connection.client.find("reset")).to be_nil end end describe "deleting all clients" do before(:each) do chef_client("reset", admin: false) chef_client("jwinsor", admin: false) end it "returns an array of Ridley::ClientObject objects" do expect(connection.client.delete_all).to each be_a(Ridley::ClientObject) end it "deletes all clients from the remote" do connection.client.delete_all expect(connection.client.all.size).to eq(0) end end describe "listing all clients" do before(:each) do chef_client("reset", admin: false) chef_client("jwinsor", admin: false) end it "returns an array of Ridley::ClientObject objects" do expect(connection.client.all).to each be_a(Ridley::ClientObject) end it "returns all of the clients on the server" do expect(connection.client.all.size).to eq(4) end end describe "regenerating a client's private key" do before { chef_client("reset", admin: false) } it "returns a Ridley::ClientObject object with a value for #private_key" do expect(connection.client.regenerate_key("reset").private_key).to match(/^-----BEGIN RSA PRIVATE KEY-----/) end end end ridley-5.1.1/spec/acceptance/environment_resource_spec.rb0000644000004100000410000000755613123557300023704 0ustar www-datawww-datarequire 'spec_helper' describe "Environment API operations", type: "acceptance" do let(:server_url) { Ridley::RSpec::ChefServer.server_url } let(:client_name) { "reset" } let(:client_key) { fixtures_path.join('reset.pem').to_s } let(:connection) { Ridley.new(server_url: server_url, client_name: client_name, client_key: client_key) } describe "finding an environment" do before { chef_environment("ridley-test-env") } it "returns a valid Ridley::EnvironmentObject object" do expect(connection.environment.find("ridley-test-env")).to be_a(Ridley::EnvironmentObject) end end describe "creating an environment" do it "returns a valid Ridley::EnvironmentObject object" do obj = connection.environment.create(name: "ridley-test-env", description: "a testing env for ridley") expect(obj).to be_a(Ridley::EnvironmentObject) end it "adds an environment to the chef server" do old = connection.environment.all.length connection.environment.create(name: "ridley") expect(connection.environment.all.size).to eq(old + 1) end end describe "deleting an environment" do before { chef_environment("ridley-env") } it "returns a Ridley::EnvironmentObject object" do expect(connection.environment.delete("ridley-env")).to be_a(Ridley::EnvironmentObject) end it "removes the environment from the server" do connection.environment.delete("ridley-env") expect(connection.environment.find("ridley-env")).to be_nil end it "raises Ridley::Errors::HTTPMethodNotAllowed when attempting to delete the '_default' environment" do expect { connection.environment.delete("_default") }.to raise_error(Ridley::Errors::HTTPMethodNotAllowed) end end describe "deleting all environments" do before do chef_environment("ridley-one") chef_environment("ridley-two") end it "returns an array of Ridley::EnvironmentObject objects" do expect(connection.environment.delete_all).to each be_a(Ridley::EnvironmentObject) end it "deletes all environments but '_default' from the remote" do connection.environment.delete_all expect(connection.environment.all.size).to eq(1) end end describe "listing all environments" do it "should return an array of Ridley::EnvironmentObject objects" do expect(connection.environment.all).to each be_a(Ridley::EnvironmentObject) end end describe "updating an environment" do before { chef_environment("ridley-env") } let(:target ) { connection.environment.find("ridley-env") } it "saves a new #description" do target.description = description = "ridley testing environment" connection.environment.update(target) expect(target.reload.description).to eql(description) end it "saves a new set of 'default_attributes'" do target.default_attributes = default_attributes = { "attribute_one" => "val_one", "nested" => { "other" => "val" } } connection.environment.update(target) obj = connection.environment.find(target) expect(obj.default_attributes).to eql(default_attributes) end it "saves a new set of 'override_attributes'" do target.override_attributes = override_attributes = { "attribute_one" => "val_one", "nested" => { "other" => "val" } } connection.environment.update(target) obj = connection.environment.find(target) expect(obj.override_attributes).to eql(override_attributes) end it "saves a new set of 'cookbook_versions'" do target.cookbook_versions = cookbook_versions = { "nginx" => "1.2.0", "tomcat" => "1.3.0" } connection.environment.update(target) obj = connection.environment.find(target) expect(obj.cookbook_versions).to eql(cookbook_versions) end end end ridley-5.1.1/spec/acceptance/search_resource_spec.rb0000644000004100000410000000156213123557300022574 0ustar www-datawww-datarequire 'spec_helper' describe "Search API operations", type: "acceptance" do let(:server_url) { Ridley::RSpec::ChefServer.server_url } let(:client_name) { "reset" } let(:client_key) { fixtures_path.join('reset.pem').to_s } let(:connection) { Ridley.new(server_url: server_url, client_name: client_name, client_key: client_key) } describe "listing indexes" do it "returns an array of indexes" do indexes = connection.search_indexes expect(indexes).to include("role") expect(indexes).to include("node") expect(indexes).to include("client") expect(indexes).to include("environment") end end describe "searching an index that doesn't exist" do it "it raises a Ridley::Errors::HTTPNotFound error" do expect { connection.search(:notthere) }.to raise_error(Ridley::Errors::HTTPNotFound) end end end ridley-5.1.1/spec/acceptance/user_resource_spec.rb0000644000004100000410000000775513123557300022317 0ustar www-datawww-datarequire 'spec_helper' describe "User API operations", type: "wip" do let(:server_url) { Ridley::RSpec::ChefServer.server_url } let(:user_name) { "reset" } let(:user_key) { fixtures_path.join('reset.pem').to_s } let(:connection) { Ridley.new(server_url: server_url, client_name: user_name, client_key: user_key) } describe "finding a user" do context "when the server has a user of the given name" do before { chef_user("reset", admin: false) } it "returns a UserObject" do expect(connection.user.find("reset")).to be_a(Ridley::UserObject) end end context "when the server does not have the user" do it "returns a nil value" do expect(connection.user.find("not_there")).to be_nil end end end describe "creating a user" do it "returns a Ridley::UserObject" do expect(connection.user.create(name: "reset")).to be_a(Ridley::UserObject) end it "adds a user to the chef server" do old = connection.user.all.length connection.user.create(name: "reset") expect(connection.user.all.size).to eq(old + 1) end it "has a value for #private_key" do expect(connection.user.create(name: "reset").private_key).not_to be_nil end end describe "deleting a user" do before { chef_user("reset", admin: false) } it "returns a Ridley::UserObject object" do expect(connection.user.delete("reset")).to be_a(Ridley::UserObject) end it "removes the user from the server" do connection.user.delete("reset") expect(connection.user.find("reset")).to be_nil end end describe "deleting all users" do before(:each) do chef_user("reset", admin: false) chef_user("jwinsor", admin: false) end it "returns an array of Ridley::UserObject objects" do expect(connection.user.delete_all).to each be_a(Ridley::UserObject) end it "deletes all users from the remote" do connection.user.delete_all expect(connection.user.all.size).to eq(0) end end describe "listing all users" do before(:each) do chef_user("reset", admin: false) chef_user("jwinsor", admin: false) end it "returns an array of Ridley::UserObject objects" do expect(connection.user.all).to each be_a(Ridley::UserObject) end it "returns all of the users on the server" do expect(connection.user.all.size).to eq(3) end end describe "regenerating a user's private key" do before { chef_user("reset", admin: false) } it "returns a Ridley::UserObject object with a value for #private_key" do expect(connection.user.regenerate_key("reset").private_key).to match(/^-----BEGIN RSA PRIVATE KEY-----/) end end describe "authenticating a user" do before { chef_user('reset', password: 'swordfish') } it "returns true when given valid username & password" do expect(connection.user.authenticate('reset', 'swordfish')).to be_truthy end it "returns false when given valid username & invalid password" do expect(connection.user.authenticate('reset', "not a swordfish")).to be_falsey end it "returns false when given invalid username & valid password" do expect(connection.user.authenticate("someone-else", 'swordfish')).to be_falsey end it "works also on a User object level" do expect(connection.user.find('reset').authenticate('swordfish')).to be_truthy expect(connection.user.find('reset').authenticate('not a swordfish')).to be_falsey end end describe "changing user's password" do before { chef_user('reset', password: 'swordfish') } subject { connection.user.find('reset') } it "changes the password with which user can authenticate" do expect(subject.authenticate('swordfish')).to be_truthy expect(subject.authenticate('salmon')).to be_falsey subject.password = 'salmon' subject.save expect(subject.authenticate('swordfish')).to be_falsey expect(subject.authenticate('salmon')).to be_truthy end end end ridley-5.1.1/spec/acceptance/data_bag_item_resource_spec.rb0000644000004100000410000000773613123557300024100 0ustar www-datawww-datarequire 'spec_helper' describe "DataBag API operations", type: "acceptance" do let(:server_url) { Ridley::RSpec::ChefServer.server_url } let(:client_name) { "reset" } let(:client_key) { fixtures_path.join('reset.pem').to_s } let(:connection) { Ridley.new(server_url: server_url, client_name: client_name, client_key: client_key) } let(:data_bag) do chef_data_bag("ridley-test") connection.data_bag.find("ridley-test") end describe "listing data bag items" do context "when the data bag has no items" do it "returns an empty array" do expect(data_bag.item.all.size).to eq(0) end end context "when the data bag has items" do before(:each) do data_bag.item.create(id: "one") data_bag.item.create(id: "two") end it "returns an array with each item" do expect(data_bag.item.all.size).to eq(2) end end end describe "creating a data bag item" do it "adds a data bag item to the collection of data bag items" do data_bag.item.create(id: "appconfig", host: "host.local", port: 80, admin: false, servers: ["one"]) expect(data_bag.item.all.size).to eq(1) end context "when an 'id' field is missing" do it "raises an Ridley::Errors::InvalidResource error" do expect { data_bag.item.create(name: "jamie") }.to raise_error(Ridley::Errors::InvalidResource) end end end describe "retrieving a data bag item" do it "returns the desired item in the data bag" do attributes = { "id" => "appconfig", "host" => "host.local", "port" => 80, "admin" => false, "servers" => [ "one" ] } data_bag.item.create(attributes) expect(data_bag.item.find("appconfig").to_hash).to eql(attributes) end end describe "deleting a data bag item" do let(:attributes) do { "id" => "appconfig", "host" => "host.local" } end before { data_bag.item.create(attributes) } it "returns the deleted data bag item" do dbi = data_bag.item.delete(attributes["id"]) expect(dbi).to be_a(Ridley::DataBagItemObject) expect(dbi.attributes).to eql(attributes) end it "deletes the data bag item from the server" do data_bag.item.delete(attributes["id"]) expect(data_bag.item.find(attributes["id"])).to be_nil end end describe "deleting all data bag items in a data bag" do before do data_bag.item.create(id: "one") data_bag.item.create(id: "two") end it "returns the array of deleted data bag items" do expect(data_bag.item.delete_all).to each be_a(Ridley::DataBagItemObject) end it "removes all data bag items from the data bag" do data_bag.item.delete_all expect(data_bag.item.all.size).to eq(0) end end describe "updating a data bag item" do before { data_bag.item.create(id: "one") } it "returns the updated data bag item" do dbi = data_bag.item.update(id: "one", name: "brooke") expect(dbi[:name]).to eql("brooke") end end describe "saving a data bag item" do context "when the data bag item exists" do let(:dbi) { data_bag.item.create(id: "ridley-test") } it "returns true if successful" do dbi[:name] = "brooke" expect(dbi.save).to be_truthy end it "creates a new data bag item on the remote" do dbi[:name] = "brooke" dbi.save expect(data_bag.item.all.size).to eq(1) end end context "when the data bag item does not exist" do it "returns true if successful" do dbi = data_bag.item.new dbi.attributes = { id: "not-there", name: "brooke" } expect(dbi.save).to be_truthy end it "creates a new data bag item on the remote" do dbi = data_bag.item.new dbi.attributes = { id: "not-there", name: "brooke" } dbi.save expect(data_bag.item.all.size).to eq(1) end end end end ridley-5.1.1/spec/acceptance/cookbook_resource_spec.rb0000644000004100000410000000460713123557300023140 0ustar www-datawww-datarequire 'spec_helper' describe "Client API operations", type: "acceptance" do let(:server_url) { Ridley::RSpec::ChefServer.server_url } let(:client_name) { "reset" } let(:client_key) { fixtures_path.join('reset.pem').to_s } let(:connection) { Ridley.new(server_url: server_url, client_name: client_name, client_key: client_key) } subject { connection.cookbook } describe "downloading a cookbook" do before { subject.upload(fixtures_path.join('example_cookbook')) } let(:name) { "example_cookbook" } let(:version) { "0.1.0" } let(:destination) { tmp_path.join("example_cookbook-0.1.0") } context "when the cookbook of the name/version is found" do before { subject.download(name, version, destination) } it "downloads the cookbook to the destination" do expect(File.exist?(destination.join("metadata.json"))).to be_truthy end end end describe "uploading a cookbook" do let(:path) { fixtures_path.join("example_cookbook") } it "uploads the entire contents of the cookbook in the given path, applying chefignore" do subject.upload(path) cookbook = subject.find("example_cookbook", "0.1.0") expect(cookbook.attributes.size).to eq(1) expect(cookbook.definitions.size).to eq(1) expect(cookbook.files.size).to eq(2) expect(cookbook.libraries.size).to eq(1) expect(cookbook.providers.size).to eq(1) expect(cookbook.recipes.size).to eq(1) expect(cookbook.resources.size).to eq(1) expect(cookbook.templates.size).to eq(1) expect(cookbook.root_files.size).to eq(1) end it "does not contain a raw metadata.rb but does contain a compiled metadata.json" do subject.upload(path) cookbook = subject.find("example_cookbook", "0.1.0") expect(cookbook.root_files.any? { |f| f[:name] == "metadata.json" }).to be_truthy expect(cookbook.root_files.any? { |f| f[:name] == "metadata.rb" }).to be_falsey end end describe "listing cookbooks" do before do chef_cookbook("ruby", "1.0.0") chef_cookbook("ruby", "2.0.0") chef_cookbook("elixir", "3.0.0") chef_cookbook("elixir", "3.0.1") end it "returns all of the cookbooks on the server" do all_cookbooks = subject.all expect(all_cookbooks.size).to eq(2) expect(all_cookbooks["ruby"].size).to eq(2) expect(all_cookbooks["elixir"].size).to eq(2) end end end ridley-5.1.1/spec/acceptance/node_resource_spec.rb0000644000004100000410000000770713123557300022263 0ustar www-datawww-datarequire 'spec_helper' describe "Node API operations", type: "acceptance" do let(:server_url) { Ridley::RSpec::ChefServer.server_url } let(:client_name) { "reset" } let(:client_key) { fixtures_path.join('reset.pem').to_s } let(:connection) { Ridley.new(server_url: server_url, client_name: client_name, client_key: client_key) } describe "finding a node" do let(:node_name) { "ridley.localhost" } before { chef_node(node_name) } it "returns a Ridley::NodeObject" do expect(connection.node.find(node_name)).to be_a(Ridley::NodeObject) end end describe "creating a node" do let(:node_name) { "ridley.localhost" } it "returns a new Ridley::NodeObject object" do expect(connection.node.create(name: node_name)).to be_a(Ridley::NodeObject) end it "adds a new node to the server" do connection.node.create(name: node_name) expect(connection.node.all.size).to eq(1) end end describe "deleting a node" do let(:node_name) { "ridley.localhost" } before { chef_node(node_name) } it "returns a Ridley::NodeObject" do expect(connection.node.delete(node_name)).to be_a(Ridley::NodeObject) end it "removes the node from the server" do connection.node.delete(node_name) expect(connection.node.find(node_name)).to be_nil end end describe "deleting all nodes" do before do chef_node("ridley.localhost") chef_node("motherbrain.localhost") end it "deletes all nodes from the remote server" do connection.node.delete_all expect(connection.node.all.size).to eq(0) end end describe "listing all nodes" do before do chef_node("ridley.localhost") chef_node("motherbrain.localhost") end it "returns an array of Ridley::NodeObject" do obj = connection.node.all expect(obj).to each be_a(Ridley::NodeObject) expect(obj.size).to eq(2) end end describe "updating a node" do let(:node_name) { "ridley.localhost" } before { chef_node(node_name) } let(:target) { connection.node.find(node_name) } it "returns the updated node" do expect(connection.node.update(target)).to eql(target) end it "saves a new set of 'normal' attributes" do target.normal = normal = { "attribute_one" => "value_one", "nested" => { "other" => "val" } } connection.node.update(target) obj = connection.node.find(target) expect(obj.normal).to eql(normal) end it "saves a new set of 'default' attributes" do target.default = defaults = { "attribute_one" => "val_one", "nested" => { "other" => "val" } } connection.node.update(target) obj = connection.node.find(target) expect(obj.default).to eql(defaults) end it "saves a new set of 'automatic' attributes" do target.automatic = automatics = { "attribute_one" => "val_one", "nested" => { "other" => "val" } } connection.node.update(target) obj = connection.node.find(target) expect(obj.automatic).to eql(automatics) end it "saves a new set of 'override' attributes" do target.override = overrides = { "attribute_one" => "val_one", "nested" => { "other" => "val" } } connection.node.update(target) obj = connection.node.find(target) expect(obj.override).to eql(overrides) end it "places a node in a new 'chef_environment'" do target.chef_environment = environment = "ridley" connection.node.update(target) obj = connection.node.find(target) expect(obj.chef_environment).to eql(environment) end it "saves a new 'run_list' for the node" do target.run_list = run_list = ["recipe[one]", "recipe[two]"] connection.node.update(target) obj = connection.node.find(target) expect(obj.run_list).to eql(run_list) end end end ridley-5.1.1/spec/acceptance/role_resource_spec.rb0000644000004100000410000000713413123557300022271 0ustar www-datawww-datarequire 'spec_helper' describe "Role API operations", type: "acceptance" do let(:server_url) { Ridley::RSpec::ChefServer.server_url } let(:client_name) { "reset" } let(:client_key) { fixtures_path.join('reset.pem').to_s } let(:connection) { Ridley.new(server_url: server_url, client_name: client_name, client_key: client_key) } describe "finding a role" do let(:role_name) { "ridley-role" } before { chef_role(role_name) } it "returns a Ridley::RoleObject" do expect(connection.role.find(role_name)).to be_a(Ridley::RoleObject) end end describe "creating a role" do let(:role_name) { "ridley-role" } it "returns a new Ridley::RoleObject" do expect(connection.role.create(name: role_name)).to be_a(Ridley::RoleObject) end it "adds a new role to the server" do connection.role.create(name: role_name) expect(connection.role.all.size).to eq(1) end end describe "deleting a role" do let(:role_name) { "ridley-role" } before { chef_role(role_name) } it "returns the deleted Ridley::RoleObject resource" do expect(connection.role.delete(role_name)).to be_a(Ridley::RoleObject) end it "removes the role from the server" do connection.role.delete(role_name) expect(connection.role.find(role_name)).to be_nil end end describe "deleting all roles" do before do chef_role("role_one") chef_role("role_two") end it "deletes all nodes from the remote server" do connection.role.delete_all expect(connection.role.all.size).to eq(0) end end describe "listing all roles" do before do chef_role("role_one") chef_role("role_two") end it "should return an array of Ridley::RoleObject" do obj = connection.role.all expect(obj.size).to eq(2) expect(obj).to each be_a(Ridley::RoleObject) end end describe "updating a role" do let(:role_name) { "ridley-role" } before { chef_role(role_name) } let(:target) { connection.role.find(role_name) } it "returns an updated Ridley::RoleObject object" do expect(connection.role.update(target)).to eql(target) end it "saves a new run_list" do target.run_list = run_list = ["recipe[one]", "recipe[two]"] connection.role.update(target) obj = connection.role.find(target) expect(obj.run_list).to eql(run_list) end it "saves a new env_run_lists" do target.env_run_lists = env_run_lists = { "production" => ["recipe[one]"], "development" => ["recipe[two]"] } connection.role.update(target) obj = connection.role.find(target) expect(obj.env_run_lists).to eql(env_run_lists) end it "saves a new description" do target.description = description = "a new description!" connection.role.update(target) obj = connection.role.find(target) expect(obj.description).to eql(description) end it "saves a new default_attributes" do target.default_attributes = defaults = { "attribute_one" => "value_one", "nested" => { "other" => false } } connection.role.update(target) obj = connection.role.find(target) expect(obj.default_attributes).to eql(defaults) end it "saves a new override_attributes" do target.override_attributes = overrides = { "attribute_two" => "value_two", "nested" => { "other" => false } } connection.role.update(target) obj = connection.role.find(target) expect(obj.override_attributes).to eql(overrides) end end end ridley-5.1.1/spec/acceptance/data_bag_resource_spec.rb0000644000004100000410000000215413123557300023047 0ustar www-datawww-datarequire 'spec_helper' describe "DataBag API operations", type: "acceptance" do let(:server_url) { Ridley::RSpec::ChefServer.server_url } let(:client_name) { "reset" } let(:client_key) { fixtures_path.join('reset.pem').to_s } let(:connection) { Ridley.new(server_url: server_url, client_name: client_name, client_key: client_key) } describe "listing data bags" do context "when no data bags exist" do it "returns an empty array" do expect(connection.data_bag.all.size).to eq(0) end end context "when the server has data bags" do before do chef_data_bag("ridley-one") chef_data_bag("ridley-two") end it "returns an array of data bags" do expect(connection.data_bag.all).to each be_a(Ridley::DataBagObject) end it "returns all of the data bags on the server" do expect(connection.data_bag.all.size).to eq(2) end end end describe "creating a data bag" do it "returns a Ridley::DataBagObject" do expect(connection.data_bag.create(name: "ridley-one")).to be_a(Ridley::DataBagObject) end end end ridley-5.1.1/spec/acceptance/sandbox_resource_spec.rb0000644000004100000410000000203213123557300022756 0ustar www-datawww-datarequire 'spec_helper' describe "Sandbox API operations", type: "acceptance" do let(:server_url) { Ridley::RSpec::ChefServer.server_url } let(:client_name) { "reset" } let(:client_key) { fixtures_path.join('reset.pem').to_s } let(:connection) { Ridley.new(server_url: server_url, client_name: client_name, client_key: client_key) } let(:checksums) do [ Ridley::SandboxUploader.checksum(File.open(fixtures_path.join("recipe_one.rb"))), Ridley::SandboxUploader.checksum(File.open(fixtures_path.join("recipe_two.rb"))) ] end describe "creating a new sandbox" do it "returns an instance of Ridley::SandboxObject" do expect(connection.sandbox.create(checksums)).to be_a(Ridley::SandboxObject) end it "contains a value for sandbox_id" do expect(connection.sandbox.create(checksums).sandbox_id).not_to be_nil end it "returns an instance with the same amount of checksums given to create" do expect(connection.sandbox.create(checksums).checksums.size).to eq(2) end end end ridley-5.1.1/.travis.yml0000644000004100000410000000167113123557300015153 0ustar www-datawww-datasudo: false language: ruby addons: apt: packages: - chef - git - graphviz - libarchive12 - libarchive-dev - libgecode-dev sources: - chef-stable-precise cache: - apt - bundler bundler_args: --without development dist: precise branches: only: - master script: "bundle exec thor spec:all" before_install: - gem update --system - gem install bundler matrix: include: - rvm: 2.2.5 - rvm: 2.3.1 - rvm: ruby-head # Test against master of berkshelf # - rvm: 2.2.5 # gemfile: berkshelf/Gemfile # before_install: # - gem update --system # - gem install bundler # - git clone --depth 1 https://github.com/berkshelf/berkshelf # - cd berkshelf # - echo "gem 'ridley', :path => '..'" >> Gemfile # install: bundle install --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle} # env: # script: bundle exec thor spec:ci ridley-5.1.1/lib/0000755000004100000410000000000013123557300013603 5ustar www-datawww-dataridley-5.1.1/lib/ridley/0000755000004100000410000000000013123557300015073 5ustar www-datawww-dataridley-5.1.1/lib/ridley/middleware/0000755000004100000410000000000013123557300017210 5ustar www-datawww-dataridley-5.1.1/lib/ridley/middleware/follow_redirects.rb0000644000004100000410000001143513123557300023107 0ustar www-datawww-datarequire 'set' module Ridley module Middleware # Borrowed and modified from: # {https://github.com/lostisland/faraday_middleware/blob/master/lib/faraday_middleware/response/follow_redirects.rb} # # Public: Follow HTTP 301, 302, 303, and 307 redirects for GET, PATCH, POST, # PUT, and DELETE requests. # # This middleware does not follow the HTTP specification for HTTP 302, by # default, in that it follows the improper implementation used by most major # web browsers which forces the redirected request to become a GET request # regardless of the original request method. # # For HTTP 301, 302, and 303, the original request is transformed into a # GET request to the response Location, by default. However, with standards # compliance enabled, a 302 will instead act in accordance with the HTTP # specification, which will replay the original request to the received # Location, just as with a 307. # # For HTTP 307, the original request is replayed to the response Location, # including original HTTP request method (GET, POST, PUT, DELETE, PATCH), # original headers, and original body. # # This middleware currently only works with synchronous requests; in other # words, it doesn't support parallelism. class FollowRedirects < Faraday::Middleware include Ridley::Logging # HTTP methods for which 30x redirects can be followed ALLOWED_METHODS = Set.new [:head, :options, :get, :post, :put, :patch, :delete] # HTTP redirect status codes that this middleware implements REDIRECT_CODES = Set.new [301, 302, 303, 307] # Keys in env hash which will get cleared between requests ENV_TO_CLEAR = Set.new [:status, :response, :response_headers] # Default value for max redirects followed FOLLOW_LIMIT = 3 # Public: Initialize the middleware. # # options - An options Hash (default: {}): # limit - A Numeric redirect limit (default: 3) # standards_compliant - A Boolean indicating whether to respect # the HTTP spec when following 302 # (default: false) # cookie - Use either an array of strings # (e.g. ['cookie1', 'cookie2']) to choose kept cookies # or :all to keep all cookies. def initialize(app, options = {}) super(app) @options = options @replay_request_codes = Set.new [307] @replay_request_codes << 302 if standards_compliant? end def call(env) perform_with_redirection(env, follow_limit) end private def perform_with_redirection(env, follows) request_body = env[:body] response = @app.call(env) response.on_complete do |env| if follow_redirect?(env, response) log.debug { "==> request redirected to #{response['location']}" } log.debug { "request env: #{env}" } if follows.zero? log.debug { "==> too many redirects" } raise Ridley::Errors::RedirectLimitReached, response end env = update_env(env, request_body, response) response = perform_with_redirection(env, follows - 1) end end response end def update_env(env, request_body, response) env[:url] += response['location'] if @options[:cookies] cookies = keep_cookies(env) env[:request_headers][:cookies] = cookies unless cookies.nil? end env[:body] = request_body ENV_TO_CLEAR.each {|key| env.delete key } env end def follow_redirect?(env, response) ALLOWED_METHODS.include? env[:method] and REDIRECT_CODES.include? response.status end def follow_limit @options.fetch(:limit, FOLLOW_LIMIT) end def keep_cookies(env) cookies = @options.fetch(:cookies, []) response_cookies = env[:response_headers][:cookies] cookies == :all ? response_cookies : selected_request_cookies(response_cookies) end def selected_request_cookies(cookies) selected_cookies(cookies)[0...-1] end def selected_cookies(cookies) "".tap do |cookie_string| @options[:cookies].each do |cookie| string = /#{cookie}=?[^;]*/.match(cookies)[0] + ';' cookie_string << string end end end def standards_compliant? @options.fetch(:standards_compliant, false) end end end end Faraday::Response.register_middleware follow_redirects: Ridley::Middleware::FollowRedirects ridley-5.1.1/lib/ridley/middleware/chef_auth.rb0000644000004100000410000000547313123557300021474 0ustar www-datawww-datarequire 'mixlib/authentication/signedheaderauth' module Ridley module Middleware class ChefAuth < Faraday::Middleware class << self include Mixlib::Authentication # Generate authentication headers for a request to a Chef Server # # @param [String] client_name # @param [String] client_key # the path OR actual client key # # @option options [String] :host # # @see {#signing_object} for options def authentication_headers(client_name, client_key, options = {}) contents = File.exists?(client_key) ? File.read(client_key) : client_key.to_s rsa_key = OpenSSL::PKey::RSA.new(contents) headers = signing_object(client_name, options).sign(rsa_key).merge(host: options[:host]) headers.inject({}) { |memo, kv| memo["#{kv[0].to_s.upcase}"] = kv[1];memo } end # Create a signing object for a Request to a Chef Server # # @param [String] client_name # # @option options [String] :http_method # @option options [String] :path # @option options [String] :body # @option options [Time] :timestamp # # @return [SigningObject] def signing_object(client_name, options = {}) options = options.reverse_merge( body: String.new, timestamp: Time.now.utc.iso8601 ) options[:user_id] = client_name options[:proto_version] = "1.0" SignedHeaderAuth.signing_object(options) end end include Ridley::Logging attr_reader :client_name attr_reader :client_key def initialize(app, client_name, client_key) super(app) @client_name = client_name @client_key = client_key end def call(env) signing_options = { http_method: env[:method], host: "#{env[:url].host}:#{env[:url].port}", path: env[:url].path, body: env[:body] || '' } authentication_headers = self.class.authentication_headers(client_name, client_key, signing_options) env[:request_headers] = default_headers.merge(env[:request_headers]).merge(authentication_headers) env[:request_headers] = env[:request_headers].merge('Content-Length' => env[:body].bytesize.to_s) if env[:body] log.debug { "==> performing authenticated Chef request as '#{client_name}'"} log.debug { "request env: #{env}"} @app.call(env) end private def default_headers { 'Accept' => 'application/json', 'Content-Type' => 'application/json', 'X-Chef-Version' => Ridley::CHEF_VERSION } end end end end Faraday::Request.register_middleware chef_auth: Ridley::Middleware::ChefAuth ridley-5.1.1/lib/ridley/middleware/parse_json.rb0000644000004100000410000000547413123557300021712 0ustar www-datawww-datamodule Ridley module Middleware class ParseJson < Faraday::Response::Middleware include Ridley::Logging JSON_TYPE = 'application/json'.freeze BRACKETS = [ "[", "{" ].freeze WHITESPACE = [ " ", "\n", "\r", "\t" ].freeze class << self include Ridley::Logging # Takes a string containing JSON and converts it to a Ruby hash # symbols for keys # # @param [String] body # # @return [Hash] def parse(body) result = JSON.parse(body) result.is_a?(Hash) ? Hashie::Mash.new(result) : result end # Extracts the type of the response from the response headers # of a Faraday request env. 'text/html' will be returned if no # content-type is specified in the response # # @example # env = { # :response_headers => { # 'content-type' => 'text/html; charset=utf-8' # } # ... # } # # ParseJson.response_type(env) => 'application/json' # # @param [Hash] env # a Faraday request env # # @return [String] def response_type(env) if env[:response_headers][CONTENT_TYPE].nil? log.debug { "response did not specify a content type" } return "text/html" end env[:response_headers][CONTENT_TYPE].split(';', 2).first end # Determines if the response of the given Faraday request env # contains JSON # # @param [Hash] env # a Faraday request env # # @return [Boolean] def json_response?(env) response_type(env) == JSON_TYPE && looks_like_json?(env) end # Examines the body of a request env and returns true if it appears # to contain JSON or false if it does not # # @param [Hash] env # a Faraday request env # @return [Boolean] def looks_like_json?(env) return false unless env[:body].present? BRACKETS.include?(first_char(env[:body])) end private def first_char(body) idx = -1 begin char = body[idx += 1] char = char.chr if char end while char && WHITESPACE.include?(char) char end end def on_complete(env) if self.class.json_response?(env) log.debug { "==> parsing Chef response body as JSON" } env[:body] = self.class.parse(env[:body]) else log.debug { "==> Chef response did not contain a JSON body" } end end end end end Faraday::Response.register_middleware parse_json: Ridley::Middleware::ParseJson ridley-5.1.1/lib/ridley/middleware/chef_response.rb0000644000004100000410000000150513123557300022361 0ustar www-datawww-datamodule Ridley module Middleware class ChefResponse < Faraday::Response::Middleware class << self # Determines if a response from the Chef server was successful # # @param [Hash] env # a faraday request env # # @return [Boolean] def success?(env) (200..210).to_a.index(env[:status].to_i) ? true : false end end include Ridley::Logging def on_complete(env) log.debug { "==> handling Chef response" } log.debug { "request env: #{env}" } unless self.class.success?(env) log.debug { "** error encounted in Chef response" } raise Errors::HTTPError.fabricate(env) end end end end end Faraday::Response.register_middleware chef_response: Ridley::Middleware::ChefResponse ridley-5.1.1/lib/ridley/mixin/0000755000004100000410000000000013123557300016217 5ustar www-datawww-dataridley-5.1.1/lib/ridley/mixin/checksum.rb0000644000004100000410000000047613123557300020355 0ustar www-datawww-datamodule Ridley::Mixin # Inspired by and dependency-free replacement for # {https://github.com/opscode/chef/blob/11.4.0/lib/chef/mixin/checksum.rb} module Checksum # @param [String] file # # @return [String] def checksum(file) Ridley::Chef::Digester.checksum_for_file(file) end end end ridley-5.1.1/lib/ridley/mixin/params_validate.rb0000644000004100000410000002002013123557300021672 0ustar www-datawww-datarequire 'ridley/errors' module Ridley::Mixin # Borrowed and modified from: {https://raw.github.com/opscode/chef/11.4.0/lib/chef/mixin/params_validate.rb} # # Copyright:: Copyright (c) 2008 Opscode, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module ParamsValidate include Ridley::Errors # Takes a hash of options, along with a map to validate them. Returns the original # options hash, plus any changes that might have been made (through things like setting # default values in the validation map) # # For example: # # validate({ :one => "neat" }, { :one => { :kind_of => String }}) # # Would raise an exception if the value of :one above is not a kind_of? string. Valid # map options are: # # :default:: Sets the default value for this parameter. # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid. # The key will be inserted into the error message if the Proc does not return true: # "Option #{key}'s value #{value} #{message}!" # :kind_of:: Ensure that the value is a kind_of?(Whatever). If passed an array, it will ensure # that the value is one of those types. # :respond_to:: Ensure that the value has a given method. Takes one method name or an array of # method names. # :required:: Raise an exception if this parameter is missing. Valid values are true or false, # by default, options are not required. # :regex:: Match the value of the paramater against a regular expression. # :equal_to:: Match the value of the paramater with ==. An array means it can be equal to any # of the values. def validate(opts, map) #-- # validate works by taking the keys in the validation map, assuming it's a hash, and # looking for _pv_:symbol as methods. Assuming it find them, it calls the right # one. #++ raise ArgumentError, "Options must be a hash" unless opts.kind_of?(Hash) raise ArgumentError, "Validation Map must be a hash" unless map.kind_of?(Hash) map.each do |key, validation| unless key.kind_of?(Symbol) || key.kind_of?(String) raise ArgumentError, "Validation map keys must be symbols or strings!" end case validation when true _pv_required(opts, key) when false true when Hash validation.each do |check, carg| check_method = "_pv_#{check.to_s}" if self.respond_to?(check_method, true) self.send(check_method, opts, key, carg) else raise ArgumentError, "Validation map has unknown check: #{check}" end end end end opts end def set_or_return(symbol, arg, validation) iv_symbol = "@#{symbol.to_s}".to_sym map = { symbol => validation } if arg == nil && self.instance_variable_defined?(iv_symbol) == true self.instance_variable_get(iv_symbol) else opts = validate({ symbol => arg }, { symbol => validation }) self.instance_variable_set(iv_symbol, opts[symbol]) end end private # Return the value of a parameter, or nil if it doesn't exist. def _pv_opts_lookup(opts, key) if opts.has_key?(key.to_s) opts[key.to_s] elsif opts.has_key?(key.to_sym) opts[key.to_sym] else nil end end # Raise an exception if the parameter is not found. def _pv_required(opts, key, is_required=true) if is_required if (opts.has_key?(key.to_s) && !opts[key.to_s].nil?) || (opts.has_key?(key.to_sym) && !opts[key.to_sym].nil?) true else raise ValidationFailed, "Required argument #{key} is missing!" end end end def _pv_equal_to(opts, key, to_be) value = _pv_opts_lookup(opts, key) unless value.nil? passes = false Array(to_be).each do |tb| passes = true if value == tb end unless passes raise ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}." end end end # Raise an exception if the parameter is not a kind_of?(to_be) def _pv_kind_of(opts, key, to_be) value = _pv_opts_lookup(opts, key) unless value.nil? passes = false Array(to_be).each do |tb| passes = true if value.kind_of?(tb) end unless passes raise ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}." end end end # Raise an exception if the parameter does not respond to a given set of methods. def _pv_respond_to(opts, key, method_name_list) value = _pv_opts_lookup(opts, key) unless value.nil? Array(method_name_list).each do |method_name| unless value.respond_to?(method_name) raise ValidationFailed, "Option #{key} must have a #{method_name} method!" end end end end # Assert that parameter returns false when passed a predicate method. # For example, :cannot_be => :blank will raise a ValidationFailed # error value.blank? returns a 'truthy' (not nil or false) value. # # Note, this will *PASS* if the object doesn't respond to the method. # So, to make sure a value is not nil and not blank, you need to do # both :cannot_be => :blank *and* :cannot_be => :nil (or :required => true) def _pv_cannot_be(opts, key, predicate_method_base_name) value = _pv_opts_lookup(opts, key) predicate_method = (predicate_method_base_name.to_s + "?").to_sym if value.respond_to?(predicate_method) if value.send(predicate_method) raise ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}" end end end # Assign a default value to a parameter. def _pv_default(opts, key, default_value) value = _pv_opts_lookup(opts, key) if value == nil opts[key] = default_value end end # Check a parameter against a regular expression. def _pv_regex(opts, key, regex) value = _pv_opts_lookup(opts, key) if value != nil passes = false [ regex ].flatten.each do |r| if value != nil if r.match(value.to_s) passes = true end end end unless passes raise ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}" end end end # Check a parameter against a hash of proc's. def _pv_callbacks(opts, key, callbacks) raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash) value = _pv_opts_lookup(opts, key) if value != nil callbacks.each do |message, zeproc| if zeproc.call(value) != true raise ValidationFailed, "Option #{key}'s value #{value} #{message}!" end end end end # Allow a parameter to default to @name def _pv_name_attribute(opts, key, is_name_attribute=true) if is_name_attribute if opts[key] == nil opts[key] = self.instance_variable_get("@name") end end end end end ridley-5.1.1/lib/ridley/mixin/from_file.rb0000644000004100000410000000366113123557300020514 0ustar www-datawww-datamodule Ridley::Mixin module FromFile module ClassMethods def from_file(filename, *args) new(*args).from_file(filename) end def class_from_file(filename, *args) new(*args).class_from_file(filename) end end class << self def included(base) base.extend(ClassMethods) end end # Loads the contents of a file within the context of the current object # # @param [#to_s] filename # path to the file to load # # @raise [IOError] if the file does not exist or cannot be read def from_file(filename) filename = filename.to_s ensure_presence!(filename) with_error_handling(filename) do self.instance_eval(IO.read(filename), filename, 1) self end end # Loads the contents of a file within the context of the current object's class # # @param [#to_s] filename # path to the file to load # # @raise [IOError] if the file does not exist or cannot be read def class_from_file(filename) filename = filename.to_s ensure_presence!(filename) with_error_handling(filename) do self.class_eval(IO.read(filename), filename, 1) self end end private # Ensure the given filename and path is readable # # @param [String] filename # # @raise [IOError] # if the target file does not exist or is not readable def ensure_presence!(filename) unless File.exists?(filename) && File.readable?(filename) raise IOError, "Could not open or read: '#{filename}'" end end # Execute the given block, handling any exceptions that occur # # @param [String] filename # # @raise [Ridley::Errors::FromFileParserError] # if any exceptions if raised def with_error_handling(filename) yield rescue => e raise Ridley::Errors::FromFileParserError.new(filename, e) end end end ridley-5.1.1/lib/ridley/chef_objects/0000755000004100000410000000000013123557300017511 5ustar www-datawww-dataridley-5.1.1/lib/ridley/chef_objects/data_bag_item_obect.rb0000644000004100000410000001006713123557300023756 0ustar www-datawww-datarequire 'yaml' module Ridley class DataBagItemObject < ChefObject set_chef_id "id" set_assignment_mode :carefree # @return [Ridley::DataBagObject] attr_reader :data_bag attribute :id, type: String, required: true alias_method :attributes=, :mass_assign alias_method :attributes, :_attributes_ # @param [Ridley::DataBagItemResource] resource # @param [Ridley::DataBagObject] data_bag # @param [#to_hash] new_attrs def initialize(resource, data_bag, new_attrs = {}) super(resource, new_attrs) @data_bag = data_bag end # Creates a resource on the target remote or updates one if the resource # already exists. # # @raise [Errors::InvalidResource] # if the resource does not pass validations # # @return [Boolean] # true if successful and false for failure def save raise Errors::InvalidResource.new(self.errors) unless valid? mass_assign(resource.create(data_bag, self)._attributes_) true rescue Errors::HTTPConflict self.update true end # Decrypts this data bag item. # # @return [Hash] decrypted attributes def decrypt decrypted_hash = Hash[_attributes_.map { |key, value| [key, key == "id" ? value : decrypt_value(value)] }] mass_assign(decrypted_hash) end # Decrypts an individual value stored inside the data bag item. # # @example # data_bag_item.decrypt_value("Xk0E8lV9r4BhZzcg4wal0X4w9ZexN3azxMjZ9r1MCZc=") # => {test: {database: {username: "test"}}} # # @param [String] an encrypted String value # # @return [Hash] a decrypted attribute value def decrypt_value(value) case format_version_of(value) when 0 decrypt_v0_value(value) when 1 decrypt_v1_value(value) else raise NotImplementedError, "Currently decrypting only version 0 & 1 databags are supported" end end # Reload the attributes of the instantiated resource # # @return [Object] def reload mass_assign(resource.find(data_bag, self)._attributes_) self end # Updates the instantiated resource on the target remote with any changes made # to self # # @raise [Errors::InvalidResource] # if the resource does not pass validations # # @return [Boolean] def update raise Errors::InvalidResource.new(self.errors) unless valid? mass_assign(resource.update(data_bag, self)._attributes_) true end # @param [#to_hash] hash # # @return [Object] def from_hash(hash) hash = Hashie::Mash.new(hash.to_hash) mass_assign(hash.has_key?(:raw_data) ? hash[:raw_data] : hash) self end private # Shamelessly lifted from https://github.com/opscode/chef/blob/2c0040c95bb942d13ad8c47498df56be43e9a82e/lib/chef/encrypted_data_bag_item.rb#L209-L215 def format_version_of(encrypted_value) if encrypted_value.respond_to?(:key?) encrypted_value["version"] else 0 end end def decrypt_v0_value(value) if encrypted_data_bag_secret.nil? raise Errors::EncryptedDataBagSecretNotSet end decoded_value = Base64.decode64(value) cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc') cipher.decrypt cipher.pkcs5_keyivgen(encrypted_data_bag_secret) decrypted_value = cipher.update(decoded_value) + cipher.final YAML.load(decrypted_value) end def decrypt_v1_value(attrs) if encrypted_data_bag_secret.nil? raise Errors::EncryptedDataBagSecretNotSet end cipher = OpenSSL::Cipher::Cipher.new(attrs[:cipher]) cipher.decrypt cipher.key = Digest::SHA256.digest(encrypted_data_bag_secret) cipher.iv = Base64.decode64(attrs[:iv]) decrypted_value = cipher.update(Base64.decode64(attrs[:encrypted_data])) + cipher.final YAML.load(decrypted_value)["json_wrapper"] end def encrypted_data_bag_secret resource.encrypted_data_bag_secret end end end ridley-5.1.1/lib/ridley/chef_objects/data_bag_object.rb0000644000004100000410000000130613123557300023106 0ustar www-datawww-datamodule Ridley class DataBagObject < ChefObject set_chef_id "name" attribute :name, required: true def item DataBagItemProxy.new(self, resource.item_resource) end # @api private class DataBagItemProxy attr_reader :data_bag_object attr_reader :item_resource # @param [Ridley::DataBagObject] data_bag_object # @param [Ridley::DataBagItemResource] item_resource def initialize(data_bag_object, item_resource) @data_bag_object = data_bag_object @item_resource = item_resource end def method_missing(fun, *args, &block) @item_resource.send(fun, data_bag_object, *args, &block) end end end end ridley-5.1.1/lib/ridley/chef_objects/client_object.rb0000644000004100000410000000242113123557300022641 0ustar www-datawww-datamodule Ridley class ClientObject < Ridley::ChefObject set_chef_id "name" set_chef_type "client" set_chef_json_class "Chef::ApiClient" attribute :name, type: String, required: true attribute :admin, type: Buff::Boolean, required: true, default: false attribute :validator, type: Buff::Boolean, required: true, default: false attribute :certificate, type: String attribute :public_key, type: String attribute :private_key, type: [ String, Buff::Boolean ], default: false attribute :orgname, type: String # Regenerates the private key of the instantiated client object. The new # private key will be set to the value of the 'private_key' accessor # of the instantiated client object. # # @return [Boolean] # true for success and false for failure def regenerate_key self.private_key = true self.save end # Override to_json to reflect to massage the returned attributes based on the type # of connection. Only OHC/OPC requires the json_class attribute is not present. def to_json if resource.connection.hosted? to_hash.except(:json_class).to_json else super end end end end ridley-5.1.1/lib/ridley/chef_objects/environment_object.rb0000644000004100000410000000677713123557300023751 0ustar www-datawww-datamodule Ridley class EnvironmentObject < Ridley::ChefObject set_chef_id "name" set_chef_type "environment" set_chef_json_class "Chef::Environment" attribute :name, required: true attribute :description, default: String.new attribute :default_attributes, default: Hashie::Mash.new attribute :override_attributes, default: Hashie::Mash.new attribute :cookbook_versions, default: Hashie::Mash.new # Set an environment level default attribute given the dotted path representation of # the Chef attribute and value # # @example setting and saving an environment level default attribute # # obj = environment.find("production") # obj.set_default_attribute("my_app.billing.enabled", false) # obj.save # # @param [String] key # @param [Object] value # # @return [Hashie::Mash] def set_default_attribute(key, value) attr_hash = Hashie::Mash.from_dotted_path(key, value) self.default_attributes = self.default_attributes.deep_merge(attr_hash) end # Set an environment level override attribute given the dotted path representation of # the Chef attribute and value # # @example setting and saving an environment level override attribute # # obj = environment.find("production") # obj.set_override_attribute("my_app.billing.enabled", false) # obj.save # # @param [String] key # @param [Object] value # # @return [Hashie::Mash] def set_override_attribute(key, value) attr_hash = Hashie::Mash.from_dotted_path(key, value) self.override_attributes = self.override_attributes.deep_merge(attr_hash) end # Removes a environment default attribute given its dotted path # representation. Returns the default attributes of the environment. # # @param [String] key # the dotted path to an attribute # # @return [Hashie::Mash] def unset_default_attribute(key) unset_attribute(key, :default) end alias :delete_default_attribute :unset_default_attribute # Removes a environment override attribute given its dotted path # representation. Returns the override attributes of the environment. # # @param [String] key # the dotted path to an attribute # # @return [Hashie::Mash] def unset_override_attribute(key) unset_attribute(key, :override) end alias :delete_override_attribute :unset_override_attribute private # Deletes an attribute at the given precedence using its dotted-path key. # # @param [String] key # the dotted path to an attribute # @param [Symbol] precedence # the precedence level to delete the attribute from # # @return [Hashie::Mash] def unset_attribute(key, precedence) keys = key.split(".") leaf_key = keys.pop attributes_to_change = case precedence when :default self.default_attributes when :override self.override_attributes end leaf_attributes = keys.inject(attributes_to_change) do |attributes, key| if attributes[key] && attributes[key].kind_of?(Hashie::Mash) attributes = attributes[key] else return attributes_to_change end end leaf_attributes.delete(leaf_key) return attributes_to_change end end end ridley-5.1.1/lib/ridley/chef_objects/role_object.rb0000644000004100000410000000324013123557300022324 0ustar www-datawww-datamodule Ridley class RoleObject < Ridley::ChefObject set_chef_id "name" set_chef_type "role" set_chef_json_class "Chef::Role" attribute :name, required: true attribute :description, default: String.new attribute :default_attributes, default: Hashie::Mash.new attribute :override_attributes, default: Hashie::Mash.new attribute :run_list, default: Array.new attribute :env_run_lists, default: Hash.new # Set a role level override attribute given the dotted path representation of the Chef # attribute and value # # @example setting and saving a node level override attribute # # obj = node.role("why_god_why") # obj.set_override_attribute("my_app.billing.enabled", false) # obj.save # # @param [String] key # @param [Object] value # # @return [Hashie::Mash] def set_override_attribute(key, value) attr_hash = Hashie::Mash.from_dotted_path(key, value) self.override_attributes = self.override_attributes.deep_merge(attr_hash) end # Set a role level default attribute given the dotted path representation of the Chef # attribute and value # # @example setting and saving a node level default attribute # # obj = node.role("why_god_why") # obj.set_default_attribute("my_app.billing.enabled", false) # obj.save # # @param [String] key # @param [Object] value # # @return [Hashie::Mash] def set_default_attribute(key, value) attr_hash = Hashie::Mash.from_dotted_path(key, value) self.default_attributes = self.default_attributes.deep_merge(attr_hash) end end end ridley-5.1.1/lib/ridley/chef_objects/cookbook_object.rb0000644000004100000410000001105513123557300023174 0ustar www-datawww-datamodule Ridley class CookbookObject < Ridley::ChefObject include Ridley::Logging FILE_TYPES = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ].freeze set_chef_id "cookbook_name" set_chef_type "cookbook" set_chef_json_class "Chef::Cookbook" attribute :name, required: true attribute :attributes, type: Array, default: Array.new attribute :cookbook_name, type: String attribute :definitions, type: Array, default: Array.new attribute :files, type: Array, default: Array.new attribute :libraries, type: Array, default: Array.new attribute :metadata, type: Hashie::Mash attribute :providers, type: Array, default: Array.new attribute :recipes, type: Array, default: Array.new attribute :resources, type: Array, default: Array.new attribute :root_files, type: Array, default: Array.new attribute :templates, type: Array, default: Array.new attribute :version, type: String attribute :frozen?, type: Buff::Boolean # Download the entire cookbook # # @param [String] destination (Dir.mktmpdir) # the place to download the cookbook too. If no value is provided the cookbook # will be downloaded to a temporary location # # @return [String] # the path to the directory the cookbook was downloaded to def download(destination = Dir.mktmpdir) destination = File.expand_path(destination) log.debug { "downloading cookbook: '#{name}'" } FILE_TYPES.each do |filetype| next unless manifest.has_key?(filetype) manifest[filetype].each do |file| file_destination = File.join(destination, file[:path].gsub('/', File::SEPARATOR)) FileUtils.mkdir_p(File.dirname(file_destination)) download_file(filetype, file[:path], file_destination) end end destination end # Download a single file from a cookbook # # @param [#to_sym] filetype # the type of file to download. These are broken up into the following types in Chef: # - attribute # - definition # - file # - library # - provider # - recipe # - resource # - root_file # - template # these types are where the files are stored in your cookbook's structure. For example, a # recipe would be stored in the recipes directory while a root_file is stored at the root # of your cookbook # @param [String] path # path of the file to download # @param [String] destination # where to download the file to # # @return [nil] def download_file(filetype, path, destination) file_list = case filetype.to_sym when :attribute, :attributes; attributes when :definition, :definitions; definitions when :file, :files; files when :library, :libraries; libraries when :provider, :providers; providers when :recipe, :recipes; recipes when :resource, :resources; resources when :root_file, :root_files; root_files when :template, :templates; templates else raise Errors::UnknownCookbookFileType.new(filetype) end file = file_list.find { |f| f[:path] == path } return nil if file.nil? destination = File.expand_path(destination) log.debug { "downloading '#{filetype}' file: #{file} to: '#{destination}'" } resource.connection.stream(file[:url], destination) end # A hash containing keys for all of the different cookbook filetypes with values # representing each file of that type this cookbook contains # # @example # { # root_files: [ # { # :name => "afile.rb", # :path => "files/ubuntu-9.10/afile.rb", # :checksum => "2222", # :specificity => "ubuntu-9.10" # }, # ], # templates: [ manifest_record1, ... ], # ... # } # # @return [Hash] def manifest {}.tap do |manifest| FILE_TYPES.each do |filetype| manifest[filetype] = get_attribute(filetype) end end end # Reload the attributes of the instantiated resource # # @return [Ridley::CookbookObject] def reload mass_assign(resource.find(self, self.version)._attributes_) self end def to_s "#{name}: #{manifest}" end end end ridley-5.1.1/lib/ridley/chef_objects/user_object.rb0000644000004100000410000000245513123557300022350 0ustar www-datawww-datamodule Ridley class UserObject < Ridley::ChefObject set_chef_id "name" set_chef_type "user" set_chef_json_class "Chef::User" attribute :name, type: String, required: true attribute :admin, type: Buff::Boolean, required: true, default: false attribute :certificate, type: String attribute :public_key, type: String attribute :private_key, type: [ String, Buff::Boolean ], default: false attribute :password, type: String attribute :orgname, type: String # Regenerates the private key of the instantiated user object. The new # private key will be set to the value of the 'private_key' accessor # of the instantiated user object. # # @return [Boolean] # true for success and false for failure def regenerate_key self.private_key = true self.save end def authenticate(password) @resource.authenticate(self.chef_id, password) end # Override to_json to reflect to massage the returned attributes based on the type # of connection. Only OHC/OPC requires the json_class attribute is not present. def to_json if resource.connection.hosted? to_hash.except(:json_class).to_json else super end end end end ridley-5.1.1/lib/ridley/chef_objects/sandbox_object.rb0000644000004100000410000000303113123557300023017 0ustar www-datawww-datamodule Ridley class SandboxObject < ChefObject set_chef_id "sandbox_id" attribute :sandbox_id, type: String attribute :uri, type: String attribute :checksums, type: Hash attribute :is_completed, type: Buff::Boolean, default: false # Return information about the given checksum # # @example # sandbox.checksum("e5a0f6b48d0712382295ff30bec1f9cc") => { # needs_upload: true, # url: "https://s3.amazonaws.com/opscode-platform-production-data/organization" # } # # @param [#to_sym] chk_id # checksum to retrieve information about # # @return [Hash] # a hash containing the checksum information def checksum(chk_id) checksums[chk_id.to_sym] end # Concurrently upload all of this sandboxes files into the checksum containers of the sandbox # # @param [Hash] checksums # a hash of file checksums and file paths # # @example # sandbox.upload( # "e5a0f6b48d0712382295ff30bec1f9cc" => "/Users/reset/code/rbenv-cookbook/recipes/default.rb", # "de6532a7fbe717d52020dc9f3ae47dbe" => "/Users/reset/code/rbenv-cookbook/recipes/ohai_plugin.rb" # ) def upload(checksums) resource.upload(self, checksums) end # Notify the Chef Server that uploading to this sandbox has completed # # @raise [Ridley::Errors::SandboxCommitError] def commit response = resource.commit(self) set_attribute(:is_completed, response[:is_completed]) end end end ridley-5.1.1/lib/ridley/chef_objects/node_object.rb0000644000004100000410000001220113123557300022305 0ustar www-datawww-datamodule Ridley class NodeObject < Ridley::ChefObject set_chef_id "name" set_chef_type "node" set_chef_json_class "Chef::Node" attribute :name, required: true attribute :chef_environment, default: "_default" attribute :automatic, default: Hashie::Mash.new attribute :normal, default: Hashie::Mash.new attribute :default, default: Hashie::Mash.new attribute :override, default: Hashie::Mash.new attribute :run_list, default: Array.new alias_method :normal_attributes, :normal alias_method :automatic_attributes, :automatic alias_method :default_attributes, :default alias_method :override_attributes, :override # A merged hash containing a deep merge of all of the attributes respecting the node attribute # precedence level. # # @return [hashie::Mash] def chef_attributes default.merge(normal.merge(override.merge(automatic))) end # Set a node level normal attribute given the dotted path representation of the Chef # attribute and value. # # @note It is not possible to set any other attribute level on a node and have it persist after # a Chef Run. This is because all other attribute levels are truncated at the start of a Chef Run. # # @example setting and saving a node level normal attribute # # obj = node.find("jwinsor-1") # obj.set_chef_attribute("my_app.billing.enabled", false) # obj.save # # @param [String] key # dotted path to key to be unset # @param [Object] value # # @return [Hashie::Mash] def set_chef_attribute(key, value) attr_hash = Hashie::Mash.from_dotted_path(key, value) self.normal = self.normal.deep_merge(attr_hash) end # Unset a node level normal attribute given the dotted path representation of the Chef # attribute and value. # # @example unsetting and saving a node level normal attribute # # obj = node.find("foonode") # obj.unset_chef_attribute("my_app.service_one.service_state") # obj.save # # @param [String] key # dotted path to key to be unset # # @return [Hashie::Mash] def unset_chef_attribute(key) keys = key.split(".") leaf_key = keys.pop attributes = keys.inject(self.normal) do |attributes, key| if attributes[key] && attributes[key].kind_of?(Hashie::Mash) attributes = attributes[key] else return self.normal end end attributes.delete(leaf_key) return self.normal end # Returns the public hostname of the instantiated node. This hostname should be used for # public communications to the node. # # @example # node.public_hostname => "reset.riotgames.com" # # @return [String] def public_hostname self.cloud? ? self.automatic[:cloud][:public_hostname] || self.automatic[:fqdn] : self.automatic[:fqdn] end # Returns the public IPv4 address of the instantiated node. This ip address should be # used for public communications to the node. # # @example # node.public_ipv4 => "10.33.33.1" # # @return [String] def public_ipv4 self.cloud? ? self.automatic[:cloud][:public_ipv4] || self.automatic[:ipaddress] : self.automatic[:ipaddress] end alias_method :public_ipaddress, :public_ipv4 # Returns the cloud provider of the instantiated node. If the node is not identified as # a cloud node, then nil is returned. # # @example # node_1.cloud_provider => "eucalyptus" # node_2.cloud_provider => "ec2" # node_3.cloud_provider => "rackspace" # node_4.cloud_provider => nil # # @return [nil, String] def cloud_provider self.cloud? ? self.automatic[:cloud][:provider] : nil end # Returns true if the node is identified as a cloud node. # # @return [Boolean] def cloud? self.automatic.has_key?(:cloud) end # Returns true if the node is identified as a cloud node using the eucalyptus provider. # # @return [Boolean] def eucalyptus? self.cloud_provider == "eucalyptus" end # Returns true if the node is identified as a cloud node using the ec2 provider. # # @return [Boolean] def ec2? self.cloud_provider == "ec2" end # Returns true if the node is identified as a cloud node using the rackspace provider. # # @return [Boolean] def rackspace? self.cloud_provider == "rackspace" end # Merges the instaniated nodes data with the given data and updates # the remote with the merged results # # @option options [Array] :run_list # run list items to merge # @option options [Hash] :attributes # attributes of normal precedence to merge # # @return [Ridley::NodeObject] def merge_data(options = {}) new_run_list = Array(options[:run_list]) new_attributes = options[:attributes] unless new_run_list.empty? self.run_list = self.run_list | new_run_list end unless new_attributes.nil? self.normal = self.normal.deep_merge(new_attributes) end self end end end ridley-5.1.1/lib/ridley/connection.rb0000644000004100000410000001140013123557300017553 0ustar www-datawww-datarequire 'open-uri' require 'retryable' require 'tempfile' require 'zlib' require 'ridley/helpers' module Ridley class Connection < Faraday::Connection include Celluloid task_class TaskThread VALID_OPTIONS = [ :retries, :retry_interval, :ssl, :proxy ] # @return [String] attr_reader :organization # @return [String] attr_reader :client_key # @return [String] attr_reader :client_name # @return [Integer] # how many retries to attempt on HTTP requests attr_reader :retries # @return [Float] # time to wait between retries attr_reader :retry_interval # @param [String] server_url # @param [String] client_name # @param [String] client_key # # @option options [Integer] :retries (5) # retry requests on 5XX failures # @option options [Float] :retry_interval (0.5) # how often we should pause between retries # @option options [Hash] :ssl # * :verify (Boolean) [true] set to false to disable SSL verification # @option options [URI, String, Hash] :proxy # URI, String, or Hash of HTTP proxy options def initialize(server_url, client_name, client_key, options = {}) options = options.reverse_merge(retries: 5, retry_interval: 0.5) @client_name = client_name @client_key = client_key @retries = options.delete(:retries) @retry_interval = options.delete(:retry_interval) options[:builder] = Faraday::RackBuilder.new do |b| b.request :retry, max: @retries, interval: @retry_interval, exceptions: [ Ridley::Errors::HTTP5XXError, Errno::ETIMEDOUT, Faraday::Error::TimeoutError ] b.request :chef_auth, client_name, client_key b.response :parse_json b.response :chef_response b.adapter :httpclient end uri_hash = Ridley::Helpers.options_slice(Addressable::URI.parse(server_url).to_hash, :scheme, :host, :port) unless uri_hash[:port] uri_hash[:port] = (uri_hash[:scheme] == "https" ? 443 : 80) end if org_match = server_url.match(/.*\/organizations\/(.*)/) @organization = org_match[1] end unless @organization.nil? uri_hash[:path] = "/organizations/#{@organization}" end super(Addressable::URI.new(uri_hash), options) @headers[:user_agent] = "Ridley v#{Ridley::VERSION}" end # @return [Symbol] def api_type organization.nil? ? :foss : :hosted end # @return [Boolean] def hosted? api_type == :hosted end # @return [Boolean] def foss? api_type == :foss end # Override Faraday::Connection#run_request to catch exceptions from {Ridley::Middleware} that # we expect. Caught exceptions are re-raised with Celluloid#abort so we don't crash the connection. def run_request(*args) super rescue Errors::HTTPError => ex abort ex rescue Faraday::Error::ConnectionFailed => ex abort Errors::ConnectionFailed.new(ex) rescue Faraday::Error::TimeoutError => ex abort Errors::TimeoutError.new(ex) rescue Faraday::Error::ClientError => ex abort Errors::ClientError.new(ex) end def server_url self.url_prefix.to_s end # Stream the response body of a remote URL to a file on the local file system # # @param [String] target # a URL to stream the response body from # @param [String] destination # a location on disk to stream the content of the response body to # # @return [Boolean] true when the destination file exists def stream(target, destination) FileUtils.mkdir_p(File.dirname(destination)) target = Addressable::URI.parse(target) headers = Middleware::ChefAuth.authentication_headers( client_name, client_key, http_method: "GET", host: target.host, path: target.path ) unless ssl[:verify] headers.merge!(ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE) end local = Tempfile.new('ridley-stream') local.binmode Retryable.retryable(tries: retries, on: OpenURI::HTTPError, sleep: retry_interval) do open(target, 'rb', headers) do |remote| body = remote.read case remote.content_encoding when ['gzip'] body = Zlib::GzipReader.new(StringIO.new(body), encoding: 'ASCII-8BIT').read when ['deflate'] body = Zlib::Inflate.inflate(body) end local.write(body) end end local.flush FileUtils.cp(local.path, destination) File.exists?(destination) rescue OpenURI::HTTPError => ex abort(ex) ensure local.close(true) unless local.nil? end end end ridley-5.1.1/lib/ridley/errors.rb0000644000004100000410000001333313123557300016737 0ustar www-datawww-datamodule Ridley module Errors class RidleyError < StandardError; end class InternalError < RidleyError; end class ArgumentError < InternalError; end class ClientError < RidleyError; end class ConnectionFailed < ClientError; end class TimeoutError < ClientError; end class ResourceNotFound < RidleyError; end class ValidatorNotFound < RidleyError; end class ValidationFailed < RidleyError; end class InvalidResource < RidleyError attr_reader :errors def initialize(errors) @errors = errors end def message errors.values end alias_method :to_s, :message end class UnknownCookbookFileType < RidleyError attr_reader :type def initialize(type) @type = type end def to_s "filetype: '#{type}'" end end class CookbookSyntaxError < RidleyError; end class EncryptedDataBagSecretNotSet < RidleyError def message "no encrypted data bag secret was set for this Ridley connection" end end class FromFileParserError < RidleyError def initialize(filename, error) super "Could not parse `#{filename}': #{error.message}" # Populate the backtrace with the actual error though set_backtrace(error.backtrace) end end class MissingNameAttribute < RidleyError def initialize(path) @path = path end def to_s out = "The metadata at '#{@path}' does not contain a 'name' " out << "attribute. While Chef does not strictly enforce this " out << "requirement, Ridley cannot continue without a valid metadata " out << "'name' entry." out end alias_method :message, :to_s end class ClientKeyFileNotFoundOrInvalid < RidleyError; end class EncryptedDataBagSecretNotFound < RidleyError; end # Exception thrown when the maximum amount of requests is exceeded. class RedirectLimitReached < RidleyError attr_reader :response def initialize(response) super "too many redirects; last one to: #{response['location']}" @response = response end end class FrozenCookbook < RidleyError; end class SandboxCommitError < RidleyError; end class PermissionDenied < RidleyError; end class SandboxUploadError < RidleyError; end class ChecksumMismatch < RidleyError; end class HTTPError < RidleyError class << self def fabricate(env) klass = lookup_error(env[:status].to_i) klass.new(env) end def register_error(status) error_map[status.to_i] = self end def lookup_error(status) error_map.fetch(status.to_i) rescue KeyError HTTPUnknownStatus end def error_map @@error_map ||= Hash.new end end attr_reader :env attr_reader :errors attr_reader :message alias_method :to_s, :message def initialize(env) @env = env @errors = env[:body].is_a?(Hash) ? Array(env[:body][:error]) : [] if errors.empty? @message = env[:body] || "no content body" else @message = "errors: " @message << errors.collect { |e| "'#{e}'" }.join(', ') end end end class HTTPUnknownStatus < HTTPError def initialize(env) super(env) @message = "status: #{env[:status]} is an unknown HTTP status code or not an error." end end class HTTPUnknownMethod < HTTPError attr_reader :method def initialize(method) @method = method @message = "unknown http method: #{method}" end end class HTTP3XXError < HTTPError; end class HTTP4XXError < HTTPError; end class HTTP5XXError < HTTPError; end # 3XX class HTTPMultipleChoices < HTTP3XXError; register_error(300); end class HTTPMovedPermanently < HTTP3XXError; register_error(301); end class HTTPFound < HTTP3XXError; register_error(302); end class HTTPSeeOther < HTTP3XXError; register_error(303); end class HTTPNotModified < HTTP3XXError; register_error(304); end class HTTPUseProxy < HTTP3XXError; register_error(305); end class HTTPTemporaryRedirect < HTTP3XXError; register_error(307); end # 4XX class HTTPBadRequest < HTTP4XXError; register_error(400); end class HTTPUnauthorized < HTTP4XXError; register_error(401); end class HTTPPaymentRequired < HTTP4XXError; register_error(402); end class HTTPForbidden < HTTP4XXError; register_error(403); end class HTTPNotFound < HTTP4XXError; register_error(404); end class HTTPMethodNotAllowed < HTTP4XXError; register_error(405); end class HTTPNotAcceptable < HTTP4XXError; register_error(406); end class HTTPProxyAuthenticationRequired < HTTP4XXError; register_error(407); end class HTTPRequestTimeout < HTTP4XXError; register_error(408); end class HTTPConflict < HTTP4XXError; register_error(409); end class HTTPGone < HTTP4XXError; register_error(410); end class HTTPLengthRequired < HTTP4XXError; register_error(411); end class HTTPPreconditionFailed < HTTP4XXError; register_error(412); end class HTTPRequestEntityTooLarge < HTTP4XXError; register_error(413); end class HTTPRequestURITooLong < HTTP4XXError; register_error(414); end class HTTPUnsupportedMediaType < HTTP4XXError; register_error(415); end # 5XX class HTTPInternalServerError < HTTP5XXError; register_error(500); end class HTTPNotImplemented < HTTP5XXError; register_error(501); end class HTTPBadGateway < HTTP5XXError; register_error(502); end class HTTPServiceUnavailable < HTTP5XXError; register_error(503); end class HTTPGatewayTimeout < HTTP5XXError; register_error(504); end end end ridley-5.1.1/lib/ridley/resources/0000755000004100000410000000000013123557300017105 5ustar www-datawww-dataridley-5.1.1/lib/ridley/resources/search_resource.rb0000644000004100000410000001433013123557300022607 0ustar www-datawww-datamodule Ridley class SearchResource < Ridley::Resource class << self # @param [String] query_string # # @option options [String] :sort # a sort string such as 'name DESC' # @option options [Integer] :rows # how many rows to return # @option options [Integer] :start # the result number to start from # # @return [Hash] def build_query(query_string, options = {}) {}.tap do |query_opts| query_opts[:q] = query_string unless query_string.nil? query_opts[:sort] = options[:sort] unless options[:sort].nil? query_opts[:rows] = options[:rows] unless options[:rows].nil? query_opts[:start] = options[:start] unless options[:start].nil? end end # Builds and returns a query parameter string for the search API # # @param [String] query_string # # @option options [String] :sort # a sort string such as 'name DESC' # @option options [Integer] :rows # how many rows to return # @option options [Integer] :start # the result number to start from # # @example # build_param_string("*:*", rows: 5) #=> "?q=*:*&rows=5" # # @return [String] def build_param_string(query_string, options = {}) query = build_query(query_string, options) param = "?q=#{escape(query[:q])}" param += "&sort=#{escape(query[:sort])}" if query[:sort] param += "&start=#{escape(query[:start])}" if query[:start] param += "&rows=#{escape(query[:rows])}" if query[:rows] param end # @param [#to_s] index # # @return [String] def query_uri(index) "#{resource_path}/#{index}" end private def escape(str) str && URI.escape(str.to_s) end end set_resource_path "search" # Returns an array of possible search indexes to be search on # # @param [Ridley::Client] client # # @example # # Search.indexes(client) => [ :client, :environment, :node, :role ] # # @return [Array] def indexes request(:get, self.class.resource_path).collect { |name, _| name } end # Executes the built up query on the search's client # # @param [#to_sym, #to_s] index # @param [#to_s] query_string # # @option options [String] :sort # a sort string such as 'name DESC' # @option options [Integer] :rows # how many rows to return # @option options [Integer] :start # the result number to start from # # @example # Search.new(client, :role) # search.run => # { # total: 1, # start: 0, # rows: [ # { # name: "ridley-test-role", # default_attributes: {}, # json_class: "Chef::Role", # env_run_lists: {}, # run_list: [], # description: "a test role for Ridley!", # chef_type: "role", # override_attributes: {} # } # ] # } # # @return [Array, Hash] def run(index, query_string, resources_registry, options = {}) query_uri = self.class.query_uri(index) query = self.class.build_query(query_string, options) handle_response(index, resources_registry, request(:get, query_uri, query)) end # Perform a partial search on the Chef server # # @param [#to_sym, #to_s] index # @param [#to_s] query_string # @param [Array] attributes # an array of strings in dotted hash notation representing the attributes to return # # @option options [String] :sort # a sort string such as 'name DESC' # @option options [Integer] :rows # how many rows to return # @option options [Integer] :start # the result number to start from # # @return [Array, Hash] def partial(index, query_string, attributes, resources_registry, options = {}) query_uri = self.class.query_uri(index) param_string = self.class.build_param_string(query_string, options) body = build_partial_body(index, attributes) handle_partial(index, resources_registry, request(:post, "#{query_uri}#{param_string}", JSON.generate(body))) end private def build_partial_body(index, attributes) chef_id = chef_id_for_index(index) Hash.new.tap do |body| body[chef_id] = [ chef_id ] if chef_id if index.to_sym == :node body['cloud.public_hostname'] = [ 'cloud', 'public_hostname' ] body['cloud.public_ip4v'] = [ 'cloud', 'public_ip4v' ] body['cloud.provider'] = [ 'cloud', 'provider' ] body['fqdn'] = [ 'fqdn' ] body['ipaddress'] = [ 'ipaddress' ] end attributes.collect { |attr| body[attr] = attr.split('.') } end end def chef_id_for_index(index) chef_id = index.to_sym == :node ? Ridley::NodeObject.chef_id : nil end def handle_partial(index, registry, response) chef_id = chef_id_for_index(index) case index.to_sym when :node response[:rows].collect do |item| attributes = Hashie::Mash.new item[:data].each do |key, value| next if key.to_s == chef_id.to_s attributes.deep_merge!(Hash.from_dotted_path(key, value)) end registry[:node_resource].new(name: item[:data][chef_id], automatic: attributes) end else response[:rows] end end def handle_response(index, registry, response) case index.to_sym when :node response[:rows].collect { |row| NodeObject.new(registry[:node_resource], row) } when :role response[:rows].collect { |row| RoleObject.new(registry[:role_resource], row) } when :client response[:rows].collect { |row| ClientObject.new(registry[:client_resource], row) } when :environment response[:rows].collect { |row| EnvironmentObject.new(registry[:environment_resource], row) } else response[:rows] end end end end ridley-5.1.1/lib/ridley/resources/cookbook_resource.rb0000644000004100000410000002234113123557300023151 0ustar www-datawww-datarequire 'ridley/helpers' module Ridley class CookbookResource < Ridley::Resource task_class TaskThread set_resource_path "cookbooks" represented_by Ridley::CookbookObject def initialize(connection_registry, client_name, client_key, options = {}) super(connection_registry) @sandbox_resource = SandboxResource.new_link(connection_registry, client_name, client_key, options) end # List all of the cookbooks and their versions present on the remote # # @example return value # { # "ant" => [ # "0.10.1" # ], # "apache2" => [ # "1.4.0" # ] # } # # @return [Hash] # a hash containing keys which represent cookbook names and values which contain # an array of strings representing the available versions def all response = request(:get, self.class.resource_path, num_versions: "all") {}.tap do |cookbooks| response.each do |name, details| cookbooks[name] = details["versions"].collect { |version| version["version"] } end end end # Delete a cookbook of the given name and version on the remote Chef server # # @param [String] name # @param [String] version # # @option options [Boolean] purge (false) # # @return [Boolean] def delete(name, version, options = {}) options = options.reverse_merge(purge: false) url = "#{self.class.resource_path}/#{name}/#{version}" url += "?purge=true" if options[:purge] request(:delete, url) true rescue AbortError => ex return nil if ex.cause.is_a?(Errors::HTTPNotFound) abort(ex.cause) end # Delete all of the versions of a given cookbook on the remote Chef server # # @param [String] name # name of the cookbook to delete # # @option options [Boolean] purge (false) def delete_all(name, options = {}) versions(name).collect { |version| future(:delete, name, version, options) }.map(&:value) end # Download the entire cookbook # # @param [String] name # @param [String] version # @param [String] destination (Dir.mktmpdir) # the place to download the cookbook too. If no value is provided the cookbook # will be downloaded to a temporary location # # @raise [Errors::ResourceNotFound] if the target cookbook is not found # # @return [String] # the path to the directory the cookbook was downloaded to def download(name, version, destination = Dir.mktmpdir) if cookbook = find(name, version) cookbook.download(destination) else abort Errors::ResourceNotFound.new("cookbook #{name} (#{version}) was not found") end end # @param [String, #chef_id] object # @param [String] version # # @return [nil, CookbookResource] def find(object, version) chef_id = object.respond_to?(:chef_id) ? object.chef_id : object new(request(:get, "#{self.class.resource_path}/#{chef_id}/#{version}")) rescue AbortError => ex return nil if ex.cause.is_a?(Errors::HTTPNotFound) abort(ex.cause) end # Return the latest version of the given cookbook found on the remote Chef server # # @param [String] name # # @raise [Errors::ResourceNotFound] if the target cookbook has no versions # # @return [String, nil] def latest_version(name) ver = versions(name).collect do |version| Semverse::Version.new(version) end.sort.last ver.nil? ? nil : ver.to_s end # Return the version of the given cookbook which best stasifies the given constraint # # @param [String] name # name of the cookbook # @param [String, Semverse::Constraint] constraint # constraint to solve for # # @raise [Errors::ResourceNotFound] if the target cookbook has no versions # # @return [CookbookResource, nil] # returns the cookbook resource for the best solution or nil if no solution exists def satisfy(name, constraint) version = Semverse::Constraint.satisfy_best(constraint, versions(name)).to_s find(name, version) rescue Semverse::NoSolutionError nil end # Update or create a new Cookbook Version of the given name, version with the # given manifest of files and checksums. # # @param [Ridley::Chef::Cookbook] cookbook # the cookbook to save # # @option options [Boolean] :force # Upload the Cookbook even if the version already exists and is frozen on # the target Chef Server # @option options [Boolean] :freeze # Freeze the uploaded Cookbook on the Chef Server so that it cannot be # overwritten # # @raise [Ridley::Errors::FrozenCookbook] # if a cookbook of the same name and version already exists on the remote Chef server # and is frozen. If the :force option is provided the given cookbook will be saved # regardless. # # @return [Hash] def update(cookbook, options = {}) options = options.reverse_merge(force: false, freeze: false) cookbook.frozen = options[:freeze] url = "cookbooks/#{cookbook.cookbook_name}/#{cookbook.version}" url << "?force=true" if options[:force] request(:put, url, cookbook.to_json) rescue AbortError => ex if ex.cause.is_a?(Errors::HTTPConflict) abort Ridley::Errors::FrozenCookbook.new(ex) end abort(ex.cause) end alias_method :create, :update # Uploads a cookbook to the remote Chef server from the contents of a filepath # # @param [String] path # path to a cookbook on local disk # # @option options [Boolean] :force (false) # Upload the Cookbook even if the version already exists and is frozen on # the target Chef Server # @option options [Boolean] :freeze (false) # Freeze the uploaded Cookbook on the Chef Server so that it cannot be # overwritten # @option options [Boolean] :validate (true) # Validate the contents of the cookbook before uploading # # @return [Hash] def upload(path, options = {}) options = options.reverse_merge(validate: true, force: false, freeze: false) cookbook = Ridley::Chef::Cookbook.from_path(path) unless (existing = find(cookbook.cookbook_name, cookbook.version)).nil? if existing.frozen? && options[:force] == false msg = "The cookbook #{cookbook.cookbook_name} (#{cookbook.version}) already exists and is" msg << " frozen on the Chef server. Use the 'force' option to override." abort Ridley::Errors::FrozenCookbook.new(msg) end end if options[:validate] cookbook.validate end # Compile metadata on upload if it hasn't been compiled already unless cookbook.compiled_metadata? compiled_metadata = cookbook.compile_metadata cookbook.reload end # Skip uploading the raw metadata (metadata.rb). The raw metadata is unecessary for the # client, and this is required until compiled metadata (metadata.json) takes precedence over # raw metadata in the Chef-Client. # # We can change back to including the raw metadata in the future after this has been fixed or # just remove these comments. There is no circumstance that I can currently think of where # raw metadata should ever be read by the client. # # - Jamie # # See the following tickets for more information: # * https://tickets.opscode.com/browse/CHEF-4811 # * https://tickets.opscode.com/browse/CHEF-4810 cookbook.manifest[:root_files].reject! do |file| File.basename(file[:name]).downcase == Ridley::Chef::Cookbook::Metadata::RAW_FILE_NAME end checksums = cookbook.checksums.dup sandbox = sandbox_resource.create(checksums.keys.sort) sandbox.upload(checksums) sandbox.commit update(cookbook, Ridley::Helpers.options_slice(options, :force, :freeze)) ensure # Destroy the compiled metadata only if it was created if compiled_metadata # The garbage collector still has a reference to the file we'd # like to delete. On *nix this isn't a big deal, but on Windows # [ ruby 2.0.0p451 (2014-02-24) [i386-mingw32] ] open files # cannot be deleted, so we're forced to garbage collect to # ensure we can delete the file. This is CRITICAL to ensure that # a stale metadata file isn't left on disk because next time we # would use that file instead of recompiling. GC.start File.delete(compiled_metadata) end end # Return a list of versions for the given cookbook present on the remote Chef server # # @param [String] name # # @example # versions("nginx") => [ "1.0.0", "1.2.0" ] # # @raise [Errors::ResourceNotFound] if the target cookbook has no versions # # @return [Array] def versions(name) response = request(:get, "#{self.class.resource_path}/#{name}") response[name]["versions"].collect do |cb_ver| cb_ver["version"] end rescue AbortError => ex if ex.cause.is_a?(Errors::HTTPNotFound) abort Errors::ResourceNotFound.new(ex) end abort(ex.cause) end private attr_reader :sandbox_resource end end ridley-5.1.1/lib/ridley/resources/client_resource.rb0000644000004100000410000000176713123557300022632 0ustar www-datawww-datamodule Ridley # @example listing all clients # conn = Ridley.new(...) # conn.client.all #=> [ # #, # # # ] class ClientResource < Ridley::Resource set_resource_path "clients" represented_by Ridley::ClientObject # Retrieves a client from the remote connection matching the given chef_id # and regenerates its private key. An instance of the updated object will # be returned and will have a value set for the 'private_key' accessor. # # @param [String, #chef_id] chef_client # # @raise [Errors::ResourceNotFound] # if a client with the given chef_id is not found # # @return [Ridley::ClientObject] def regenerate_key(chef_client) unless chef_client = find(chef_client) abort Errors::ResourceNotFound.new("client '#{chef_client}' not found") end chef_client.private_key = true update(chef_client) end end end ridley-5.1.1/lib/ridley/resources/sandbox_resource.rb0000644000004100000410000000647413123557300023012 0ustar www-datawww-datamodule Ridley class SandboxResource < Ridley::Resource set_resource_path "sandboxes" represented_by Ridley::SandboxObject finalizer :finalize_callback def initialize(connection_registry, client_name, client_key, options = {}) super(connection_registry) options = options.reverse_merge(pool_size: 4) @uploader = SandboxUploader.pool(size: options.delete(:pool_size), args: [ client_name, client_key, options ]) end # Create a new Sandbox on the client's Chef Server. A Sandbox requires an # array of file checksums which lets the Chef Server know what the signature # of the contents to be uploaded will look like. # # @param [Ridley::Client] client # @param [Array] checksums # a hash of file checksums # # @example using the Ridley client to create a sandbox # client.sandbox.create([ # "385ea5490c86570c7de71070bce9384a", # "f6f73175e979bd90af6184ec277f760c", # "2e03dd7e5b2e6c8eab1cf41ac61396d5" # ]) # # @return [Array] def create(checksums = []) sumhash = { checksums: Hash.new }.tap do |chks| Array(checksums).each { |chk| chks[:checksums][chk] = nil } end new(request(:post, self.class.resource_path, JSON.fast_generate(sumhash))) end # @param [#chef_id] object # # @raise [Ridley::Errors::SandboxCommitError] # @raise [Ridley::Errors::ResourceNotFound] # @raise [Ridley::Errors::PermissionDenied] # # @return [Hash] def commit(object) chef_id = object.respond_to?(:chef_id) ? object.chef_id : object request(:put, "#{self.class.resource_path}/#{chef_id}", JSON.fast_generate(is_completed: true)) rescue AbortError => ex case ex.cause when Ridley::Errors::HTTPBadRequest; abort Ridley::Errors::SandboxCommitError.new(ex.message) when Ridley::Errors::HTTPNotFound; abort Ridley::Errors::ResourceNotFound.new(ex.message) when Ridley::Errors::HTTPUnauthorized, Ridley::Errors::HTTPForbidden abort Ridley::Errors::PermissionDenied.new(ex.message) else; abort(ex.cause) end end # Concurrently upload all of the files in the given sandbox # # @param [Ridley::SandboxObject] sandbox # @param [Hash] checksums # a hash of file checksums and file paths # # @example # SandboxUploader.upload(sandbox, # "e5a0f6b48d0712382295ff30bec1f9cc" => "/Users/reset/code/rbenv-cookbook/recipes/default.rb", # "de6532a7fbe717d52020dc9f3ae47dbe" => "/Users/reset/code/rbenv-cookbook/recipes/ohai_plugin.rb" # ) # # @return [Array] def upload(object, checksums) checksums.collect do |chk_id, path| uploader.future(:upload, object, chk_id, path) end.map(&:value) end def update(*args) abort RuntimeError.new("action not supported") end def all(*args) abort RuntimeError.new("action not supported") end def find(*args) abort RuntimeError.new("action not supported") end def delete(*args) abort RuntimeError.new("action not supported") end def delete_all(*args) abort RuntimeError.new("action not supported") end private attr_reader :uploader def finalize_callback uploader.async.terminate if uploader end end end ridley-5.1.1/lib/ridley/resources/environment_resource.rb0000644000004100000410000000251413123557300023707 0ustar www-datawww-datamodule Ridley class EnvironmentResource < Ridley::Resource set_resource_path "environments" represented_by Ridley::EnvironmentObject # Used to return a hash of the cookbooks and cookbook versions (including all dependencies) # that are required by the run_list array. # # @param [String] environment # name of the environment to run against # @param [Array] run_list # an array of cookbooks to satisfy # # @raise [Errors::ResourceNotFound] if the given environment is not found # # @return [Hash] def cookbook_versions(environment, run_list = []) run_list = Array(run_list).flatten chef_id = environment.respond_to?(:chef_id) ? environment.chef_id : environment request(:post, "#{self.class.resource_path}/#{chef_id}/cookbook_versions", JSON.fast_generate(run_list: run_list)) rescue AbortError => ex if ex.cause.is_a?(Errors::HTTPNotFound) abort Errors::ResourceNotFound.new(ex) end abort(ex.cause) end # Delete all of the environments on the client. The '_default' environment # will never be deleted. # # @return [Array] def delete_all envs = all.reject { |env| env.name.to_s == '_default' } envs.collect { |resource| future(:delete, resource) }.map(&:value) end end end ridley-5.1.1/lib/ridley/resources/node_resource.rb0000644000004100000410000000170713123557300022273 0ustar www-datawww-datamodule Ridley class NodeResource < Ridley::Resource include Ridley::Logging set_resource_path "nodes" represented_by Ridley::NodeObject # @param [Celluloid::Registry] connection_registry def initialize(connection_registry, options = {}) super(connection_registry) end # Merges the given data with the the data of the target node on the remote # # @param [Ridley::NodeResource, String] target # node or identifier of the node to merge # # @option options [Array] :run_list # run list items to merge # @option options [Hash] :attributes # attributes of normal precedence to merge # # @raise [Errors::ResourceNotFound] # if the target node is not found # # @return [Ridley::NodeObject] def merge_data(target, options = {}) unless node = find(target) abort Errors::ResourceNotFound.new end update(node.merge_data(options)) end end end ridley-5.1.1/lib/ridley/resources/data_bag_resource.rb0000644000004100000410000000200013123557300023053 0ustar www-datawww-datamodule Ridley class DataBagResource < Ridley::Resource require_relative 'data_bag_item_resource' set_resource_path "data" represented_by Ridley::DataBagObject attr_reader :item_resource finalizer :finalize_callback # @param [Celluloid::Registry] connection_registry # @param [String] data_bag_secret def initialize(connection_registry, data_bag_secret) super(connection_registry) @item_resource = DataBagItemResource.new_link(connection_registry, data_bag_secret) end # @param [String, #chef_id] object # # @return [nil, Ridley::DataBagResource] def find(object) chef_id = object.respond_to?(:chef_id) ? object.chef_id : object request(:get, "#{self.class.resource_path}/#{chef_id}") new(name: chef_id) rescue AbortError => ex return nil if ex.cause.is_a?(Errors::HTTPNotFound) abort(ex.cause) end private def finalize_callback item_resource.async.terminate if item_resource end end end ridley-5.1.1/lib/ridley/resources/data_bag_item_resource.rb0000644000004100000410000000537613123557300024114 0ustar www-datawww-datamodule Ridley class DataBagItemResource < Ridley::Resource represented_by Ridley::DataBagItemObject attr_reader :encrypted_data_bag_secret # @param [Celluloid::Registry] connection_registry # @param [String] encrypted_data_bag_secret def initialize(connection_registry, encrypted_data_bag_secret) super(connection_registry) @encrypted_data_bag_secret = encrypted_data_bag_secret end # @param [Ridley::DataBagObject] data_bag # # @return [Array] def all(data_bag) request(:get, "#{DataBagResource.resource_path}/#{data_bag.name}").collect do |id, location| new(data_bag, id: id) end end # @param [Ridley::DataBagObject] data_bag # @param [String, #chef_id] object # # @return [Ridley::DataBagItemObject, nil] def find(data_bag, object) chef_id = object.respond_to?(:chef_id) ? object.chef_id : object new(data_bag).from_hash(request(:get, "#{DataBagResource.resource_path}/#{data_bag.name}/#{chef_id}")) rescue AbortError => ex return nil if ex.cause.is_a?(Errors::HTTPNotFound) abort(ex.cause) end # @param [Ridley::DataBagObject] data_bag # @param [#to_hash] object # # @return [Ridley::DataBagItemObject, nil] def create(data_bag, object) resource = new(data_bag, object.to_hash) unless resource.valid? abort Errors::InvalidResource.new(resource.errors) end new_attributes = request(:post, "#{DataBagResource.resource_path}/#{data_bag.name}", resource.to_json) resource.mass_assign(new_attributes) resource end # @param [Ridley::DataBagObject] data_bag # @param [String, #chef_id] object # # @return [Ridley::DataBagItemObject, nil] def delete(data_bag, object) chef_id = object.respond_to?(:chef_id) ? object.chef_id : object new(data_bag).from_hash(request(:delete, "#{DataBagResource.resource_path}/#{data_bag.name}/#{chef_id}")) rescue AbortError => ex return nil if ex.cause.is_a?(Errors::HTTPNotFound) abort(ex.cause) end # @param [Ridley::DataBagObject] data_bag # # @return [Array] def delete_all(data_bag) all(data_bag).collect { |resource| future(:delete, data_bag, resource) }.map(&:value) end # @param [Ridley::DataBagObject] data_bag # @param [#to_hash] object # # @return [Ridley::DataBagItemObject, nil] def update(data_bag, object) resource = new(data_bag, object.to_hash) new(data_bag).from_hash( request(:put, "#{DataBagResource.resource_path}/#{data_bag.name}/#{resource.chef_id}", resource.to_json) ) rescue AbortError => ex return nil if ex.cause.is_a?(Errors::HTTPConflict) abort(ex.cause) end end end ridley-5.1.1/lib/ridley/resources/role_resource.rb0000644000004100000410000000020413123557300022276 0ustar www-datawww-datamodule Ridley class RoleResource < Ridley::Resource set_resource_path "roles" represented_by Ridley::RoleObject end end ridley-5.1.1/lib/ridley/resources/user_resource.rb0000644000004100000410000000227013123557300022320 0ustar www-datawww-datamodule Ridley # @example listing all users # conn = Ridley.new(...) # conn.user.all #=> [ # # # ] class UserResource < Ridley::Resource set_resource_path "users" represented_by Ridley::UserObject # Retrieves a user from the remote connection matching the given chef_id # and regenerates its private key. An instance of the updated object will # be returned and will have a value set for the 'private_key' accessor. # # @param [String, #chef_id] chef_user # # @raise [Errors::ResourceNotFound] # if a user with the given chef_id is not found # # @return [Ridley::UserObject] def regenerate_key(chef_user) unless chef_user = find(chef_user) abort Errors::ResourceNotFound.new("user '#{chef_user}' not found") end chef_user.private_key = true update(chef_user) end def authenticate(username, password) resp = request(:post, '/authenticate_user', {'name' => username, 'password' => password}.to_json) abort("Username mismatch: sent #{username}, received #{resp['name']}") unless resp['name'] == username resp['verified'] end end end ridley-5.1.1/lib/ridley/chef_object.rb0000644000004100000410000000533713123557300017663 0ustar www-datawww-datarequire 'varia_model' module Ridley class ChefObject class << self # @return [String, nil] def chef_id @chef_id end # @param [#to_sym] identifier # # @return [String] def set_chef_id(identifier) @chef_id = identifier.to_sym end # @return [String] def chef_type @chef_type ||= self.class.name.underscore end # @param [#to_s] type # # @return [String] def set_chef_type(type) @chef_type = type.to_s attribute(:chef_type, default: type) end # @return [String, nil] def chef_json_class @chef_json_class end # @param [String, Symbol] klass # # @return [String] def set_chef_json_class(klass) @chef_json_class = klass attribute(:json_class, default: klass) end end include VariaModel include Comparable # @param [Ridley::Resource] resource # @param [Hash] new_attrs def initialize(resource, new_attrs = {}) @resource = resource mass_assign(new_attrs) end # Creates a resource on the target remote or updates one if the resource # already exists. # # @raise [Errors::InvalidResource] # if the resource does not pass validations # # @return [Boolean] def save raise Errors::InvalidResource.new(self.errors) unless valid? mass_assign(resource.create(self)._attributes_) true rescue Errors::HTTPConflict self.update true end # Updates the instantiated resource on the target remote with any changes made # to self # # @raise [Errors::InvalidResource] # if the resource does not pass validations # # @return [Boolean] def update raise Errors::InvalidResource.new(self.errors) unless valid? mass_assign(resource.update(self)._attributes_) true end # Reload the attributes of the instantiated resource # # @return [Object] def reload new_attributes = resource.find(self)._attributes_ @_attributes_ = nil mass_assign(new_attributes) self end # @return [String] def chef_id get_attribute(self.class.chef_id) end def inspect "#<#{self.class} chef_id:#{self.chef_id}, attributes:#{self._attributes_}>" end # @param [Object] other # # @return [Boolean] def <=>(other) self.chef_id <=> other.chef_id end def ==(other) self.chef_id == other.chef_id end # @param [Object] other # # @return [Boolean] def eql?(other) self.class == other.class && self == other end def hash self.chef_id.hash end private attr_reader :resource end end ridley-5.1.1/lib/ridley/sandbox_uploader.rb0000644000004100000410000000574713123557300020766 0ustar www-datawww-datamodule Ridley # @api private class SandboxUploader class << self # Return the checksum of the contents of the file at the given filepath # # @param [String] io # a filepath or an IO # @param [Digest::Base] digest # # @return [String] # the binary checksum of the contents of the file def checksum(io, digest = Digest::MD5.new) while chunk = io.read(1024 * 8) digest.update(chunk) end digest.hexdigest end # Return a base64 encoded checksum of the contents of the given file. This is the expected # format of sandbox checksums given to the Chef Server. # # @param [String] io # a filepath or an IO # # @return [String] # a base64 encoded checksum def checksum64(io) Base64.encode64([checksum(io)].pack("H*")).strip end end include Celluloid attr_reader :client_name attr_reader :client_key attr_reader :options def initialize(client_name, client_key, options = {}) @client_name = client_name @client_key = client_key @options = options end # Upload one file into the sandbox for the given checksum id # # @param [Ridley::SandboxObject] sandbox # @param [String] chk_id # checksum of the file being uploaded # @param [String, #read] file # a filepath or an IO # # @raise [Errors::ChecksumMismatch] # if the given file does not match the expected checksum # # @return [Hash, nil] def upload(sandbox, chk_id, file) checksum = sandbox.checksum(chk_id) unless checksum[:needs_upload] return nil end io = file.respond_to?(:read) ? file : File.new(file, 'rb') calculated_checksum = self.class.checksum64(io) expected_checksum = Base64.encode64([chk_id].pack('H*')).strip unless calculated_checksum == expected_checksum raise Errors::ChecksumMismatch, "Error uploading #{chk_id}. Expected #{expected_checksum} but got #{calculated_checksum}" end headers = { 'Content-Type' => 'application/x-binary', 'content-md5' => calculated_checksum } url = URI(checksum[:url]) upload_path = url.path url.path = "" # versions prior to OSS Chef 11 will strip the port to upload the file to in the checksum # url returned. This will ensure we are uploading to the proper location. if sandbox.send(:resource).connection.foss? url.port = URI(sandbox.send(:resource).connection.server_url).port end begin io.rewind Faraday.new(url, self.options) do |c| c.response :chef_response c.response :follow_redirects c.request :chef_auth, self.client_name, self.client_key c.adapter :httpclient end.put(upload_path, io.read, headers) rescue Ridley::Errors::HTTPError => ex abort(ex) end end end end ridley-5.1.1/lib/ridley/logger.rb0000644000004100000410000000333613123557300016704 0ustar www-datawww-datamodule Ridley module Logging class Logger < Logger def initialize(device = STDOUT) super self.level = Logger::WARN @filter_params = Array.new end # Reimplements Logger#add adding message filtering. The info, # warn, debug, error, and fatal methods all call add. # # @param [Fixnum] severity # an integer measuing the severity - Logger::INFO, etc. # @param [String] message = nil # the message to log # @param [String] progname = nil # the program name performing the logging # @param &block # a block that will be evaluated (for complicated logging) # # @example # log.filter_param("hello") # log.info("hello world!") => "FILTERED world!" # # @return [Boolean] def add(severity, message = nil, progname = nil, &block) severity ||= Logger::UNKNOWN if @logdev.nil? or severity < @level return true end progname ||= @progname if message.nil? if block_given? message = yield else message = progname progname = @progname end end @logdev.write( format_message(format_severity(severity), Time.now, progname, filter(message))) true end def filter_params @filter_params.dup end def filter_param(param) @filter_params << param unless filter_params.include?(param) end def clear_filter_params @filter_params.clear end def filter(message) filter_params.each do |param| message.gsub!(param.to_s, 'FILTERED') end message end end end end ridley-5.1.1/lib/ridley/client.rb0000644000004100000410000002404513123557300016703 0ustar www-datawww-datarequire 'ridley/helpers' module Ridley class Client class ConnectionSupervisor < ::Celluloid::SupervisionGroup def initialize(registry, options) super(registry) pool(Ridley::Connection, size: options[:pool_size], args: [ options[:server_url], options[:client_name], options[:client_key], Ridley::Helpers.options_slice(options, *Ridley::Connection::VALID_OPTIONS) ], as: :connection_pool) end end class ResourcesSupervisor < ::Celluloid::SupervisionGroup def initialize(registry, connection_registry, options) super(registry) supervise_as :client_resource, Ridley::ClientResource, connection_registry supervise_as :cookbook_resource, Ridley::CookbookResource, connection_registry, options[:client_name], options[:client_key], Ridley::Helpers.options_slice(options, *Ridley::Connection::VALID_OPTIONS) supervise_as :data_bag_resource, Ridley::DataBagResource, connection_registry, options[:encrypted_data_bag_secret] supervise_as :environment_resource, Ridley::EnvironmentResource, connection_registry supervise_as :node_resource, Ridley::NodeResource, connection_registry, options supervise_as :role_resource, Ridley::RoleResource, connection_registry supervise_as :sandbox_resource, Ridley::SandboxResource, connection_registry, options[:client_name], options[:client_key], Ridley::Helpers.options_slice(options, *Ridley::Connection::VALID_OPTIONS) supervise_as :search_resource, Ridley::SearchResource, connection_registry supervise_as :user_resource, Ridley::UserResource, connection_registry end end class << self def open(options = {}, &block) client = new(options) yield client ensure client.terminate if client && client.alive? end # @raise [ArgumentError] # # @return [Boolean] def validate_options(options) missing = (REQUIRED_OPTIONS - options.keys) if missing.any? missing.collect! { |opt| "'#{opt}'" } raise ArgumentError, "Missing required option(s): #{missing.join(', ')}" end missing_values = options.slice(*REQUIRED_OPTIONS).select { |key, value| !value.present? } if missing_values.any? values = missing_values.keys.collect { |opt| "'#{opt}'" } raise ArgumentError, "Missing value for required option(s): '#{values.join(', ')}'" end end end REQUIRED_OPTIONS = [ :server_url, :client_name, :client_key ].freeze extend Forwardable include Celluloid include Ridley::Logging finalizer :finalize_callback def_delegator :connection, :build_url def_delegator :connection, :scheme def_delegator :connection, :host def_delegator :connection, :port def_delegator :connection, :path_prefix def_delegator :connection, :url_prefix def_delegator :connection, :organization def_delegator :connection, :client_key def_delegator :connection, :client_key= def_delegator :connection, :client_name def_delegator :connection, :client_name= attr_reader :options attr_accessor :validator_client attr_accessor :validator_path attr_accessor :encrypted_data_bag_secret_path attr_accessor :chef_version # @option options [String] :server_url # URL to the Chef API # @option options [String] :client_name # name of the client used to authenticate with the Chef API # @option options [String] :client_key # filepath to the client's private key used to authenticate with the Chef API # @option options [String] :validator_client (nil) # @option options [String] :validator_path (nil) # @option options [String] :encrypted_data_bag_secret_path (nil) # @option options [String] :chef_version # the version of Chef to use when bootstrapping # @option options [Hash] :params # URI query unencoded key/value pairs # @option options [Hash] :headers # unencoded HTTP header key/value pairs # @option options [Hash] :request # request options # @option options [Hash] :ssl # * :verify (Boolean) [true] set to false to disable SSL verification # @option options [URI, String, Hash] :proxy # URI, String, or Hash of HTTP proxy options # @option options [Integer] :pool_size (4) # size of the connection pool # # @raise [Errors::ClientKeyFileNotFoundOrInvalid] if the option for :client_key does not contain # a file path pointing to a readable client key, or is a string containing a valid key def initialize(options = {}) @options = options.reverse_merge( pool_size: 4 ).deep_symbolize_keys self.class.validate_options(@options) @chef_version = @options[:chef_version] @validator_client = @options[:validator_client] if @options[:validator_path] @validator_path = File.expand_path(@options[:validator_path]) end @options[:encrypted_data_bag_secret] ||= begin if @options[:encrypted_data_bag_secret_path] @encrypted_data_bag_secret_path = File.expand_path(@options[:encrypted_data_bag_secret_path]) end encrypted_data_bag_secret end unless verify_client_key(@options[:client_key]) @options[:client_key] = @options[:client_key].call if @options[:client_key].kind_of? Proc @options[:client_key] = File.expand_path(@options[:client_key]) raise Errors::ClientKeyFileNotFoundOrInvalid, "client key is invalid or not found at: '#{@options[:client_key]}'" unless File.exist?(@options[:client_key]) && verify_client_key(::IO.read(@options[:client_key])) end @connection_registry = Celluloid::Registry.new @resources_registry = Celluloid::Registry.new @connection_supervisor = ConnectionSupervisor.new(@connection_registry, @options) @resources_supervisor = ResourcesSupervisor.new(@resources_registry, @connection_registry, @options) end # @return [Ridley::ClientResource] def client @resources_registry[:client_resource] end # @return [Ridley::CookbookResource] def cookbook @resources_registry[:cookbook_resource] end # @return [Ridley::DataBagResource] def data_bag @resources_registry[:data_bag_resource] end # @return [Ridley::EnvironmentResource] def environment @resources_registry[:environment_resource] end # @return [Ridley::NodeResource] def node @resources_registry[:node_resource] end # @return [Ridley::RoleResource] def role @resources_registry[:role_resource] end # @return [Ridley::SandboxResource] def sandbox @resources_registry[:sandbox_resource] end # @return [Ridley::UserResource] def user @resources_registry[:user_resource] end # Perform a search the Chef Server # # @param [#to_sym, #to_s] index # @param [#to_s] query # # @option options [String] :sort # a sort string such as 'name DESC' # @option options [Integer] :rows # how many rows to return # @option options [Integer] :start # the result number to start from # # @return [Array, Hash] def search(index, query = nil, options = {}) @resources_registry[:search_resource].run(index, query, @resources_registry, options) end # Return an array of all possible search indexes for the including connection # # @example # ridley = Ridley.new(...) # ridley.search_indexes #=> # [:client, :environment, :node, :role, :"ridley-two", :"ridley-one"] # # @return [Array] def search_indexes @resources_registry[:search_resource].indexes end # Perform a partial search on the Chef Server. Partial objects or a smaller hash will be returned resulting # in a faster response for larger response sets. Specify the attributes you want returned with the # attributes parameter. # # @param [#to_sym, #to_s] index # @param [#to_s] query # @param [Array] attributes # an array of strings in dotted hash notation representing the attributes to return # # @option options [String] :sort # a sort string such as 'name DESC' # @option options [Integer] :rows # how many rows to return # @option options [Integer] :start # the result number to start from # # @example # ridley = Ridley.new(...) # ridley.partial_search(:node, "chef_environment:RESET", [ 'ipaddress', 'some.application.setting' ]) #=> # [ # # "192.168.1.1", "some" => { "application" => { "setting" => "value" } } } ...> # ] # # @return [Array, Hash] def partial_search(index, query = nil, attributes = [], options = {}) @resources_registry[:search_resource].partial(index, query, Array(attributes), @resources_registry, options) end def universe connection.send(:get, "universe").body rescue Errors::HTTPError, Errors::ClientError => ex abort(ex) end # The encrypted data bag secret for this connection. # # @raise [Ridley::Errors::EncryptedDataBagSecretNotFound] # # @return [String, nil] def encrypted_data_bag_secret return nil if encrypted_data_bag_secret_path.nil? ::IO.read(encrypted_data_bag_secret_path).chomp rescue Errno::ENOENT => e raise Errors::EncryptedDataBagSecretNotFound, "Encrypted data bag secret provided but not found at '#{encrypted_data_bag_secret_path}'" end def server_url self.url_prefix.to_s end private def verify_client_key(key) OpenSSL::PKey::RSA.new(key) true rescue false end def connection @connection_registry[:connection_pool] end def finalize_callback @connection_supervisor.async.terminate if @connection_supervisor @resources_supervisor.async.terminate if @resources_supervisor end end end ridley-5.1.1/lib/ridley/chef/0000755000004100000410000000000013123557300016000 5ustar www-datawww-dataridley-5.1.1/lib/ridley/chef/chefignore.rb0000644000004100000410000000214213123557300020435 0ustar www-datawww-datarequire 'buff/ignore' module Ridley::Chef class Chefignore < Buff::Ignore::IgnoreFile include Ridley::Logging # The filename of the chefignore # # @return [String] FILENAME = 'chefignore'.freeze # Create a new chefignore # # @param [#to_s] path # the path to find a chefignore from (default: `Dir.pwd`) def initialize(path = Dir.pwd) ignore = chefignore(path) if ignore log.debug "Using '#{FILENAME}' at '#{ignore}'" end super(ignore, base: path) end private # Find the chefignore file in the current directory # # @return [String, nil] # the path to the chefignore file or nil if one was not # found def chefignore(path) Pathname.new(path).ascend do |dir| next unless dir.directory? [ dir.join(FILENAME), dir.join('cookbooks', FILENAME), dir.join('.chef', FILENAME), ].each do |possible| return possible.expand_path.to_s if possible.exist? end end nil end end end ridley-5.1.1/lib/ridley/chef/cookbook/0000755000004100000410000000000013123557300017606 5ustar www-datawww-dataridley-5.1.1/lib/ridley/chef/cookbook/metadata.rb0000644000004100000410000006662313123557300021730 0ustar www-datawww-datamodule Ridley::Chef class Cookbook # Borrowed and modified from: {https://raw.github.com/opscode/chef/11.4.0/lib/chef/cookbook/metadata.rb} # # Copyright:: Copyright 2008-2010 Opscode, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # == Chef::Cookbook::Metadata # Chef::Cookbook::Metadata provides a convenient DSL for declaring metadata # about Chef Cookbooks. class Metadata class << self def from_hash(hash) new.from_hash(hash) end def from_json(json) new.from_json(json) end end NAME = 'name'.freeze DESCRIPTION = 'description'.freeze LONG_DESCRIPTION = 'long_description'.freeze MAINTAINER = 'maintainer'.freeze MAINTAINER_EMAIL = 'maintainer_email'.freeze LICENSE = 'license'.freeze PLATFORMS = 'platforms'.freeze DEPENDENCIES = 'dependencies'.freeze RECOMMENDATIONS = 'recommendations'.freeze SUGGESTIONS = 'suggestions'.freeze CONFLICTING = 'conflicting'.freeze PROVIDING = 'providing'.freeze REPLACING = 'replacing'.freeze ATTRIBUTES = 'attributes'.freeze GROUPINGS = 'groupings'.freeze RECIPES = 'recipes'.freeze VERSION = 'version'.freeze SOURCE_URL = 'source_url'.freeze ISSUES_URL = 'issues_url'.freeze PRIVACY = "privacy".freeze CHEF_VERSIONS = "chef_versions".freeze OHAI_VERSIONS = "ohai_versions".freeze GEMS = "gems".freeze COMPILED_FILE_NAME = "metadata.json".freeze RAW_FILE_NAME = "metadata.rb".freeze COMPARISON_FIELDS = [ :name, :description, :long_description, :maintainer, :maintainer_email, :license, :platforms, :dependencies, :recommendations, :suggestions, :conflicting, :providing, :replacing, :attributes, :groupings, :recipes, :version, :source_url, :issues_url, :privacy, :chef_versions, :ohai_versions, :gems ] include Ridley::Mixin::ParamsValidate include Ridley::Mixin::FromFile attr_reader :cookbook attr_reader :platforms attr_reader :dependencies attr_reader :recommendations attr_reader :suggestions attr_reader :conflicting attr_reader :providing attr_reader :replacing attr_reader :attributes attr_reader :groupings attr_reader :recipes attr_reader :version # @return [Array] Array of supported Chef versions attr_reader :chef_versions # @return [Array] Array of supported Ohai versions attr_reader :ohai_versions # @return [Array] Array of gems to install with *args as an Array attr_reader :gems # Builds a new Chef::Cookbook::Metadata object. # # === Parameters # cookbook:: An optional cookbook object # maintainer:: An optional maintainer # maintainer_email:: An optional maintainer email # license::An optional license. Default is Apache v2.0 # # === Returns # metadata def initialize(cookbook = nil, maintainer = 'YOUR_COMPANY_NAME', maintainer_email = 'YOUR_EMAIL', license = 'none') @cookbook = cookbook @name = cookbook ? cookbook.name : "" @long_description = "" self.maintainer(maintainer) self.maintainer_email(maintainer_email) self.license(license) self.description('A fabulous new cookbook') @platforms = Hashie::Mash.new @dependencies = Hashie::Mash.new @recommendations = Hashie::Mash.new @suggestions = Hashie::Mash.new @conflicting = Hashie::Mash.new @providing = Hashie::Mash.new @replacing = Hashie::Mash.new @attributes = Hashie::Mash.new @groupings = Hashie::Mash.new @recipes = Hashie::Mash.new @version = Semverse::Version.new("0.0.0") @source_url = '' @issues_url = '' @privacy = false @chef_versions = [] @ohai_versions = [] @gems = [] if cookbook @recipes = cookbook.fully_qualified_recipe_names.inject({}) do |r, e| e = self.name if e =~ /::default$/ r[e] = "" self.provides e r end end end def ==(other) COMPARISON_FIELDS.inject(true) do |equal_so_far, field| equal_so_far && other.respond_to?(field) && (other.send(field) == send(field)) end end # Ensure that we don't have to update Ridley every time we add # a new metadata field to Chef def method_missing(method_sym, *args) Ridley::Logging.logger.warn "Ignoring unknown metadata" end # Sets the cookbooks maintainer, or returns it. # # === Parameters # maintainer:: The maintainers name # # === Returns # maintainer:: Returns the current maintainer. def maintainer(arg = nil) set_or_return( :maintainer, arg, :kind_of => [ String ] ) end # Sets the maintainers email address, or returns it. # # === Parameters # maintainer_email:: The maintainers email address # # === Returns # maintainer_email:: Returns the current maintainer email. def maintainer_email(arg = nil) set_or_return( :maintainer_email, arg, :kind_of => [ String ] ) end # Sets the current license, or returns it. # # === Parameters # license:: The current license. # # === Returns # license:: Returns the current license def license(arg = nil) set_or_return( :license, arg, :kind_of => [ String ] ) end # Sets the current description, or returns it. Should be short - one line only! # # === Parameters # description:: The new description # # === Returns # description:: Returns the description def description(arg = nil) set_or_return( :description, arg, :kind_of => [ String ] ) end # Sets the current long description, or returns it. Might come from a README, say. # # === Parameters # long_description:: The new long description # # === Returns # long_description:: Returns the long description def long_description(arg = nil) set_or_return( :long_description, arg, :kind_of => [ String ] ) end # Sets the current cookbook version, or returns it. Can be two or three digits, seperated # by dots. ie: '2.1', '1.5.4' or '0.9'. # # === Parameters # version:: The curent version, as a string # # === Returns # version:: Returns the current version def version(arg = nil) if arg @version = Semverse::Version.new(arg) end @version.to_s end # Sets the name of the cookbook, or returns it. # # === Parameters # name:: The curent cookbook name. # # === Returns # name:: Returns the current cookbook name. def name(arg = nil) set_or_return( :name, arg, :kind_of => [ String ] ) end # Adds a supported platform, with version checking strings. # # === Parameters # platform,:: The platform (like :ubuntu or :mac_os_x) # version:: A version constraint of the form "OP VERSION", # where OP is one of < <= = > >= ~> and VERSION has # the form x.y.z or x.y. # # === Returns # versions:: Returns the list of versions for the platform def supports(platform, *version_args) version = version_args.first @platforms[platform] = Semverse::Constraint.new(version).to_s @platforms[platform] end # Adds a dependency on another cookbook, with version checking strings. # # === Parameters # cookbook:: The cookbook # version:: A version constraint of the form "OP VERSION", # where OP is one of < <= = > >= ~> and VERSION has # the form x.y.z or x.y. # # === Returns # versions:: Returns the list of versions for the platform def depends(cookbook, *version_args) version = version_args.first @dependencies[cookbook] = Semverse::Constraint.new(version).to_s @dependencies[cookbook] end # Adds a recommendation for another cookbook, with version checking strings. # # === Parameters # cookbook:: The cookbook # version:: A version constraint of the form "OP VERSION", # where OP is one of < <= = > >= ~> and VERSION has # the form x.y.z or x.y. # # === Returns # versions:: Returns the list of versions for the platform def recommends(cookbook, *version_args) version = version_args.first @recommendations[cookbook] = Semverse::Constraint.new(version).to_s @recommendations[cookbook] end # Adds a suggestion for another cookbook, with version checking strings. # # === Parameters # cookbook:: The cookbook # version:: A version constraint of the form "OP VERSION", # where OP is one of < <= = > >= ~> and VERSION has the # formx.y.z or x.y. # # === Returns # versions:: Returns the list of versions for the platform def suggests(cookbook, *version_args) version = version_args.first @suggestions[cookbook] = Semverse::Constraint.new(version).to_s @suggestions[cookbook] end # Adds a conflict for another cookbook, with version checking strings. # # === Parameters # cookbook:: The cookbook # version:: A version constraint of the form "OP VERSION", # where OP is one of < <= = > >= ~> and VERSION has # the form x.y.z or x.y. # # === Returns # versions:: Returns the list of versions for the platform def conflicts(cookbook, *version_args) version = version_args.first @conflicting[cookbook] = Semverse::Constraint.new(version).to_s @conflicting[cookbook] end # Adds a recipe, definition, or resource provided by this cookbook. # # Recipes are specified as normal # Definitions are followed by (), and can include :params for prototyping # Resources are the stringified version (service[apache2]) # # === Parameters # recipe, definition, resource:: The thing we provide # version:: A version constraint of the form "OP VERSION", # where OP is one of < <= = > >= ~> and VERSION has # the form x.y.z or x.y. # # === Returns # versions:: Returns the list of versions for the platform def provides(cookbook, *version_args) version = version_args.first @providing[cookbook] = Semverse::Constraint.new(version).to_s @providing[cookbook] end # Adds a cookbook that is replaced by this one, with version checking strings. # # === Parameters # cookbook:: The cookbook we replace # version:: A version constraint of the form "OP VERSION", # where OP is one of < <= = > >= ~> and VERSION has the form x.y.z or x.y. # # === Returns # versions:: Returns the list of versions for the platform def replaces(cookbook, *version_args) version = version_args.first @replacing[cookbook] = Semverse::Constraint.new(version).to_s @replacing[cookbook] end # Metadata DSL to set a valid chef_version. May be declared multiple times # with the result being 'OR'd such that if any statements match, the version # is considered supported. Uses Gem::Requirement for its implementation. # # @param version_args [Array] Version constraint in String form # @return [Array] Current chef_versions array def chef_version(*version_args) @chef_versions << Gem::Dependency.new("chef", *version_args) unless version_args.empty? @chef_versions end # Metadata DSL to set a valid ohai_version. May be declared multiple times # with the result being 'OR'd such that if any statements match, the version # is considered supported. Uses Gem::Requirement for its implementation. # # @param version_args [Array] Version constraint in String form # @return [Array] Current ohai_versions array def ohai_version(*version_args) @ohai_versions << Gem::Dependency.new("ohai", *version_args) unless version_args.empty? @ohai_versions end # Metadata DSL to set a gem to install from the cookbook metadata. May be declared # multiple times. All the gems from all the cookbooks are combined into one Gemfile # and depsolved together. Uses Bundler's DSL for its implementation. # # @param args [Array] Gem name and options to pass to Bundler's DSL # @return [Array] Array of gem statements as args def gem(*args) @gems << args unless args.empty? @gems end # Adds a description for a recipe. # # === Parameters # recipe:: The recipe # description:: The description of the recipe # # === Returns # description:: Returns the current description def recipe(name, description) @recipes[name] = description end # Adds an attribute )hat a user needs to configure for this cookbook. Takes # a name (with the / notation for a nested attribute), followed by any of # these options # # display_name:: What a UI should show for this attribute # description:: A hint as to what this attr is for # choice:: An array of choices to present to the user. # calculated:: If true, the default value is calculated by the recipe and cannot be displayed. # type:: "string" or "array" - default is "string" ("hash" is supported for backwards compatibility) # required:: Whether this attr is 'required', 'recommended' or 'optional' - default 'optional' (true/false values also supported for backwards compatibility) # recipes:: An array of recipes which need this attr set. # default,,:: The default value # # === Parameters # name:: The name of the attribute ('foo', or 'apache2/log_dir') # options:: The description of the options # # === Returns # options:: Returns the current options hash def attribute(name, options) validate( options, { :display_name => { :kind_of => String }, :description => { :kind_of => String }, :choice => { :kind_of => [ Array ], :default => [] }, :calculated => { :equal_to => [ true, false ], :default => false }, :type => { :equal_to => [ "string", "array", "hash", "symbol", "boolean", "numeric" ], :default => "string" }, :required => { :equal_to => [ "required", "recommended", "optional", true, false ], :default => "optional" }, :recipes => { :kind_of => [ Array ], :default => [] }, :default => { :kind_of => [ String, Array, Hash, Symbol, Numeric, TrueClass, FalseClass ] }, :source_url => { :kind_of => String }, :issues_url => { :kind_of => String }, :privacy => { :kind_of => [ TrueClass, FalseClass ] }, } ) options[:required] = remap_required_attribute(options[:required]) unless options[:required].nil? validate_choice_array(options) validate_calculated_default_rule(options) validate_choice_default_rule(options) @attributes[name] = options @attributes[name] end def grouping(name, options) validate( options, { :title => { :kind_of => String }, :description => { :kind_of => String } } ) @groupings[name] = options @groupings[name] end # Convert an Array of Gem::Dependency objects (chef_version/ohai_version) to an Array. # # Gem::Dependencey#to_s is not useful, and there is no #to_json defined on it or its component # objets, so we have to write our own rendering method. # # [ Gem::Dependency.new(">= 12.5"), Gem::Dependency.new(">= 11.18.0", "< 12.0") ] # # results in: # # [ [ ">= 12.5" ], [ ">= 11.18.0", "< 12.0" ] ] # # @param deps [Array] Multiple Gem-style version constraints # @return [Array]] Simple object representation of version constraints (for json) def gem_requirements_to_array(*deps) deps.map do |dep| dep.requirement.requirements.map do |op, version| "#{op} #{version}" end.sort end end # Convert an Array of Gem::Dependency objects (chef_version/ohai_version) to a hash. # # This is the inverse of #gem_requirements_to_array # # @param what [String] What version constraint we are constructing ('chef' or 'ohai' presently) # @param array [Array]] Simple object representation of version constraints (from json) # @return [Array] Multiple Gem-style version constraints def gem_requirements_from_array(what, array) array.map do |dep| Gem::Dependency.new(what, *dep) end end def to_hash { NAME => self.name, DESCRIPTION => self.description, LONG_DESCRIPTION => self.long_description, MAINTAINER => self.maintainer, MAINTAINER_EMAIL => self.maintainer_email, LICENSE => self.license, PLATFORMS => self.platforms, DEPENDENCIES => self.dependencies, RECOMMENDATIONS => self.recommendations, SUGGESTIONS => self.suggestions, CONFLICTING => self.conflicting, PROVIDING => self.providing, REPLACING => self.replacing, ATTRIBUTES => self.attributes, GROUPINGS => self.groupings, RECIPES => self.recipes, VERSION => self.version, SOURCE_URL => self.source_url, ISSUES_URL => self.issues_url, PRIVACY => self.privacy, CHEF_VERSIONS => gem_requirements_to_array(*self.chef_versions), OHAI_VERSIONS => gem_requirements_to_array(*self.ohai_versions), GEMS => self.gems, } end # @return [String] def to_json # Switched from fast to pretty generate here # to match `knife cookbook metadata from file` format # See https://github.com/RiotGames/ridley/pull/287 JSON.pretty_generate(to_hash) end def from_hash(o) @name = o[NAME] if o.has_key?(NAME) @description = o[DESCRIPTION] if o.has_key?(DESCRIPTION) @long_description = o[LONG_DESCRIPTION] if o.has_key?(LONG_DESCRIPTION) @maintainer = o[MAINTAINER] if o.has_key?(MAINTAINER) @maintainer_email = o[MAINTAINER_EMAIL] if o.has_key?(MAINTAINER_EMAIL) @license = o[LICENSE] if o.has_key?(LICENSE) @platforms = handle_deprecated_constraints(o[PLATFORMS]) if o.has_key?(PLATFORMS) @dependencies = handle_deprecated_constraints(o[DEPENDENCIES]) if o.has_key?(DEPENDENCIES) @recommendations = handle_deprecated_constraints(o[RECOMMENDATIONS]) if o.has_key?(RECOMMENDATIONS) @suggestions = handle_deprecated_constraints(o[SUGGESTIONS]) if o.has_key?(SUGGESTIONS) @conflicting = handle_deprecated_constraints(o[CONFLICTING]) if o.has_key?(CONFLICTING) @providing = o[PROVIDING] if o.has_key?(PROVIDING) @replacing = handle_deprecated_constraints(o[REPLACING]) if o.has_key?(REPLACING) @attributes = o[ATTRIBUTES] if o.has_key?(ATTRIBUTES) @groupings = o[GROUPINGS] if o.has_key?(GROUPINGS) @recipes = o[RECIPES] if o.has_key?(RECIPES) @version = o[VERSION] if o.has_key?(VERSION) @source_url = o[SOURCE_URL] if o.has_key?(SOURCE_URL) @issues_url = o[ISSUES_URL] if o.has_key?(ISSUES_URL) @privacy = o[PRIVACY] if o.has_key?(PRIVACY) @chef_versions = gem_requirements_from_array("chef", o[CHEF_VERSIONS]) if o.has_key?(CHEF_VERSIONS) @ohai_versions = gem_requirements_from_array("ohai", o[OHAI_VERSIONS]) if o.has_key?(OHAI_VERSIONS) @gems = o[GEMS] if o.has_key?(GEMS) self end def from_json(json) from_hash JSON.parse(json) end # Sets the cookbook's source URL, or returns it. # # === Parameters # maintainer:: The source URL # # === Returns # source_url:: Returns the current source URL. def source_url(arg = nil) set_or_return( :source_url, arg, :kind_of => [ String ] ) end # Sets the cookbook's issues URL, or returns it. # # === Parameters # issues_url:: The issues URL # # === Returns # issues_url:: Returns the current issues URL. def issues_url(arg = nil) set_or_return( :issues_url, arg, :kind_of => [ String ] ) end # # Sets the cookbook's privacy flag, or returns it. # # === Parameters # privacy:: Whether this cookbook is private or not # # === Returns # privacy:: Whether this cookbook is private or not # def privacy(arg = nil) set_or_return( :privacy, arg, :kind_of => [ TrueClass, FalseClass ], ) end private # Verify that the given array is an array of strings # # Raise an exception if the members of the array are not Strings # # === Parameters # arry:: An array to be validated def validate_string_array(arry) if arry.kind_of?(Array) arry.each do |choice| validate( {:choice => choice}, {:choice => {:kind_of => String}} ) end end end # Validate the choice of the options hash # # Raise an exception if the members of the array do not match the defaults # === Parameters # opts:: The options hash def validate_choice_array(opts) if opts[:choice].kind_of?(Array) case opts[:type] when "string" validator = [ String ] when "array" validator = [ Array ] when "hash" validator = [ Hash ] when "symbol" validator = [ Symbol ] when "boolean" validator = [ TrueClass, FalseClass ] when "numeric" validator = [ Numeric ] end opts[:choice].each do |choice| validate( {:choice => choice}, {:choice => {:kind_of => validator}} ) end end end # For backwards compatibility, remap Boolean values to String # true is mapped to "required" # false is mapped to "optional" # # === Parameters # required_attr:: The value of options[:required] # # === Returns # required_attr:: "required", "recommended", or "optional" def remap_required_attribute(value) case value when true value = "required" when false value = "optional" end value end def validate_calculated_default_rule(options) calculated_conflict = ((options[:default].is_a?(Array) && !options[:default].empty?) || (options[:default].is_a?(String) && !options[:default] != "")) && options[:calculated] == true raise ArgumentError, "Default cannot be specified if calculated is true!" if calculated_conflict end def validate_choice_default_rule(options) return if !options[:choice].is_a?(Array) || options[:choice].empty? if options[:default].is_a?(String) && options[:default] != "" raise ArgumentError, "Default must be one of your choice values!" if options[:choice].index(options[:default]) == nil end if options[:default].is_a?(Array) && !options[:default].empty? options[:default].each do |val| raise ArgumentError, "Default values must be a subset of your choice values!" if options[:choice].index(val) == nil end end end # This method translates version constraint strings from # cookbooks with the old format. # # Before we began respecting version constraints, we allowed # multiple constraints to be placed on cookbooks, as well as the # << and >> operators, which are now just < and >. For # specifications with more than one constraint, this method switches to # the default constraint from SemVerse. If there is only one # constraint, we are replacing the old << and >> with the new < # and >. def handle_deprecated_constraints(specification) specification.inject(Hashie::Mash.new) do |acc, (cb, constraints)| constraints = Array(constraints) acc[cb] = if constraints.size == 1 constraints.first.gsub(/>>/, '>').gsub(/< [ # { # name: "default.rb", # path: "recipes/default.rb", # checksum: "fb1f925dcd5fc4ebf682c4442a21c619", # specificity: "default" # } # ] # ... # ... # } attr_reader :manifest # @return [Boolean] attr_accessor :frozen def_delegator :@metadata, :version def initialize(name, path, metadata) @cookbook_name = name @path = Pathname.new(path) @metadata = metadata @frozen = false @chefignore = Ridley::Chef::Chefignore.new(@path) rescue nil clear_files load_files end # @return [Hash] # an hash containing the checksums and expanded file paths of all of the # files found in the instance of CachedCookbook # # example: # { # "da97c94bb6acb2b7900cbf951654fea3" => "/Users/reset/.ridley/nginx-0.101.2/README.md" # } def checksums {}.tap do |checksums| files.each do |file| checksums[self.class.checksum(file)] = file end end end # Compiles the raw metadata of the cookbook and writes it to a metadata.json file at the given # out path. The default out path is the directory containing the cookbook itself. # # @param [String] out # directory to output compiled metadata to # # @return [String] # path to the compiled metadata def compile_metadata(out = self.path) filepath = File.join(out, Metadata::COMPILED_FILE_NAME) File.open(filepath, "wb+") do |f| f.write(metadata.to_json) end filepath end # Returns true if the cookbook instance has a compiled metadata file and false if it # does not. # # @return [Boolean] def compiled_metadata? manifest[:root_files].any? { |file| file[:name].downcase == Metadata::COMPILED_FILE_NAME } end # @param [Symbol] category # the category of file to generate metadata about # @param [String] target # the filepath to the file to get metadata information about # # @return [Hash] # a Hash containing a name, path, checksum, and specificity key representing the # metadata about a file contained in a Cookbook. This metadata is used when # uploading a Cookbook's files to a Chef Server. # # @example # file_metadata(:root_files, "somefile.h") => { # name: "default.rb", # path: "recipes/default.rb", # checksum: "fb1f925dcd5fc4ebf682c4442a21c619", # specificity: "default" # } def file_metadata(category, target) target = Pathname.new(target) { name: target.basename.to_s, path: target.relative_path_from(path).to_s, checksum: self.class.checksum(target), specificity: file_specificity(category, target) } end # @param [Symbol] category # @param [Pathname] target # # @return [String] def file_specificity(category, target) case category when :files, :templates relpath = target.relative_path_from(path).to_s relpath.slice(/(.+)\/(.+)\/.+/, 2) || 'root_default' else 'default' end end # @return [String] # the name of the cookbook and the version number separated by a dash (-). # # example: # "nginx-0.101.2" def name "#{cookbook_name}-#{version}" end # Reload the cookbook from the files located on disk at `#path`. def reload clear_files load_files end def validate raise IOError, "No Cookbook found at: #{path}" unless path.exist? unless syntax_checker.validate_ruby_files raise Ridley::Errors::CookbookSyntaxError, "Invalid ruby files in cookbook: #{cookbook_name} (#{version})." end unless syntax_checker.validate_templates raise Ridley::Errors::CookbookSyntaxError, "Invalid template files in cookbook: #{cookbook_name} (#{version})." end true end def to_hash result = manifest.dup result[:chef_type] = CHEF_TYPE result[:name] = name result[:cookbook_name] = cookbook_name result[:version] = version result[:metadata] = metadata.to_hash result[:frozen?] = frozen result end def to_json(*args) result = self.to_hash result['json_class'] = CHEF_JSON_CLASS result.to_json(*args) end def to_s "#{cookbook_name} (#{version}) '#{path}'" end def <=>(other) [self.cookbook_name, self.version] <=> [other.cookbook_name, other.version] end private # @return [Array] attr_reader :files # @return [Ridley::Chef::Chefignore, nil] attr_reader :chefignore def clear_files @files = Array.new @manifest = Hashie::Mash.new( recipes: Array.new, definitions: Array.new, libraries: Array.new, attributes: Array.new, files: Array.new, templates: Array.new, resources: Array.new, providers: Array.new, root_files: Array.new ) end def load_files load_shallow(:recipes, 'recipes', '*.rb') load_shallow(:definitions, 'definitions', '*.rb') load_shallow(:attributes, 'attributes', '*.rb') load_recursively(:libraries, 'libraries', '*.rb') load_recursively(:files, "files", "*") load_recursively(:templates, "templates", "*") load_recursively(:resources, "resources", "*.rb") load_recursively(:providers, "providers", "*.rb") load_root end def load_root [].tap do |files| Dir.glob(path.join('*'), File::FNM_DOTMATCH).each do |file| next if File.directory?(file) next if ignored?(file) @files << file @manifest[:root_files] << file_metadata(:root_files, file) end end end def load_recursively(category, category_dir, glob) [].tap do |files| file_spec = path.join(category_dir, '**', glob) Dir.glob(file_spec, File::FNM_DOTMATCH).each do |file| next if File.directory?(file) next if ignored?(file) @files << file @manifest[category] << file_metadata(category, file) end end end def load_shallow(category, *path_glob) [].tap do |files| Dir[path.join(*path_glob)].each do |file| next if ignored?(file) @files << file @manifest[category] << file_metadata(category, file) end end end def syntax_checker @syntax_checker ||= Cookbook::SyntaxCheck.new(path.to_s, chefignore) end # Determine if the given file should be ignored by the chefignore # # @return [Boolean] # true if it should be ignored, false otherwise def ignored?(file) !!chefignore && chefignore.ignored?(file) end end end ridley-5.1.1/lib/ridley/chef/digester.rb0000644000004100000410000000220413123557300020131 0ustar www-datawww-datarequire 'digest' module Ridley::Chef # Borrowed and modified from: {https://github.com/opscode/chef/blob/11.4.0/lib/chef/digester.rb} class Digester class << self def instance @instance ||= new end def checksum_for_file(*args) instance.checksum_for_file(*args) end def md5_checksum_for_file(*args) instance.generate_md5_checksum_for_file(*args) end end def validate_checksum(*args) self.class.validate_checksum(*args) end def checksum_for_file(file) generate_checksum(file) end def generate_checksum(file) checksum_file(file, Digest::SHA256.new) end def generate_md5_checksum_for_file(file) checksum_file(file, Digest::MD5.new) end def generate_md5_checksum(io) checksum_io(io, Digest::MD5.new) end private def checksum_file(file, digest) File.open(file, 'rb') do |f| checksum_io(f, digest) end end def checksum_io(io, digest) while chunk = io.read(1024 * 8) digest.update(chunk) end digest.hexdigest end end end ridley-5.1.1/lib/ridley/chef/config.rb0000644000004100000410000000247513123557300017602 0ustar www-datawww-datarequire 'chef-config/config' require 'chef-config/workstation_config_loader' require 'socket' module Ridley::Chef class Config # Create a new Chef Config object. # # @param [#to_s] path # the path to the configuration file # @param [Hash] options def initialize(path, options = {}) ChefConfig::WorkstationConfigLoader.new(path).load ChefConfig::Config.merge!(options) ChefConfig::Config.export_proxies # Set proxy settings as environment variables end # Keep defaults that aren't in ChefConfig::Config def cookbook_copyright(*args, &block) ChefConfig::Config.cookbook_copyright(*args, &block) || 'YOUR_NAME' end def cookbook_email(*args, &block) ChefConfig::Config.cookbook_email(*args, &block) || 'YOUR_EMAIL' end def cookbook_license(*args, &block) ChefConfig::Config.cookbook_license(*args, &block) || 'reserved' end # The configuration as a hash def to_hash ChefConfig::Config.save(true) end # Load from a file def self.from_file(file) new(file) end # Behave just like ChefConfig::Config in general def method_missing(name, *args, &block) ChefConfig::Config.send(name, *args, &block) end def respond_to_missing?(name) ChefConfig::Config.respond_to?(name) end end end ridley-5.1.1/lib/ridley/logging.rb0000644000004100000410000000101413123557300017042 0ustar www-datawww-datarequire 'logger' module Ridley module Logging class << self # @return [Logger] def logger @logger ||= begin Ridley::Logging::Logger.new end end # @param [Logger, nil] obj # # @return [Logger] def set_logger(obj) @logger = (obj.nil? ? Logger.new(File::NULL) : obj) end alias_method :logger=, :set_logger end # @return [Logger] def logger Ridley::Logging.logger end alias_method :log, :logger end end ridley-5.1.1/lib/ridley/helpers.rb0000644000004100000410000000032213123557300017057 0ustar www-datawww-datamodule Ridley module Helpers def self.options_slice(options, *keys) keys.inject({}) do |memo, key| memo[key] = options[key] if options.include?(key) memo end end end end ridley-5.1.1/lib/ridley/version.rb0000644000004100000410000000004613123557300017105 0ustar www-datawww-datamodule Ridley VERSION = "5.1.1" end ridley-5.1.1/lib/ridley/middleware.rb0000644000004100000410000000042213123557300017533 0ustar www-datawww-datamodule Ridley module Middleware CONTENT_TYPE = 'content-type'.freeze CONTENT_ENCODING = 'content-encoding'.freeze end end Dir["#{File.dirname(__FILE__)}/middleware/*.rb"].sort.each do |path| require_relative "middleware/#{File.basename(path, '.rb')}" end ridley-5.1.1/lib/ridley/httpclient_ext.rb0000644000004100000410000000016713123557300020462 0ustar www-datawww-datarequire_relative 'httpclient_ext/cookie' ::WebAgent::Cookie.send(:include, ::Ridley::HTTPClientExt::WebAgent::Cookie) ridley-5.1.1/lib/ridley/resources.rb0000644000004100000410000000020513123557300017427 0ustar www-datawww-dataDir["#{File.dirname(__FILE__)}/resources/*.rb"].sort.each do |path| require_relative "resources/#{File.basename(path, '.rb')}" end ridley-5.1.1/lib/ridley/resource.rb0000644000004100000410000000716713123557300017262 0ustar www-datawww-datamodule Ridley class Resource class << self # @return [String] def resource_path @resource_path ||= representation.chef_type end # @param [String] path # # @return [String] def set_resource_path(path) @resource_path = path end def representation return @representation if @representation raise RuntimeError.new("no representation set") end def represented_by(klass) @representation = klass end end include Celluloid # @param [Celluloid::Registry] connection_registry def initialize(connection_registry) @connection_registry = connection_registry end def new(*args) self.class.representation.new(Actor.current, *args) end # Used to build a representation from a file with the current Actor's resource # # @param [String] filename # a full filename from which to build this representation (currently only supports .json files) # # @return [representation.class] def from_file(filename) from_json(File.read(filename)) end # Used to build a representation from a serialized json string with the current Actor's resource # # @param [String] json # a representation serialized into json # # @return [representation.class] def from_json(json) new(JSON.parse(json)) end # @return [Ridley::Connection] def connection @connection_registry[:connection_pool] end # @param [Ridley::Client] client # # @return [Array] def all request(:get, self.class.resource_path).collect do |identity, location| new(self.class.representation.chef_id.to_s => identity) end end # @param [String, #chef_id] object # # @return [Object, nil] def find(object) chef_id = object.respond_to?(:chef_id) ? object.chef_id : object new(request(:get, "#{self.class.resource_path}/#{chef_id}")) rescue AbortError => ex return nil if ex.cause.is_a?(Errors::HTTPNotFound) abort(ex.cause) end # @param [#to_hash] object # # @return [Object] def create(object) resource = new(object.to_hash) new_attributes = request(:post, self.class.resource_path, resource.to_json) resource.mass_assign(resource._attributes_.deep_merge(new_attributes)) resource end # @param [String, #chef_id] object # # @return [Object, nil] def delete(object) chef_id = object.respond_to?(:chef_id) ? object.chef_id : object new(request(:delete, "#{self.class.resource_path}/#{chef_id}")) rescue AbortError => ex return nil if ex.cause.is_a?(Errors::HTTPNotFound) abort(ex.cause) end # @return [Array] def delete_all all.collect { |resource| future(:delete, resource) }.map(&:value) end # @param [#to_hash] object # # @return [Object, nil] def update(object) resource = new(object.to_hash) new(request(:put, "#{self.class.resource_path}/#{resource.chef_id}", resource.to_json)) rescue AbortError => ex return nil if ex.cause.is_a?(Errors::HTTPConflict) abort(ex.cause) end private # @param [Symbol] method def request(method, *args) raw_request(method, *args).body end # @param [Symbol] method def raw_request(method, *args) unless Connection::METHODS.include?(method) raise Errors::HTTPUnknownMethod, "unknown http method: #{method}" end connection.send(method, *args) rescue Errors::HTTPError, Errors::ClientError => ex abort(ex) end end end ridley-5.1.1/lib/ridley/chef_objects.rb0000644000004100000410000000021313123557300020032 0ustar www-datawww-dataDir["#{File.dirname(__FILE__)}/chef_objects/*.rb"].sort.each do |path| require_relative "chef_objects/#{File.basename(path, '.rb')}" end ridley-5.1.1/lib/ridley/httpclient_ext/0000755000004100000410000000000013123557300020131 5ustar www-datawww-dataridley-5.1.1/lib/ridley/httpclient_ext/cookie.rb0000644000004100000410000000031513123557300021726 0ustar www-datawww-datarequire 'httpclient/webagent-cookie' module Ridley module HTTPClientExt module WebAgent module Cookie def domain self.original_domain end end end end end ridley-5.1.1/lib/ridley/chef.rb0000644000004100000410000000045313123557300016327 0ustar www-datawww-datamodule Ridley # Classes and modules used for integrating with a Chef Server, the Chef community # site, and Chef Cookbooks module Chef require_relative 'chef/cookbook' require_relative 'chef/config' require_relative 'chef/chefignore' require_relative 'chef/digester' end end ridley-5.1.1/lib/ridley/mixin.rb0000644000004100000410000000017513123557300016547 0ustar www-datawww-dataDir["#{File.dirname(__FILE__)}/mixin/*.rb"].sort.each do |path| require_relative "mixin/#{File.basename(path, '.rb')}" end ridley-5.1.1/lib/ridley.rb0000644000004100000410000000421113123557300015416 0ustar www-datawww-datarequire 'addressable/uri' require 'celluloid' require 'celluloid/io' require 'faraday' require 'forwardable' require 'hashie' require 'json' require 'pathname' require 'semverse' require 'httpclient' require 'ridley/httpclient_ext' JSON.create_id = nil module Ridley CHEF_VERSION = '11.4.0'.freeze class << self extend Forwardable def_delegator "Ridley::Logging", :logger alias_method :log, :logger def_delegator "Ridley::Logging", :logger= def_delegator "Ridley::Logging", :set_logger # @return [Ridley::Client] def new(*args) Client.new(*args) end # Create a new Ridley connection from the Chef config (knife.rb) # # @param [#to_s] filepath # the path to the Chef Config # # @param [hash] options # list of options to pass to the Ridley connection (@see {Ridley::Client#new}) # # @return [Ridley::Client] def from_chef_config(filepath = nil, options = {}) config = Ridley::Chef::Config.new(filepath).to_hash config[:validator_client] = config.delete(:validation_client_name) config[:validator_path] = config.delete(:validation_key) config[:client_name] = config.delete(:node_name) config[:server_url] = config.delete(:chef_server_url) if config[:ssl_verify_mode] == :verify_none config[:ssl] = {verify: false} end Client.new(config.merge(options)) end def open(*args, &block) Client.open(*args, &block) end # @return [Pathname] def root @root ||= Pathname.new(File.expand_path('../', File.dirname(__FILE__))) end end require_relative 'ridley/mixin' require_relative 'ridley/logging' require_relative 'ridley/logger' require_relative 'ridley/chef_object' require_relative 'ridley/chef_objects' require_relative 'ridley/client' require_relative 'ridley/connection' require_relative 'ridley/chef' require_relative 'ridley/middleware' require_relative 'ridley/resource' require_relative 'ridley/resources' require_relative 'ridley/sandbox_uploader' require_relative 'ridley/version' require_relative 'ridley/errors' end Celluloid.logger = Ridley.logger ridley-5.1.1/.gitignore0000644000004100000410000000030213123557300015020 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp *.sw[op] .DS_Store .rspec .bin/ vendor/ ridley-5.1.1/LICENSE0000644000004100000410000000120013123557300014033 0ustar www-datawww-dataCopyright 2012-2013 Riot Games Jamie Winsor () Kyle Allan () Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ridley-5.1.1/CHANGELOG.md0000644000004100000410000000663713123557300014662 0ustar www-datawww-data# Ridley Changelog ## 4.6.1 - Pin to buff-ignore to ensure support for legacy Ruby releases. This will be the last release of Ridley that supports Ruby < 2.1.X. Pin accordingly if you require support for deprecated Ruby releases. ## 4.6.0 ### Enhancements - Switch from net_http to httpclient under the hood to add proxy support ## 4.5.0 ### Enhancements - Add support for chef server universe endpoint ## 4.4.3 - updating httpclient version dep to ~> 2.7 ## 4.4.2 - Bring back 4.4.0 with backcompat fixes! ## 4.4.1 - Revert 4.4.0 while we figure out a regression ## 4.4.0 ### Enhancements - Use chef-config gem for Chef configuration, including proxy improvements ## 4.3.2 ### Bug Fixes - Supress warning output re: cookbook domain from httpclient ## 4.3.1 - Tighten constraint on varia_model to `~> 0.4.0` ## 4.3.0 ### Enhancements - Switch internal HTTP client from `net_http_persistent` to `httpclient` - Loosen constraint on Hashie to allow for both the 2.x and 3.x line ### Bug Fixes - Fix missiong constant ValidationFailed when performing a params validation ## 4.2.0 ### Enhancements - Support 'root_default' files ## 4.1.2 - Fixed: permission denied errors on Windows while uploading cookbooks - Fixed: no proc error when evaluating client_key option - Bump required version of retryable ## 4.1.1 ### Enhancements - Add support for metadata source_url field - Add support for metadata issues_url field ## 4.1.0 - Fix monkey patching issue with options#slice when running under Vagrant - Bump required version of Celluloid `~> 0.16.0` - Bump required version of Celluloid-IO `~> 0.16.1` ## 4.0.0 - Update many out of date dependencies - buff-config - buff-extensions - varia_model - hashie ## 3.1.0 - Fix issue with evaluating metadata generated by older versions of knife - Move remaining host-connector code out of Ridley and into Ridley-Connectors ## 3.0.0 - Update Faraday - Update Celluloid - Fix noise in chefignore when debug flag is turned on - Fix issue reading files that contain UTF-8 ## 2.5.1 - Fix no method error bug in partial search ## 2.5.0 - Releasing 2.5.0 as a proper new version over 2.4.4. ## 2.4.4 - [#248](https://github.com/RiotGames/ridley/pull/248) Fix some edge cases and styling for deleting attributes from an environment - [#247](https://github.com/RiotGames/ridley/pull/247) Add support for removing attributes from a node ## 2.4.3 - [#245](https://github.com/RiotGames/ridley/pull/245) Fix for numeric and boolean attribute types ## 2.4.2 - [#244](https://github.com/RiotGames/ridley/pull/244) Fix a bug with deleting deeply nested environment attributes. ## 2.4.0 - Add support for Chef 11 User Objects ## 2.1.0 - [#228](https://github.com/RiotGames/ridley/pull/228) Add a new API for filtering log output. Useful for output you might not want to display because it is sensitive. ## 2.0.0 - [#227](https://github.com/RiotGames/ridley/pull/227) HostCommander and HostConnector code has been moved into its own gem - [found here](https://github.com/RiotGames/ridley-connectors) - As discussed by @jtimberman in [#225](https://github.com/RiotGames/ridley/issues/225) it makes sense to move this code based on Ridley's main purpose, and gives a decent performance boost to users who don't need this extra functionality. ## 1.7.1 - [#224](https://github.com/RiotGames/ridley/pull/224) Connection#stream will now return true/false on whether it copied the file that was streamed. ridley-5.1.1/README.md0000644000004100000410000003061713123557300014323 0ustar www-datawww-data# Ridley [![Gem Version](https://badge.fury.io/rb/ridley.svg)](http://badge.fury.io/rb/ridley) [![Build Status](https://secure.travis-ci.org/berkshelf/ridley.svg?branch=master)](http://travis-ci.org/berkshelf/ridley) [![Dependency Status](https://gemnasium.com/berkshelf/ridley.svg?travis)](https://gemnasium.com/berkshelf/ridley) [![Code Climate](https://codeclimate.com/github/berkshelf/ridley.svg)](https://codeclimate.com/github/berkshelf/ridley) A reliable Chef API client with a clean syntax Installation ------------ Add Ridley to your `Gemfile`: ```ruby gem 'ridley' ``` And run the `bundle` command to install. Alternatively, you can install the gem directly: $ gem install ridley Usage ----- Before you can use Ridley, you must require it in your application: ```ruby require 'ridley' ``` ### Creating a new Ridley client ```ruby ridley = Ridley.new( server_url: "https://api.opscode.com/organizations/ridley", client_name: "reset", client_key: "/Users/reset/.chef/reset.pem" ) ``` Creating a new instance of Ridley requires the following options: - server_url - client_name - client_key client_key can be either a file path or the client key as a string. You can also optionally supply an encrypted data bag secret for decrypting encrypted data bags. The option is "encrypted_data_bag_secret" This can be a file name or the key itself as a string. ridley = Ridley.new( server_url: "https://api.opscode.com/organizations/ridley", client_name: "reset", client_key: "some key data", encrypted_data_bag_secret: "File path or key as a string" ) Ridley exposes a number of functions that return resources which you can use to retrieve or create objects on your Chef server. Here is a simple example of getting a list of all the roles on your Chef server. ```ruby ridley = Ridley.new(...) ridley.role.all #=> [ #, # ] ``` For more information scroll down to the Manipulating Chef Resources section of this README. You can also tell Ridley to read the values from your Chef config (knife.rb): ```ruby ridley = Ridley.from_chef_config('/path/to/knife.rb') ridley.role.all #=> [ #, # ] ``` The mapping between Chef Config values and Ridley values is:
Ridley Chef
validator_client validation_client_name
validator_path validation_key
client_name node_name
server_url chef_server_url
Additionally, you can leave the path blank and Ridley will perform a "knife.rb search" the same way Chef does: ```ruby ridley = Ridley.from_chef_config ridley.role.all #=> [ #, # ] ``` If you don't want to instantiate and manage a connection object you can use `Ridley.open` to open a connection, do some work, and it will be closed for you after the block executes. ```ruby Ridley.open(server_url: "https://api.opscode.com", ...) do |r| r.node.all end ``` ### Manipulating Chef Resources Resources are accessed by instance functions on a new instance of `Ridley::Client`. ```ruby ridley = Ridley.new(...) ridley.client #=> Ridley::ClientResource ridley.cookbook #=> Ridley::CookbookResource ridley.data_bag #=> Ridley::DataBagResource ridley.environment #=> Ridley::EnvironmentResource ridley.node #=> Ridley::NodeResource ridley.role #=> Ridley::RoleResource ridley.sandbox #=> Ridley::SandboxResource ridley.search #=> Ridley::SearchResource ridley.user #=> Ridley::UserResource ``` DataBagItems are the only exception to this rule. The DataBagItem resource is accessed from a DataBagObject ```ruby data_bag = ridley.data_bag.find("my_data") data_bag.item #=> Ridley::DataBagItemResource data_bag.item.find("my_item") #=> Ridley::DataBagItemObject ``` ### CRUD Most resources can be listed, retrieved, created, updated, and destroyed. These are commonly referred to as CRUD (Create Read Update Delete) operations. #### Create A new Chef Object can be created in four ways _With the `#create` function and an attribute hash_ ```ruby ridley = Ridley.new(...) ridley.role.create(name: "reset") #=> # ``` _With the `#create` function and an instance of a Chef Object_ ```ruby obj = ridley.role.new obj.name = "reset" ridley.role.create(obj) #=> # ``` _With the `#save` function on an instance of a Chef Object_ ```ruby obj = ridley.role.new obj.name = "reset" obj.save #=> # ``` _With the `#save` function on an instance of a Chef Object built from serialized json_ obj = ridley.role.from_file('/path/to/role.json') obj.save #=> # Each of these methods produce an identical object on the Chef server. It is up to you on how you'd like to create new resources. #### Read Most resources have two read functions - `#all` for listing all the Chef Objects - `#find` for retrieving a specific Chef Object ##### Listing If you want to get a list of all of the roles on your Chef server ```ruby ridley = Ridley.new(...) ridley.role.all #=> [ #, # ] ``` Notify: You have to send the #reload message to node objects returned from a full listing. Their attributes aren't automatically populated from the initial search. ```ruby ridley = Ridley.new(...) ridley.role.all.first => # description="" env_run_lists=# json_class="Chef::Role" name="some_chef_id" override_attributes=# run_list=[]>> ridley.role.all.first.reload => # description="Some description" env_run_lists=# json_class="Chef::Role" name="some_chef_id" override_attributes=# run_list=[ SOME RUN LIST ]>> ```` ##### Finding If you want to retrieve a single role from the Chef server ```ruby ridley = Ridley.new(...) ridley.role.find("motherbrain_srv") #=> # ``` If the role does not exist on the Chef server then `nil` is returned ```ruby ridley = Ridley.new(...) ridley.role.find("not_there") #=> nil ``` #### Update Updating a resource can be expressed in three ways _With the `#update` function, the ID of the Object to update, and an attributes hash_ ```ruby ridley = Ridley.new(...) ridley.role.update("motherbrain_srv", description: "testing updates") #=> # ``` _With the `#update` function and an instance of a Chef Object_ ```ruby obj = ridley.role.find("motherbrain_srv") obj.description = "chef object" ridley.role.update(obj) #=> # # ``` #### Destroy Destroying a resource can be expressed in three ways _With the `#delete` function and the ID of the Object to destroy_ ```ruby ridley = Ridley.new(...) ridley.role.delete("motherbrain_srv") => # ``` _With the `#delete` function and a Chef Object_ ```ruby obj = ridley.role.find("motherbrain_srv") ridley.role.delete(obj) => # ``` _With the `#destroy` function on an instance of a Chef Object_ ```ruby obj = ridley.role.find("motherbrain_srv") obj.destroy #=> true ``` Client Resource --------------- ### Regenerating a client's private key _With the `#regnerate_key` function and the ID of the Client to regenerate_ ```ruby ridley = Ridley.new(...) ridley.client.regenerate_key("jamie") #=> # ``` _With the `#regenerate_key` function on an instance of a Client Object_ ```ruby obj = ridley.client.find("jamie") obj.regenerate_key #=> # ``` Cookbook Resource ----------------- Cookbooks can be created, listed, updated and deleted as shown in the Manipulating Chef Resources section of this README. To find a cookbook, you must provide both its name and version: ```ruby ridley = Ridley.new(...) ridley.cookbook.find("apache2", "1.6.6") ``` Cookbooks can be downloaded. If no download path is specified, the chosen cookbook will be downloaded to a tmp folder. ```ruby ridley = Ridley.new(...) ridley.cookbook.download("apache2", "1.6.6", "/path/to/download/cookbook") #=> "/path/to/download/cookbook" ridley.cookbook.download("apache2", "1.6.6") #=> "/tmp/d20140211-6621-kstalp" ``` A cookbook's metadata can be accessed using the `#metadata` method: ```ruby ridley = Ridley.new(...) apache2 = ridley.cookbook.find("apache2", "1.6.6") apache2.metadata #=> Hashie::Mash ``` Data Bag Resource ----------------- A data bag is managed exactly the same as any other Chef resource ```ruby ridley = Ridley.new(...) ridley.data_bag.create(name: "ridley-test") ``` You can create, delete, update, or retrieve a data bag exactly how you would expect if you read through the Manipulating Chef Resources portion of this document. Unlike a role, node, client, or environment, a data bag is a container for other resources. These other resources are Data Bag Items. Data Bag Items behave slightly different than other resources. Data Bag Items can have an arbitrary attribute hash filled with any key values that you would like. The one exception is that every Data Bag Item __requires__ an 'id' key and value. This identifier is the name of the Data Bag Item. ### Creating a Data Bag Item ```ruby ridley = Ridley.new(...) data_bag = ridley.data_bag.create(name: "ridley-test") data_bag.item.create(id: "appconfig", host: "reset.local", user: "jamie") #=> # ``` Environment Resource -------------------- ### Setting Attributes Setting a default environment attribute is just like setting a node level default attribute ```ruby ridley = Ridley.new(...) production_env = ridley.environment.find("production") production_env.set_default_attribute("my_app.proxy.enabled", false) production_env.save #=> true ``` And the same goes for setting an environment level override attribute ```ruby production_env.set_override_attribute("my_app.proxy.enabled", false) production_env.save #=> true ``` Role Resource ------------- ### Role Attributes Setting role attributes is just like setting node and environment attributes ```ruby ridley = Ridley.new(...) my_app_role = ridley.role.find("my_app") my_app_role.set_default_attribute("my_app.proxy.enabled", false) my_app_role.save #=> true ``` And the same goes for setting an environment level override attribute ```ruby my_app_role.set_override_attribute("my_app.proxy.enabled", false) my_app_role.save #=> true ``` Sandbox Resource ---------------- Search Resource --------------- ```ruby ridley = Ridley.new(...) ridley.search(:node) ridley.search(:node, "name:ridley-test.local") ``` Search will return an array of the appropriate Chef Objects if one of the default indices is specified. The indices are - node - role - client - environment User Resource ------------- ### Regenerating a user's private key Works the same way as with a client resource. ### Authenticating a user's password ```ruby ridley = Ridley.new(...) ridley.user.authenticate('username', 'password') ridley.user.find('username').authenticate('password') ``` Authors and Contributors ------------------------ - Jamie Winsor () - Kyle Allan () Thank you to all of our [Contributors](https://github.com/reset/ridley/graphs/contributors), testers, and users. ridley-5.1.1/Guardfile0000644000004100000410000000117713123557300014670 0ustar www-datawww-datanotification :off guard 'spork' do watch('Gemfile') watch('spec/spec_helper.rb') { :rspec } watch(%r{^spec/support/.+\.rb$}) { :rspec } end guard 'yard', stdout: '/dev/null', stderr: '/dev/null' do watch(%r{app/.+\.rb}) watch(%r{lib/.+\.rb}) watch(%r{ext/.+\.c}) end guard 'rspec', cli: "--color --drb --format Fuubar", all_on_start: false, all_after_pass: false do watch(%r{^spec/unit/.+_spec\.rb$}) watch(%r{^spec/acceptance/.+_spec\.rb$}) watch(%r{^lib/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" } watch('spec/spec_helper.rb') { "spec" } watch(%r{^spec/support/.+\.rb$}) { "spec" } end