diff --git a/lib/puppet/resource_api/transport.rb b/lib/puppet/resource_api/transport.rb index ac66a8ac..5858cd91 100644 --- a/lib/puppet/resource_api/transport.rb +++ b/lib/puppet/resource_api/transport.rb @@ -1,3 +1,9 @@ +# rubocop:disable Style/Documentation +module Puppet; end +module Puppet::ResourceApi; end +module Puppet::ResourceApi::Transport; end +# rubocop:enable Style/Documentation + # Remote target transport API module Puppet::ResourceApi::Transport def register(schema) diff --git a/lib/puppet/resource_api/transport/wrapper.rb b/lib/puppet/resource_api/transport/wrapper.rb new file mode 100644 index 00000000..97fe4fe0 --- /dev/null +++ b/lib/puppet/resource_api/transport/wrapper.rb @@ -0,0 +1,43 @@ +require 'puppet/resource_api/transport' +require 'hocon' +require 'hocon/config_syntax' + +# Puppet::ResourceApi::Transport::Wrapper` to interface between the Util::NetworkDevice +class Puppet::ResourceApi::Transport::Wrapper + attr_reader :transport + + def initialize(name, url_or_config) + if url_or_config.is_a? String + @url = URI.parse(url_or_config) + raise "Unexpected url '#{url_or_config}' found. Only file:/// URLs for configuration supported at the moment." unless @url.scheme == 'file' + else + @config = url_or_config + end + + @transport = Puppet::ResourceApi::Transport.connect(name, config) + end + + def config + raise "Trying to load config from '#{@url.path}, but file does not exist." if @url && !File.exist?(@url.path) + # symbolize top-level keys to match up with expected provider/resource data conventions + @config ||= (Hocon.load(@url.path, syntax: Hocon::ConfigSyntax::HOCON) || {}).map { |k, v| [k.to_sym, v] }.to_h + end + + def facts + # @transport.facts + custom_facts # look into custom facts work by TP + @transport.facts + end + + def respond_to_missing?(name, _include_private) + (@transport.respond_to? name) || super + end + + def method_missing(method_name, *args, &block) + if @transport.respond_to? method_name + puts "Delegating #{method_name}" + @transport.send(method_name, *args, &block) + else + super + end + end +end diff --git a/spec/acceptance/device_spec.rb b/spec/acceptance/device_spec.rb index 69302007..909313c9 100644 --- a/spec/acceptance/device_spec.rb +++ b/spec/acceptance/device_spec.rb @@ -74,9 +74,11 @@ <= Gem::Version.new('5.3.6') && Gem::Version.new(Puppet::PUPPETVERSION) != Gem::Version.new('5.4.0') @@ -86,10 +88,14 @@ def is_device_apply_supported? skip "No device --apply in puppet before v5.3.6 nor in v5.4.0 (v#{Puppet::PUPPETVERSION} is installed)" unless is_device_apply_supported? device_conf.write(device_conf_content) device_conf.close + + device_credentials.write(device_credentials_content) + device_credentials.close end after(:each) do device_conf.unlink + device_credentials.unlink end context 'with no config specified' do diff --git a/spec/fixtures/test_module/lib/puppet/transport/schema/test_device.rb b/spec/fixtures/test_module/lib/puppet/transport/schema/test_device.rb new file mode 100644 index 00000000..5bd6ee45 --- /dev/null +++ b/spec/fixtures/test_module/lib/puppet/transport/schema/test_device.rb @@ -0,0 +1,8 @@ +require 'puppet/resource_api' + +Puppet::ResourceApi.register_transport( + name: 'test_device', # points at class Puppet::Transport::TestDevice + desc: 'Connects to a device', + connection_info: { + }, +) diff --git a/spec/fixtures/test_module/lib/puppet/transport/test_device.rb b/spec/fixtures/test_module/lib/puppet/transport/test_device.rb new file mode 100644 index 00000000..ad3a04f6 --- /dev/null +++ b/spec/fixtures/test_module/lib/puppet/transport/test_device.rb @@ -0,0 +1,21 @@ +module Puppet::Transport +# a transport for a test_device +class TestDevice + def initialize(connection_info); + puts connection_info + end + + def facts + { 'foo' => 'bar' } + end + + def verify + return true + end + + def close + return + end +end + +end diff --git a/spec/puppet/resource_api/transport/wrapper_spec.rb b/spec/puppet/resource_api/transport/wrapper_spec.rb new file mode 100644 index 00000000..f2692b19 --- /dev/null +++ b/spec/puppet/resource_api/transport/wrapper_spec.rb @@ -0,0 +1,116 @@ +require 'spec_helper' +require 'puppet/resource_api/transport/wrapper' +require_relative '../../../fixtures/test_module/lib/puppet/transport/test_device' + +RSpec.describe Puppet::ResourceApi::Transport::Wrapper, agent_test: true do + describe '#initialize(name, url_or_config)' do + context 'when called with a url' do + context 'with a file:// prefix' do + let(:url) { 'file:///etc/credentials' } + + it 'will not throw an error' do + allow(File).to receive(:exist?).and_return(true) + expect(Puppet::ResourceApi::Transport).to receive(:connect) + expect(Hocon).to receive(:load) + expect { described_class.new('wibble', url) }.not_to raise_error + end + end + + context 'with an http:// prefix' do + let(:url) { 'http://www.puppet.com' } + + it { expect { described_class.new('wibble', url) }.to raise_error RuntimeError, %r{Only file:/// URLs for configuration supported} } + end + end + + context 'when called with a config hash' do + let(:config) { {} } + + it 'will use the configuration directly' do + expect(Hocon).not_to receive(:load) + expect(Puppet::ResourceApi::Transport).to receive(:connect) + described_class.new('wibble', config) + end + end + end + + describe '#facts' do + context 'when called' do + let(:instance) { described_class.new('wibble', {}) } + let(:facts) { { 'foo' => 'bar' } } + let(:transport) { instance_double(Puppet::Transport::TestDevice, 'transport') } + + it 'will return the facts provided by the transport' do + allow(Puppet::ResourceApi::Transport).to receive(:connect).and_return(transport) + allow(transport).to receive(:facts).and_return(facts) + + expect(instance.facts).to eq(facts) + end + end + end + + context 'when an unsupported method is called' do + context 'when the transport can handle the method' do + let(:instance) { described_class.new('wibble', {}) } + let(:transport) { instance_double(Puppet::Transport::TestDevice, 'transport') } + + it 'will return the facts provided by the transport' do + allow(Puppet::ResourceApi::Transport).to receive(:connect).and_return(transport) + expect(transport).to receive(:close) + + instance.close + end + end + + context 'when the transport cannot handle the method' do + let(:instance) { described_class.new('wibble', {}) } + let(:transport) { instance_double(Puppet::Transport::TestDevice, 'transport') } + + it 'will raise a NoMethodError' do + allow(Puppet::ResourceApi::Transport).to receive(:connect).and_return(transport) + expect { instance.wibble }.to raise_error NoMethodError + end + end + end + + context 'when a method is checked for' do + let(:instance) { described_class.new('wibble', {}) } + let(:transport) { instance_double(Puppet::Transport::TestDevice, 'transport') } + + before(:each) do + allow(Puppet::ResourceApi::Transport).to receive(:connect).and_return(transport) + end + + context 'when the transport does not support the function' do + context 'when using respond_to?' do + it 'will return false' do + expect(instance.respond_to?(:wibble)).to eq(false) + end + end + + context 'when using method?' do + it 'will return false' do + expect { instance.method :wibble }.to raise_error + end + end + end + + context 'when the transport does support the function' do + before(:each) do + allow(transport).to receive(:close) + end + + context 'when using respond_to?' do + it 'will return true' do + expect(instance.respond_to?(:close)).to eq(true) + end + end + + context 'when using method?' do + it 'will return the method' do + expect(instance.method(:close)).to be_a(Method) + end + end + end + end +end diff --git a/spec/puppet/resource_api/transport_spec.rb b/spec/puppet/resource_api/transport_spec.rb index f1f27d5a..3208900d 100644 --- a/spec/puppet/resource_api/transport_spec.rb +++ b/spec/puppet/resource_api/transport_spec.rb @@ -102,12 +102,12 @@ end context 'when the transport file does exist' do - context 'with and incorrectly defined transport' do + context 'with an incorrectly defined transport' do it 'throws a DevError' do expect(described_class).to receive(:validate).with(name, connection_info) expect(described_class).to receive(:require).with('puppet/transport/test_target') expect { described_class.connect(name, connection_info) }.to raise_error Puppet::DevError, - %r{uninitialized constant Puppet::Transport} + %r{uninitialized constant (Puppet::Transport|TestTarget)} end end