lib/chef-config/mixin/train_transport.rb
# Author:: Bryan McLellan <btm@loftninjas.org> # Copyright:: Copyright (c) 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 "credentials" autoload :Train, "train" require_relative "../config" require "chef-utils/dist" unless defined?(ChefUtils::Dist) module ChefConfig module Mixin module TrainTransport include ChefConfig::Mixin::Credentials attr_accessor :logger def initialize(logger) @logger = logger end # # Returns a RFC099 credentials profile as a hash # def load_credentials(profile) # Tomlrb.load_file returns a hash with keys as strings credentials = parse_credentials_file if contains_split_fqdn?(credentials, profile) logger.warn("Credentials file #{credentials_file_path} contains target '#{profile}' as a Hash, expected a string.") logger.warn("Hostnames must be surrounded by single quotes, e.g. ['host.example.org']") end # host names must be specified in credentials file as ['foo.example.org'] with quotes if !credentials.nil? && !credentials[profile].nil? credentials[profile].transform_keys(&:to_sym) # return symbolized keys to match Train.options() else nil end end # Toml creates hashes when a key is separated by periods, e.g. # [host.example.org] => { host: { example: { org: {} } } } # # Returns true if the above example is true # # A hostname has to be specified as ['host.example.org'] # This will be a common mistake so we should catch it # def contains_split_fqdn?(hash, fqdn) fqdn.split(".").reduce(hash) do |h, k| v = h[k] if Hash === v v else break false end end end # ChefConfig::Mixin::Credentials.credentials_file_path is designed around knife, # overriding it here. # # Credentials file preference: # # 1) target_mode.credentials_file # 2) /etc/chef/TARGET_MODE_HOST/credentials # 3) #credentials_file_path from parent ($HOME/.chef/credentials) # def credentials_file_path tm_config = config.target_mode profile = tm_config.host credentials_file = if tm_config.credentials_file && File.exist?(tm_config.credentials_file) tm_config.credentials_file elsif File.exist?(config.platform_specific_path("#{ChefConfig::Config.etc_chef_dir}/#{profile}/credentials")) config.platform_specific_path("#{ChefConfig::Config.etc_chef_dir}/#{profile}/credentials") else super end raise ArgumentError, "No credentials file found for target '#{profile}'" unless credentials_file raise ArgumentError, "Credentials file specified for target mode does not exist: '#{credentials_file}'" unless File.exist?(credentials_file) logger.debug("Loading credentials file '#{credentials_file}' for target '#{profile}'") credentials_file end def build_transport return nil unless config.target_mode? # TODO: Consider supporting parsing the protocol from a URI passed to `--target` # train_config = {} # Load the target_mode config context from config, and place any valid settings into the train configuration tm_config = config.target_mode # Load the credentials file, and place any valid settings into the train configuration credentials = load_credentials(tm_config.host) protocol = credentials[:transport_protocol] || tm_config.protocol train_config = tm_config.to_hash.select { |k| Train.options(protocol).key?(k) } logger.trace("Using target mode options from #{ChefUtils::Dist::Infra::PRODUCT} config file: #{train_config.keys.join(", ")}") if train_config if credentials valid_settings = credentials.select { |k| Train.options(protocol).key?(k) } valid_settings[:enable_password] = credentials[:enable_password] if credentials.key?(:enable_password) train_config.merge!(valid_settings) logger.trace("Using target mode options from credentials file: #{valid_settings.keys.join(", ")}") if valid_settings end train_config[:logger] = logger # Train handles connection retries for us Train.create(protocol, train_config) rescue SocketError => e # likely a dns failure, not caught by train e.message.replace "Error connecting to #{train_config[:target]} via #{protocol} - #{e.message}" raise e rescue Train::PluginLoadError logger.error("Invalid target mode protocol: #{protocol}") exit(1) end def config raise NotImplementedError end end end end