lib/chef-cli/policyfile/policyfile_location_specification.rb



#
# Copyright:: 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 "../policyfile_lock"
require_relative "local_lock_fetcher"
require_relative "remote_lock_fetcher"
require_relative "chef_server_lock_fetcher"
require_relative "git_lock_fetcher"
require_relative "../exceptions"

module ChefCLI
  module Policyfile
    # A PolicyfileLocationSpecification specifies where a policyfile lock is to be fetched from.
    # Using this information, it provides a fetcher that is capable loading the policyfile
    # lock.
    #
    # @attr_reader [String] name The name of the policyfile
    # @attr_reader [Hash] source_options Options describing how to get the policyfile lock
    class PolicyfileLocationSpecification
      attr_reader :name
      attr_reader :source_options

      attr_reader :storage_config
      attr_reader :chef_config
      attr_reader :ui

      LOCATION_TYPES = %i{path remote server git}.freeze

      # Initialize a location spec
      #
      # @param name [String] the name of the policyfile
      # @param source_options [Hash] options describing where the policyfile lock lives
      # @param storage_config [Policyfile::StorageConfig]
      # @param chef_config [Chef::Config] chef config that will be used when communication
      #                    with a chef server is required
      def initialize(name, source_options, storage_config, chef_config = nil)
        @name = name
        @source_options = source_options
        @storage_config = storage_config
        @ui = nil
        @chef_config = chef_config
      end

      # @return The revision id from the fetched lock
      def revision_id
        fetcher.lock_data["revision_id"]
      end

      # @return A policyfile lock fetcher compatible with the given source_options
      def fetcher
        @fetcher ||= if source_options[:path] && !source_options[:git]
                       Policyfile::LocalLockFetcher.new(name, source_options, storage_config)
                     elsif source_options[:remote]
                       Policyfile::RemoteLockFetcher.new(name, source_options)
                     elsif source_options[:server]
                       Policyfile::ChefServerLockFetcher.new(name, source_options, chef_config)
                     elsif source_options[:git]
                       Policyfile::GitLockFetcher.new(name, source_options, storage_config)
                     else
                       raise ChefCLI::InvalidPolicyfileLocation.new(
                         "Invalid policyfile lock location type. The supported locations are: #{LOCATION_TYPES.join(", ")}"
                       )
                     end
      end

      # @return [True] if there were no errors with the provided source_options
      # @return [False] if there were errors with the provided source_options
      def valid?
        errors.empty?
      end

      # Check the options provided when creating this class for errors
      #
      # @return [Array<String>] A list of errors found
      def errors
        error_messages = []

        if LOCATION_TYPES.all? { |l| source_options[l].nil? }
          error_messages << "include_policy must use one of the following sources: #{LOCATION_TYPES.join(", ")}"
        else
          unless fetcher.nil?
            error_messages += fetcher.errors
          end
        end

        error_messages
      end

      # Fetches and loads the policyfile lock
      #
      # @return [PolicyfileLock] the loaded policyfile lock
      def policyfile_lock
        @policyfile_lock ||= PolicyfileLock.new(storage_config, ui:).build_from_lock_data(fetcher.lock_data)
      end

      # @return [Hash] The source_options that describe how to fetch this exact lock again
      def source_options_for_lock
        fetcher.source_options_for_lock
      end

      # Applies source options from a lock file. This is used to make sure that the same
      # policyfile lock is loaded that was locked
      #
      # @param options_from_lock [Hash] The source options loaded from a policyfile lock
      def apply_locked_source_options(options_from_lock)
        fetcher.apply_locked_source_options(options_from_lock)
      end
    end
  end
end