summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2020-09-15 17:33:20 -0700
committerLamont Granquist <lamont@scriptkiddie.org>2020-09-15 17:33:20 -0700
commitb597635a194f0f238692b0c69ffcf7d8958986bc (patch)
treeb2430ba1e232cda0e0f591e2b8d122120bf4da86
parentba3c9622e8a296f9b6c28783bd49efb20b62c6c6 (diff)
downloadchef-b597635a194f0f238692b0c69ffcf7d8958986bc.tar.gz
Move TrainTransport to ChefConfig
Also pull in some helpers from the Ohai work so that Ohai can use them later. This is code rearrangement to support the remote ohai work but does not change any existing behavior yet. 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.rb141
-rw-r--r--chef-utils/lib/chef-utils/dsl/train_helpers.rb27
-rw-r--r--lib/chef/run_context.rb2
-rw-r--r--lib/chef/train_transport.rb109
-rw-r--r--spec/unit/train_transport_spec.rb27
6 files changed, 190 insertions, 124 deletions
diff --git a/chef-config/lib/chef-config/mixin/credentials.rb b/chef-config/lib/chef-config/mixin/credentials.rb
index e480a3bbc9..a17d94b443 100644
--- a/chef-config/lib/chef-config/mixin/credentials.rb
+++ b/chef-config/lib/chef-config/mixin/credentials.rb
@@ -85,17 +85,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..c58836a766
--- /dev/null
+++ b/chef-config/lib/chef-config/mixin/train_transport.rb
@@ -0,0 +1,141 @@
+# 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].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("#{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
+ 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
+
+ # 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/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 7d6539b416..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 "chef-utils/dist" unless defined?(ChefUtils::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("#{ChefConfig::Config.etc_chef_dir}/#{profile}/credentials"))
- Chef::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)
-
- 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 #{ChefUtils::Dist::Infra::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
diff --git a/spec/unit/train_transport_spec.rb b/spec/unit/train_transport_spec.rb
index 0922c5b00d..68ef3716be 100644
--- a/spec/unit/train_transport_spec.rb
+++ b/spec/unit/train_transport_spec.rb
@@ -19,32 +19,33 @@
require "spec_helper"
describe Chef::TrainTransport do
+ let(:transport) { Chef::TrainTransport.new(Chef::Log) }a
+
describe "load_credentials" do
- let(:transport) { Chef::TrainTransport.new }
let(:good_credentials) { { "switch.cisco.com" => { "user" => "cisco", "password" => "cisco", "enable_password" => "secret" } } }
before do
- allow(Chef::TrainTransport).to receive(:parse_credentials_file).and_return(good_credentials)
+ allow(transport).to receive(:parse_credentials_file).and_return(good_credentials)
end
it "matches credentials when they exist" do
- expect(Chef::TrainTransport.load_credentials("switch.cisco.com")[:user]).to eq("cisco")
- expect(Chef::TrainTransport.load_credentials("switch.cisco.com")[:password]).to eq("cisco")
- expect(Chef::TrainTransport.load_credentials("switch.cisco.com")[:enable_password]).to eq("secret")
+ expect(transport.load_credentials("switch.cisco.com")[:user]).to eq("cisco")
+ expect(transport.load_credentials("switch.cisco.com")[:password]).to eq("cisco")
+ expect(transport.load_credentials("switch.cisco.com")[:enable_password]).to eq("secret")
end
it "returns nil if there is no match" do
- expect(Chef::TrainTransport.load_credentials("router.unicorns.com")).to be_nil
+ expect(transport.load_credentials("router.unicorns.com")).to be_nil
end
# [foo.example.org] => {"foo"=>{"example"=>{"org"=>{}}}}
# ['foo.example.org'] => {"foo.example.org"=>{}}
it "warns if the host has been split by toml" do
- allow(Chef::TrainTransport).to receive(:credentials_file_path).and_return("/Users/scotthourglass/.chef/credentials")
- allow(Chef::TrainTransport).to receive(:parse_credentials_file).and_return({ "foo" => { "example" => { "org" => {} } } })
+ allow(transport).to receive(:credentials_file_path).and_return("/Users/scotthourglass/.chef/credentials")
+ allow(transport).to receive(:parse_credentials_file).and_return({ "foo" => { "example" => { "org" => {} } } })
expect(Chef::Log).to receive(:warn).with(/as a Hash/)
expect(Chef::Log).to receive(:warn).with(/Hostnames must be surrounded by single quotes/)
- expect(Chef::TrainTransport.load_credentials("foo.example.org")).to be_nil
+ expect(transport.load_credentials("foo.example.org")).to be_nil
end
end
@@ -60,25 +61,25 @@ describe Chef::TrainTransport do
it "returns the path if it exists" do
allow(File).to receive(:exist?).with(config_cred_file_path).and_return(true)
- expect(Chef::TrainTransport.credentials_file_path).to eq(config_cred_file_path)
+ expect(transport.credentials_file_path).to eq(config_cred_file_path)
end
it "raises an error if it does not exist" do
allow(File).to receive(:exist?).and_return(false)
- expect { Chef::TrainTransport.credentials_file_path }.to raise_error(ArgumentError, /does not exist/)
+ expect { transport.credentials_file_path }.to raise_error(ArgumentError, /does not exist/)
end
end
it "raises an error if the default creds files do not exist" do
allow(File).to receive(:exist?).and_return(false)
- expect { Chef::TrainTransport.credentials_file_path }.to raise_error(ArgumentError, /does not exist/)
+ expect { transport.credentials_file_path }.to raise_error(ArgumentError, /does not exist/)
end
it "returns the path to the default config file if it exists" do
tm_config = double("Config Context", host: "foo.example.org", credentials_file: nil)
allow(Chef::Config).to receive(:target_mode).and_return(tm_config)
allow(File).to receive(:exist?).with(host_cred_file_path).and_return(true)
- expect(Chef::TrainTransport.credentials_file_path).to eq(host_cred_file_path)
+ expect(transport.credentials_file_path).to eq(host_cred_file_path)
end
end
end