summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2020-09-11 13:50:45 -0700
committerLamont Granquist <lamont@scriptkiddie.org>2020-09-11 14:07:00 -0700
commitbadf232ba1f6f130334e2cb1a8e098fae9120aab (patch)
tree659bed78897293f83dc7721dd7b64514f129c592
parentacb0178b9fe02483dcb855a32e5e5de46e137127 (diff)
downloadchef-lcg/ohai-target-mode.tar.gz
Signed-off-by: Lamont Granquist <lamont@scriptkiddie.org>
-rw-r--r--chef-config/lib/chef-config/mixin/credentials.rb8
-rw-r--r--chef-config/lib/chef-config/mixin/train_transport.rb140
-rw-r--r--chef-utils/lib/chef-utils/dsl/train_helpers.rb27
-rw-r--r--lib/chef/application/client.rb7
-rw-r--r--lib/chef/client.rb33
-rw-r--r--lib/chef/run_context.rb2
-rw-r--r--lib/chef/train_transport.rb109
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