lib/chef-cli/policyfile/included_policies_cookbook_source.rb



#
# Copyright:: Copyright (c) 2014-2018 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_relative "../ui"

module ChefCLI
  module Policyfile
    class IncludedPoliciesCookbookSource

      class ConflictingCookbookVersions < StandardError
      end

      class ConflictingCookbookDependencies < StandardError
      end

      class ConflictingCookbookSources < StandardError
      end

      # Why do we need this class?
      # If we rely on default sources, we may not have the the universe of cookbooks
      # provided in the included policies
      #
      # This is not meant to be used from the DSL

      # A list of included policies
      attr_reader :included_policy_location_specs
      # UI object for output
      attr_accessor :ui

      # Constructor
      #
      def initialize(included_policy_location_specs)
        @included_policy_location_specs = included_policy_location_specs
        @ui = UI.new
        @preferred_cookbooks = []
        yield self if block_given?
      end

      def default_source_args
        [:included_policies, []]
      end

      def check_for_conflicts!
        source_options
        universe_graph
      end

      # All are preferred here
      def preferred_source_for?(cookbook_name)
        universe_graph.include?(cookbook_name)
      end

      def preferred_cookbooks
        universe_graph.keys
      end

      def ==(other)
        other.is_a?(self.class) && other.included_policy_location_specs == included_policy_location_specs
      end

      # Calls the slurp_metadata! helper once to calculate the @universe_graph
      # and @cookbook_version_paths metadata.  Returns the @universe_graph.
      #
      # @return [Hash] universe_graph
      def universe_graph
        @universe_graph ||= build_universe
      end

      # Returns the metadata (path and version) for an individual cookbook
      #
      # @return [Hash] metadata for a single cookbook version
      def source_options_for(cookbook_name, cookbook_version)
        source_options[cookbook_name][cookbook_version]
      end

      def null?
        false
      end

      def desc
        "included_policies()"
      end

      private

      def build_universe
        included_policy_location_specs.inject({}) do |acc, policy_spec|
          lock = policy_spec.policyfile_lock
          cookbook_dependencies = lock.solution_dependencies.cookbook_dependencies
          cookbook_dependencies.each do |(cookbook, deps)|
            name = cookbook.name
            version = cookbook.version
            mapped_deps = deps.map do |dep|
              [dep[0], dep[1].to_s]
            end
            if acc[name]
              if acc[name][version]
                if acc[name][version] != mapped_deps
                  raise ConflictingCookbookDependencies.new("Conflicting dependencies provided for cookbook #{name}")
                end
              else
                raise ConflictingCookbookVersions.new("Multiple versions provided for cookbook #{name}")
              end
            else
              acc[name] = {}
              acc[name][version] = mapped_deps
            end
          end
          acc
        end
      end

      def source_options
        @source_options ||= build_source_options
      end

      ## Collect all the source options
      def build_source_options
        included_policy_location_specs.inject({}) do |acc, policy_spec|
          lock = policy_spec.policyfile_lock
          lock.cookbook_locks.each do |(name, cookbook_lock)|
            version = cookbook_lock.version
            if acc[name]
              if acc[name][version]
                if acc[name][version] != cookbook_lock.source_options
                  raise ConflictingCookbookSources.new("Conflicting sources provided for cookbook #{name}")
                end
              else
                raise ConflictingCookbookVersions.new("Multiple sources provided for cookbook #{name}")
              end
            else
              acc[name] = {}
              acc[name][version] = cookbook_lock.source_options
            end
          end
          acc
        end
      end

    end
  end
end