From 35a8c7e72ed933339470d61191db8a96787c829e Mon Sep 17 00:00:00 2001 From: danielsdeleo Date: Mon, 19 Oct 2015 17:57:05 -0700 Subject: [PATCH] Add :delivery_supermarket default source type This type behaves like the supermarket/community source, except that only the highest version cookbook is exposed via the universe graph. --- lib/chef-dk/policyfile/cookbook_sources.rb | 1 + .../policyfile/delivery_supermarket_source.rb | 82 ++++++++++++++ lib/chef-dk/policyfile/dsl.rb | 10 ++ .../delivery_supermarket_source_spec.rb | 105 ++++++++++++++++++ spec/unit/policyfile_evaluation_spec.rb | 36 ++++++ 5 files changed, 234 insertions(+) create mode 100644 lib/chef-dk/policyfile/delivery_supermarket_source.rb create mode 100644 spec/unit/policyfile/delivery_supermarket_source_spec.rb diff --git a/lib/chef-dk/policyfile/cookbook_sources.rb b/lib/chef-dk/policyfile/cookbook_sources.rb index 0ea42ff29..d7126385a 100644 --- a/lib/chef-dk/policyfile/cookbook_sources.rb +++ b/lib/chef-dk/policyfile/cookbook_sources.rb @@ -19,3 +19,4 @@ require 'chef-dk/policyfile/community_cookbook_source' require 'chef-dk/policyfile/chef_server_cookbook_source' require 'chef-dk/policyfile/chef_repo_cookbook_source' +require 'chef-dk/policyfile/delivery_supermarket_source' diff --git a/lib/chef-dk/policyfile/delivery_supermarket_source.rb b/lib/chef-dk/policyfile/delivery_supermarket_source.rb new file mode 100644 index 000000000..a060b8c6f --- /dev/null +++ b/lib/chef-dk/policyfile/delivery_supermarket_source.rb @@ -0,0 +1,82 @@ +# +# Copyright:: Copyright (c) 2014 Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# 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. +# + +require 'forwardable' + +require 'semverse' + +require 'chef-dk/policyfile/community_cookbook_source' + +module ChefDK + module Policyfile + + # Fetches cookbooks from a supermarket, similar to CommunityCookbookSource + # (which it delegates to), except that only the latest versions of any + # cookbook can be used. + # + # This is intended to be used in an environment where the team wants to + # make only the newest version of a given cookbook available in order to + # force developers to integrate continuously at the component artifact + # (cookbook) level. To achieve this goal, two constraints must be imposed: + # + # * Cookbook changes pass through a Ci pipeline and are ultimately uploaded + # to a private supermarket (or equivalent, i.e. mini-mart) after final + # approval (which can be automated or not) + # * Version numbers for cookbooks that pass through the Ci pipeline always + # increase over time (so that largest version number == newest) + # + # In the future, alternative approaches may be persued to achieve the goal + # of continuously integrating at the cookbook level without imposing those + # constraints. + # + class DeliverySupermarketSource + + extend Forwardable + + def_delegator :@community_source, :uri + def_delegator :@community_source, :source_options_for + def_delegator :@community_source, :null? + + def initialize(uri) + @community_source = CommunityCookbookSource.new(uri) + end + + def ==(other) + other.kind_of?(self.class) && other.uri == uri + end + + def universe_graph + @universe_graph ||= begin + @community_source.universe_graph.inject({}) do |truncated, (cookbook_name, version_and_deps_list)| + sorted_versions = version_and_deps_list.keys.sort_by do |version_string| + Semverse::Version.new(version_string) + end + greatest_version = sorted_versions.last + truncated[cookbook_name] = { greatest_version => version_and_deps_list[greatest_version] } + truncated + end + end + end + + def desc + "delivery_supermarket(#{uri})" + end + + end + end +end + diff --git a/lib/chef-dk/policyfile/dsl.rb b/lib/chef-dk/policyfile/dsl.rb index bc9963888..411c8a8de 100644 --- a/lib/chef-dk/policyfile/dsl.rb +++ b/lib/chef-dk/policyfile/dsl.rb @@ -73,6 +73,8 @@ def default_source(source_type = nil, source_argument = nil) case source_type when :community, :supermarket set_default_community_source(source_argument) + when :delivery_supermarket + set_default_delivery_supermarket_source(source_argument) when :chef_server set_default_chef_server_source(source_argument) when :chef_repo @@ -143,6 +145,14 @@ def set_default_community_source(source_uri) set_default_source(CommunityCookbookSource.new(source_uri)) end + def set_default_delivery_supermarket_source(source_uri) + if source_uri.nil? + @errors << "You must specify the server's URI when using a default_source :delivery_supermarket" + else + set_default_source(DeliverySupermarketSource.new(source_uri)) + end + end + def set_default_chef_server_source(source_uri) if source_uri.nil? @errors << "You must specify the server's URI when using a default_source :chef_server" diff --git a/spec/unit/policyfile/delivery_supermarket_source_spec.rb b/spec/unit/policyfile/delivery_supermarket_source_spec.rb new file mode 100644 index 000000000..7a72ca18f --- /dev/null +++ b/spec/unit/policyfile/delivery_supermarket_source_spec.rb @@ -0,0 +1,105 @@ +# +# Copyright:: Copyright (c) 2014 Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# 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. +# + +require 'spec_helper' +require 'chef-dk/policyfile/delivery_supermarket_source' + +describe ChefDK::Policyfile::DeliverySupermarketSource do + + let(:supermarket_uri) { "https://delivery-supermarket.example" } + + subject(:cookbook_source) { ChefDK::Policyfile::DeliverySupermarketSource.new(supermarket_uri) } + + let(:http_connection) { double("Chef::HTTP::Simple") } + + let(:universe_response_encoded) { IO.read(File.join(fixtures_path, "cookbooks_api/small_universe.json")) } + + let(:truncated_universe) do + { + "nginx" => { + "2.7.4" => { + "apt" => "~> 2.2.0", + "bluepill" => "~> 2.3.0", + "build-essential" => "~> 2.0.0", + "ohai" => "~> 2.0.0", + "runit" => "~> 1.2.0", + "yum-epel" => "~> 0.3.0" + } + }, + "mysql" => { + "5.3.6" => { + "yum-mysql-community" => ">= 0.0.0" + } + }, + "application" => { + "4.1.4" => {} + }, + "database" => { + "2.2.0" => { + "mysql" => ">= 5.0.0", + "postgresql" => ">= 1.0.0", + "aws" => ">= 0.0.0", + "xfs" => ">= 0.0.0", + "mysql-chef_gem" => ">= 0.0.0" + } + }, + "postgresql" => { + "3.4.1" => { + "apt" => ">= 1.9.0", + "build-essential" => ">= 0.0.0", + "openssl" => ">= 0.0.0" + } + }, + "apache2" => { + "1.10.4" => { + "iptables" => ">= 0.0.0", + "logrotate" => ">= 0.0.0", + "pacman" => ">= 0.0.0" + } + }, + "apt" => { "2.4.0" => {}}, + "yum" => { "3.2.2" => {}} + } + end + + it "uses `delivery_supermarket` it its description" do + expect(cookbook_source.desc).to eq("delivery_supermarket(https://delivery-supermarket.example)") + end + + describe "when fetching the /universe graph" do + + before do + expect(Chef::HTTP::Simple).to receive(:new).with(supermarket_uri).and_return(http_connection) + expect(http_connection).to receive(:get).with("/universe").and_return(universe_response_encoded) + end + + it "fetches the universe graph and truncates to only the latest versions" do + actual_universe = cookbook_source.universe_graph + expect(actual_universe).to have_key("apt") + expect(actual_universe["apt"]).to eq(truncated_universe["apt"]) + expect(cookbook_source.universe_graph).to eq(truncated_universe) + end + + it "generates location options for a cookbook from the given graph" do + expected_opts = { artifactserver: "https://supermarket.chef.io/api/v1/cookbooks/apache2/versions/1.10.4/download", version: "1.10.4" } + expect(cookbook_source.source_options_for("apache2", "1.10.4")).to eq(expected_opts) + end + + end + +end + diff --git a/spec/unit/policyfile_evaluation_spec.rb b/spec/unit/policyfile_evaluation_spec.rb index e068d1278..7fd69bdf9 100644 --- a/spec/unit/policyfile_evaluation_spec.rb +++ b/spec/unit/policyfile_evaluation_spec.rb @@ -226,6 +226,42 @@ end + context "with the default source set to a delivery_supermarket" do + + context "when no URI is given" do + + let(:policyfile_rb) do + <<-EOH + run_list "foo", "bar" + default_source :delivery_supermarket + EOH + end + + it "errors out with a message that the supermarket URI is required" do + expect(policyfile.errors).to eq([ "You must specify the server's URI when using a default_source :delivery_supermarket" ]) + end + + end + + context "when the URI is given" do + + let(:policyfile_rb) do + <<-EOH + run_list "foo", "bar" + default_source :delivery_supermarket, "https://supermarket.example.com" + EOH + end + + it "sets the default source to the delivery_supermarket" do + expect(policyfile.errors).to eq([]) + expected = [ ChefDK::Policyfile::DeliverySupermarketSource.new("https://supermarket.example.com") ] + expect(policyfile.default_source).to eq(expected) + end + + end + + end + context "with the default source set to a chef server" do let(:policyfile_rb) do