diff options
author | Lamont Granquist <lamont@scriptkiddie.org> | 2020-09-11 13:50:45 -0700 |
---|---|---|
committer | Lamont Granquist <lamont@scriptkiddie.org> | 2020-09-11 14:07:00 -0700 |
commit | badf232ba1f6f130334e2cb1a8e098fae9120aab (patch) | |
tree | 659bed78897293f83dc7721dd7b64514f129c592 | |
parent | acb0178b9fe02483dcb855a32e5e5de46e137127 (diff) | |
download | chef-lcg/ohai-target-mode.tar.gz |
Signed-off-by: Lamont Granquist <lamont@scriptkiddie.org>
-rw-r--r-- | chef-config/lib/chef-config/mixin/credentials.rb | 8 | ||||
-rw-r--r-- | chef-config/lib/chef-config/mixin/train_transport.rb | 140 | ||||
-rw-r--r-- | chef-utils/lib/chef-utils/dsl/train_helpers.rb | 27 | ||||
-rw-r--r-- | lib/chef/application/client.rb | 7 | ||||
-rw-r--r-- | lib/chef/client.rb | 33 | ||||
-rw-r--r-- | lib/chef/run_context.rb | 2 | ||||
-rw-r--r-- | lib/chef/train_transport.rb | 109 |
7 files changed, 183 insertions, 143 deletions
diff --git a/chef-config/lib/chef-config/mixin/credentials.rb b/chef-config/lib/chef-config/mixin/credentials.rb index de004a062f..eff8e59f25 100644 --- a/chef-config/lib/chef-config/mixin/credentials.rb +++ b/chef-config/lib/chef-config/mixin/credentials.rb @@ -84,17 +84,17 @@ module ChefConfig # @return [void] def load_credentials(profile = nil) profile = credentials_profile(profile) - config = parse_credentials_file - return if config.nil? # No credentials, nothing to do here. + cred_config = parse_credentials_file + return if cred_config.nil? # No credentials, nothing to do here. - if config[profile].nil? + if cred_config[profile].nil? # Unknown profile name. For "default" just silently ignore, otherwise # raise an error. return if profile == "default" raise ChefConfig::ConfigurationError, "Profile #{profile} doesn't exist. Please add it to #{credentials_file_path}." end - apply_credentials(config[profile], profile) + apply_credentials(cred_config[profile], profile) end end end diff --git a/chef-config/lib/chef-config/mixin/train_transport.rb b/chef-config/lib/chef-config/mixin/train_transport.rb new file mode 100644 index 0000000000..25364475c9 --- /dev/null +++ b/chef-config/lib/chef-config/mixin/train_transport.rb @@ -0,0 +1,140 @@ +# 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" +require "train" +require "chef/dist" unless defined?(Chef::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].map { |k, v| [k.to_sym, v] }.to_h # 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("#{Chef::Dist::CONF_DIR}/#{profile}/credentials")) + config.platform_specific_path("#{Chef::Dist::CONF_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 + protocol = tm_config.protocol + train_config = tm_config.to_hash.select { |k| Train.options(protocol).key?(k) } + logger.trace("Using target mode options from #{Chef::Dist::PRODUCT} config file: #{train_config.keys.join(", ")}") if train_config + + # Load the credentials file, and place any valid settings into the train configuration + credentials = load_credentials(tm_config.host) + 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]} - #{e.message}" + raise e + rescue Train::PluginLoadError + logger.error("Invalid target mode protocol: #{protocol}") + exit(false) + end + + def config + raise NotImplementedError + end + end + end +end diff --git a/chef-utils/lib/chef-utils/dsl/train_helpers.rb b/chef-utils/lib/chef-utils/dsl/train_helpers.rb index a821383eac..b4be878723 100644 --- a/chef-utils/lib/chef-utils/dsl/train_helpers.rb +++ b/chef-utils/lib/chef-utils/dsl/train_helpers.rb @@ -25,8 +25,13 @@ module ChefUtils # # FIXME: generally these helpers all use the pattern of checking for target_mode? # and then if it is we use train. That approach should likely be flipped so that - # even when we're running without target mode we still use inspec in its local - # mode. + # even when we're running without target mode we still use train in its local + # mode. A prerequisite for that will be better CI testing of train against + # chef-client though, and ensuring that the APIs are entirely compatible. This + # will be particularly problematic for shell_out APIs and eventual file-creating + # APIs which are unlikely to be as sophisticated as the exiting code in chef-client + # for locally shelling out and creating files, and just dropping inspec local mode + # into chef-client would break the world. # # Train wrapper around File.exist? to make it local mode aware. @@ -57,6 +62,24 @@ module ChefUtils end end + # Alias to easily convert IO.read / File.read to file_read + def file_read(path) + file_open(path).read + end + + def file_directory?(path) + if __transport_connection + __transport_connection.file(filename).directory? + else + File.directory?(path) + end + end + + # Alias to easily convert Dir.exist to dir_exist + def dir_exist?(path) + file_directory?(path) + end + extend self end end diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb index 03ceff1727..961c864e64 100644 --- a/lib/chef/application/client.rb +++ b/lib/chef/application/client.rb @@ -26,6 +26,7 @@ module Mixlib autoload :Log, "mixlib/authentication" end end +autoload :Train, "train" # DO NOT MAKE EDITS, see Chef::Application::Base # @@ -114,8 +115,12 @@ class Chef::Application::Client < Chef::Application::Base Chef::Config.chef_zero.port = config[:chef_zero_port] if config[:chef_zero_port] if config[:target] || Chef::Config.target - Chef::Config.target_mode.enabled = true Chef::Config.target_mode.host = config[:target] || Chef::Config.target + if URI.parse(Chef::Config.target_mode.host).scheme + train_config = Train.unpack_target_from_uri(Chef::Config.target_mode.host) + Chef::Config.target_mode = train_config + end + Chef::Config.target_mode.enabled = true Chef::Config.node_name = Chef::Config.target_mode.host unless Chef::Config.node_name end diff --git a/lib/chef/client.rb b/lib/chef/client.rb index 1a6da64c8f..7fcc5402f0 100644 --- a/lib/chef/client.rb +++ b/lib/chef/client.rb @@ -252,11 +252,7 @@ class Chef logger.debug("#{Chef::Dist::CLIENT.capitalize} request_id: #{request_id}") ENV["PATH"] = ChefUtils::DSL::DefaultPaths.default_paths if Chef::Config[:enforce_default_paths] || Chef::Config[:enforce_path_sanity] - if Chef::Config.target_mode? - get_ohai_data_remotely - else - run_ohai - end + run_ohai unless Chef::Config[:solo_legacy_mode] register @@ -577,32 +573,6 @@ class Chef end # - # Populate the minimal ohai attributes defined in #run_ohai with data train collects. - # - # Eventually ohai may support colleciton of data. - # - def get_ohai_data_remotely - ohai.data[:fqdn] = if transport_connection.respond_to?(:hostname) - transport_connection.hostname - else - Chef::Config[:target_mode][:host] - end - if transport_connection.respond_to?(:os) - ohai.data[:platform] = transport_connection.os.name - ohai.data[:platform_version] = transport_connection.os.release - ohai.data[:os] = transport_connection.os.family_hierarchy[1] - ohai.data[:platform_family] = transport_connection.os.family - end - # train does not collect these specifically - # ohai.data[:machinename] = nil - # ohai.data[:hostname] = nil - # ohai.data[:os_version] = nil # kernel version - - ohai.data[:ohai_time] = Time.now.to_f - events.ohai_completed(node) - end - - # # Run ohai plugins. Runs all ohai plugins unless minimal_ohai is specified. # # Sends the ohai_completed event when finished. @@ -614,6 +584,7 @@ class Chef # def run_ohai filter = Chef::Config[:minimal_ohai] ? %w{fqdn machinename hostname platform platform_version ohai_time os os_version init_package} : nil + ohai.transport_connection = transport_connection if Chef::Config.target_mode? ohai.all_plugins(filter) events.ohai_completed(node) end diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb index bfefc6e101..75c18f2fcf 100644 --- a/lib/chef/run_context.rb +++ b/lib/chef/run_context.rb @@ -627,7 +627,7 @@ class Chef # @return [Train::Plugins::Transport] The child class for our train transport. # def transport - @transport ||= Chef::TrainTransport.build_transport(logger) + @transport ||= Chef::TrainTransport.new(logger).build_transport end # Remote connection object from Train diff --git a/lib/chef/train_transport.rb b/lib/chef/train_transport.rb index a4f311fc51..4fe1fcadec 100644 --- a/lib/chef/train_transport.rb +++ b/lib/chef/train_transport.rb @@ -15,114 +15,15 @@ # limitations under the License. # -require "chef-config/mixin/credentials" -autoload :Train, "train" -require_relative "dist" +require "chef-config/mixin/train_transport" unless defined?(ChefConfig::Mixin::TrainTransport) class Chef class TrainTransport - extend ChefConfig::Mixin::Credentials + include ChefConfig::Mixin::TrainTransport - # - # Returns a RFC099 credentials profile as a hash - # - def self.load_credentials(profile) - # Tomlrb.load_file returns a hash with keys as strings - credentials = parse_credentials_file - if contains_split_fqdn?(credentials, profile) - Chef::Log.warn("Credentials file #{credentials_file_path} contains target '#{profile}' as a Hash, expected a string.") - Chef::Log.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].map { |k, v| [k.to_sym, v] }.to_h # 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 self.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 self.credentials_file_path - tm_config = Chef::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?(Chef::Config.platform_specific_path("#{Chef::Dist::CONF_DIR}/#{profile}/credentials")) - Chef::Config.platform_specific_path("#{Chef::Dist::CONF_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) - - Chef::Log.debug("Loading credentials file '#{credentials_file}' for target '#{profile}'") - - credentials_file - end - - def self.build_transport(logger = Chef::Log.with_child(subsystem: "transport")) - return nil unless Chef::Config.target_mode? - - # TODO: Consider supporting parsing the protocol from a URI passed to `--target` - # - train_config = {} - - # Load the target_mode config context from Chef::Config, and place any valid settings into the train configuration - tm_config = Chef::Config.target_mode - protocol = tm_config.protocol - train_config = tm_config.to_hash.select { |k| Train.options(protocol).key?(k) } - Chef::Log.trace("Using target mode options from #{Chef::Dist::PRODUCT} config file: #{train_config.keys.join(", ")}") if train_config - - # Load the credentials file, and place any valid settings into the train configuration - credentials = load_credentials(tm_config.host) - 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) - Chef::Log.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]} - #{e.message}" - raise e - rescue Train::PluginLoadError - logger.error("Invalid target mode protocol: #{protocol}") - exit(false) + def config + require "chef/config" unless defined?(Chef::Config) + Chef::Config end end end |