Skip to content

Commit 2ca3255

Browse files
authored
Merge pull request #157 from puppetlabs/transport
(FM-7695) Transports - the remote content framework
2 parents 2419162 + 1647088 commit 2ca3255

29 files changed

+1644
-358
lines changed

.rubocop.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
---
22
require: rubocop-rspec
3-
inherit_from: .rubocop_todo.yml
43
AllCops:
54
TargetRubyVersion: '2.1'
65
Include:
@@ -157,4 +156,4 @@ Naming/UncommunicativeMethodParamName:
157156

158157
# This cop breaks syntax highlighting in VSCode
159158
Layout/ClosingHeredocIndentation:
160-
Enabled: false
159+
Enabled: false

.rubocop_todo.yml

Lines changed: 0 additions & 17 deletions
This file was deleted.

.travis.yml

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ matrix:
2525
- env: RVM="jruby-9.1.9.0" PUPPET_GEM_VERSION='~> 5' JRUBY_OPTS="--debug"
2626
before_cache: pushd ~/.rvm && rm -rf archives rubies/ruby-2.2.7 rubies/ruby-2.3.4 && popd
2727
cache:
28-
bundler: true
28+
bundler: false
2929
directories: ~/.rvm
3030
before_install: rvm use jruby-9.1.9.0 --install --binary --fuzzy
3131
- rvm: 2.4.3
@@ -65,10 +65,5 @@ matrix:
6565
- rvm: 2.5.1
6666
env: PUPPET_GEM_VERSION='https://github.com/puppetlabs/puppet.git#6.0.x'
6767
notifications:
68-
hipchat:
69-
rooms:
70-
secure: 10a49kkZcghKHNnef8x7eBG+KjScL3i1VpygFg6DPAOK2YNbEoyEx1Kv9KLC7GSRYov/SQZOsZrvHZtDhEtFSKhhiAjOwxl1jV1t6aAMGMnN1IoZBOvdAJKrZsm54/bBeYp+je2wqnnoFNtLVFSoOX0LkFaDEWT+zGZ5xKJIH25GpeQEZf1eDxs/d8YX/m+RwbGXHVA//hOpvZo0ntvznh2EbW5OPODKSeUXbWZ+W4ndODTsKWFc/WLMSSgFDzW/Y2/9V40D4IC8lvSx6eKFryMfAQy6pO/d1aTB468awzyVcdYAMMCOITm7hlKGRKxNgq6NkOsXs5KLg6ifpn+a/Rhapbz6Qxbpjjho/7Wxngl4B3T+i35ap/mFrS/fOfKCq3gEQlYn29its9bEFArNGbr+/sXKABb+sRpgW4RTPWYDHJyHJendbevd5tZ+fd0JUBOi0Cb4PcXxQxM8IQrbuu2zso0K5MV05kL0S1DE/VsuUrPaK0RsF+b1+i6NfvtN8kgbYs1eiVku+guIG2ec3xIefQ1hsEOFPFNqSDfHp7nANnRVIbBCt8qw8DhmNEczsfN5Dp21euJUsO9qpau++NzD3jRhkE5Zki5cwsakU7hIQzw82BIb0eSQJCHygieExeEboWRqtDgy/IKIWPgIvEuU68ppR2bl2reKCHLCnWc=
71-
template:
72-
- '%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message}
73-
(<a href="%{build_url}">Details</a> | <a href="%{compare_url}">PR/Diff</a>)'
74-
format: html
68+
slack:
69+
secure: aPXZYNow8LsmmlS8PQU3FjL0bc7FqUUA95d++wZfIu7YAjGboIUiekxYouQ0XnY+Aig8InvbTOIgBHgGNheyr/YFbFS90/jtulbF8oW7BitW+imgjeAHDCwlQZTCc4FFYde/2pI7QTT8H5NpLR9mKxlTU77Sqr8gFAIybuPdHcKMYQZdEZS07ma2pUp7+GyKS6PDQpzW2+mDCz/wfi3/JdsUvc0mclCZ8vxySc66j5P1E6nFDMzuakBOjwJHpgeDpreapbmSUQLAX0a3ZsFP+N+SNduLotlV2BWnJK2gcO6rGFP4Fz1D0bGXuBnYYdIiB+9OgI3wtXg9y1SifNHUG3IrOBAA8CGNyrebTGKtH0TS2O+HZLbaNX2g6udD5e3156vys9wScmJuQ/rSkVtQfXf1qUm5eijvlXI+DIbssbZHqm6QQGyM4p3NoULmNmF1C85bQoZ4GF7b1P/8mstsVE/HUfnzRPNbwD0r6j1aE/ck3PKMi7ZAhIi0Ja9RnAgP3wi0t62uERYcJGGYEycWohMWnrf2w6GFwGeuoiwAkASdHOLX0/AOMPc4mBOjlc621o8uYMrrZqfF5CrOAvJ151USSsWn2AhXaibIvnHo6X91paNvvNpU/GYu3CUAl6q8OhYovvjtRVPVnhs2DrpgoRB+6NWHnzjRG/wr6Z9U+vA=

lib/puppet/resource_api.rb

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
require 'puppet/resource_api/property'
66
require 'puppet/resource_api/puppet_context' unless RUBY_PLATFORM == 'java'
77
require 'puppet/resource_api/read_only_parameter'
8+
require 'puppet/resource_api/transport'
9+
require 'puppet/resource_api/transport/wrapper'
810
require 'puppet/resource_api/type_definition'
911
require 'puppet/resource_api/value_creator'
1012
require 'puppet/resource_api/version'
1113
require 'puppet/type'
1214
require 'puppet/util/network_device'
1315

16+
# This module contains the main API to register and access types, providers and transports.
1417
module Puppet::ResourceApi
1518
@warning_count = 0
1619

@@ -38,7 +41,12 @@ def register_type(definition)
3841
# Keeps a copy of the provider around. Weird naming to avoid clashes with puppet's own `provider` member
3942
define_singleton_method(:my_provider) do
4043
@my_provider ||= Hash.new { |hash, key| hash[key] = Puppet::ResourceApi.load_provider(definition[:name]).new }
41-
@my_provider[Puppet::Util::NetworkDevice.current.class]
44+
45+
if Puppet::Util::NetworkDevice.current.is_a? Puppet::ResourceApi::Transport::Wrapper
46+
@my_provider[Puppet::Util::NetworkDevice.current.transport.class]
47+
else
48+
@my_provider[Puppet::Util::NetworkDevice.current.class]
49+
end
4250
end
4351

4452
# make the provider available in the instance's namespace
@@ -352,7 +360,7 @@ def cache_current_state(resource_hash)
352360
end
353361

354362
define_singleton_method(:context) do
355-
@context ||= PuppetContext.new(definition)
363+
@context ||= PuppetContext.new(TypeDefinition.new(definition))
356364
end
357365

358366
def context
@@ -410,8 +418,10 @@ def load_provider(type_name)
410418
type_name_sym = type_name.to_sym
411419
device_name = if Puppet::Util::NetworkDevice.current.nil?
412420
nil
413-
else
421+
elsif Puppet::Util::NetworkDevice.current.is_a? Puppet::ResourceApi::Transport::Wrapper
414422
# extract the device type from the currently loaded device's class
423+
Puppet::Util::NetworkDevice.current.schema.name
424+
else
415425
Puppet::Util::NetworkDevice.current.class.name.split('::')[-2].downcase
416426
end
417427
device_class_name = class_name_from_type_name(device_name)
@@ -451,6 +461,12 @@ def load_device_provider(class_name, type_name_sym, device_class_name, device_na
451461
end
452462
module_function :load_device_provider # rubocop:disable Style/AccessModifierDeclarations
453463

464+
# keeps the existing register API format. e.g. Puppet::ResourceApi.register_type
465+
def register_transport(schema)
466+
Puppet::ResourceApi::Transport.register(schema)
467+
end
468+
module_function :register_transport # rubocop:disable Style/AccessModifierDeclarations
469+
454470
def self.class_name_from_type_name(type_name)
455471
type_name.to_s.split('_').map(&:capitalize).join
456472
end

lib/puppet/resource_api/base_context.rb

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,34 @@
11
require 'puppet/resource_api/type_definition'
22

3+
# rubocop:disable Style/Documentation
34
module Puppet; end
45
module Puppet::ResourceApi; end
6+
# rubocop:enable Style/Documentation
7+
8+
# This class provides access to all common external dependencies of providers and transports.
9+
# The runtime environment will inject an appropriate implementation.
510
class Puppet::ResourceApi::BaseContext
611
attr_reader :type
712

813
def initialize(definition)
9-
raise ArgumentError, 'BaseContext requires definition to be a Hash' unless definition.is_a?(Hash)
10-
@typename = definition[:name]
11-
@type = Puppet::ResourceApi::TypeDefinition.new(definition)
14+
if definition.is_a?(Hash)
15+
# this is only for backwards compatibility
16+
@type = Puppet::ResourceApi::TypeDefinition.new(definition)
17+
elsif definition.is_a? Puppet::ResourceApi::BaseTypeDefinition
18+
@type = definition
19+
else
20+
raise ArgumentError, 'BaseContext requires definition to be a child of Puppet::ResourceApi::BaseTypeDefinition, not <%{actual_type}>' % { actual_type: definition.class }
21+
end
1222
end
1323

1424
def device
1525
raise 'Received device() on an unprepared BaseContext. Use a PuppetContext instead.'
1626
end
1727

28+
def transport
29+
raise 'No transport available.'
30+
end
31+
1832
def failed?
1933
@failed
2034
end
@@ -27,7 +41,7 @@ def feature_support?(feature)
2741
[:debug, :info, :notice, :warning, :err].each do |level|
2842
define_method(level) do |*args|
2943
if args.length == 1
30-
message = "#{@context || @typename}: #{args.last}"
44+
message = "#{@context || @type.name}: #{args.last}"
3145
elsif args.length == 2
3246
resources = format_titles(args.first)
3347
message = "#{resources}: #{args.last}"
@@ -137,9 +151,9 @@ def send_log(_level, _message)
137151

138152
def format_titles(titles)
139153
if titles.length.zero? && !titles.is_a?(String)
140-
@typename
154+
@type.name
141155
else
142-
"#{@typename}[#{[titles].flatten.compact.join(', ')}]"
156+
"#{@type.name}[#{[titles].flatten.compact.join(', ')}]"
143157
end
144158
end
145159

lib/puppet/resource_api/io_context.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
require 'puppet/resource_api/base_context'
22

3+
# Implement Resource API Conext to log through an IO object, defaulting to `$stderr`.
4+
# There is no access to a device here. You can supply a transport if necessary.
35
class Puppet::ResourceApi::IOContext < Puppet::ResourceApi::BaseContext
4-
def initialize(definition, target = $stderr)
6+
attr_reader :transport
7+
8+
def initialize(definition, target = $stderr, transport = nil)
59
super(definition)
610
@target = target
11+
@transport = transport
712
end
813

914
protected

lib/puppet/resource_api/puppet_context.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
require 'puppet/resource_api/base_context'
22
require 'puppet/util/logging'
33

4+
# Implement Resource API Context to log through Puppet facilities
5+
# and access/expose the puppet process' current device/transport
46
class Puppet::ResourceApi::PuppetContext < Puppet::ResourceApi::BaseContext
57
def device
68
# TODO: evaluate facter_url setting for loading config if there is no `current` NetworkDevice
79
raise 'no device configured' unless Puppet::Util::NetworkDevice.current
810
Puppet::Util::NetworkDevice.current
911
end
1012

13+
def transport
14+
device.transport
15+
end
16+
1117
def log_exception(exception, message: 'Error encountered', trace: false)
1218
super(exception, message: message, trace: trace || Puppet[:trace])
1319
end

lib/puppet/resource_api/transport.rb

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
module Puppet::ResourceApi; end # rubocop:disable Style/Documentation
2+
3+
# Remote target transport API
4+
module Puppet::ResourceApi::Transport
5+
def register(schema)
6+
raise Puppet::DevError, 'requires a hash as schema, not `%{other_type}`' % { other_type: schema.class } unless schema.is_a? Hash
7+
raise Puppet::DevError, 'requires a `:name`' unless schema.key? :name
8+
raise Puppet::DevError, 'requires `:desc`' unless schema.key? :desc
9+
raise Puppet::DevError, 'requires `:connection_info`' unless schema.key? :connection_info
10+
raise Puppet::DevError, '`:connection_info` must be a hash, not `%{other_type}`' % { other_type: schema[:connection_info].class } unless schema[:connection_info].is_a?(Hash)
11+
12+
init_transports
13+
unless @transports[@environment][schema[:name]].nil?
14+
raise Puppet::DevError, 'Transport `%{name}` is already registered for `%{environment}`' % {
15+
name: schema[:name],
16+
environment: @environment,
17+
}
18+
end
19+
@transports[@environment][schema[:name]] = Puppet::ResourceApi::TransportSchemaDef.new(schema)
20+
end
21+
module_function :register # rubocop:disable Style/AccessModifierDeclarations
22+
23+
# retrieve a Hash of transport schemas, keyed by their name.
24+
def list
25+
init_transports
26+
Marshal.load(Marshal.dump(@transports[@environment]))
27+
end
28+
module_function :list # rubocop:disable Style/AccessModifierDeclarations
29+
30+
def connect(name, connection_info)
31+
validate(name, connection_info)
32+
require "puppet/transport/#{name}"
33+
class_name = name.split('_').map { |e| e.capitalize }.join
34+
Puppet::Transport.const_get(class_name).new(get_context(name), wrap_sensitive(name, connection_info))
35+
end
36+
module_function :connect # rubocop:disable Style/AccessModifierDeclarations
37+
38+
def inject_device(name, transport)
39+
transport_wrapper = Puppet::ResourceApi::Transport::Wrapper.new(name, transport)
40+
41+
if Puppet::Util::NetworkDevice.respond_to?(:set_device)
42+
Puppet::Util::NetworkDevice.set_device(name, transport_wrapper)
43+
else
44+
Puppet::Util::NetworkDevice.instance_variable_set(:@current, transport_wrapper)
45+
end
46+
end
47+
module_function :inject_device # rubocop:disable Style/AccessModifierDeclarations
48+
49+
def self.validate(name, connection_info)
50+
init_transports
51+
require "puppet/transport/schema/#{name}" unless @transports[@environment].key? name
52+
transport_schema = @transports[@environment][name]
53+
if transport_schema.nil?
54+
raise Puppet::DevError, 'Transport for `%{target}` not registered with `%{environment}`' % {
55+
target: name,
56+
environment: @environment,
57+
}
58+
end
59+
message_prefix = 'The connection info provided does not match the Transport Schema'
60+
transport_schema.check_schema(connection_info, message_prefix)
61+
transport_schema.validate(connection_info)
62+
end
63+
private_class_method :validate
64+
65+
def self.get_context(name)
66+
require 'puppet/resource_api/puppet_context'
67+
Puppet::ResourceApi::PuppetContext.new(@transports[@environment][name])
68+
end
69+
private_class_method :get_context
70+
71+
def self.init_transports
72+
lookup = Puppet.lookup(:current_environment) if Puppet.respond_to? :lookup
73+
@environment = if lookup.nil?
74+
:transports_default
75+
else
76+
lookup.name
77+
end
78+
@transports ||= {}
79+
@transports[@environment] ||= {}
80+
end
81+
private_class_method :init_transports
82+
83+
def self.wrap_sensitive(name, connection_info)
84+
transport_schema = @transports[@environment][name]
85+
if transport_schema
86+
transport_schema.definition[:connection_info].each do |attr_name, options|
87+
if options.key?(:sensitive) && (options[:sensitive] == true)
88+
connection_info[attr_name] = Puppet::Pops::Types::PSensitiveType::Sensitive.new(connection_info[attr_name])
89+
end
90+
end
91+
end
92+
connection_info
93+
end
94+
private_class_method :wrap_sensitive
95+
end
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
require 'puppet/resource_api/transport'
2+
require 'hocon'
3+
require 'hocon/config_syntax'
4+
5+
# Puppet::ResourceApi::Transport::Wrapper` to interface between the Util::NetworkDevice
6+
class Puppet::ResourceApi::Transport::Wrapper
7+
attr_reader :transport, :schema
8+
9+
def initialize(name, url_or_config_or_transport)
10+
if url_or_config_or_transport.is_a? String
11+
url = URI.parse(url_or_config_or_transport)
12+
raise "Unexpected url '#{url_or_config_or_transport}' found. Only file:/// URLs for configuration supported at the moment." unless url.scheme == 'file'
13+
raise "Trying to load config from '#{url.path}, but file does not exist." if url && !File.exist?(url.path)
14+
config = self.class.deep_symbolize(Hocon.load(url.path, syntax: Hocon::ConfigSyntax::HOCON) || {})
15+
elsif url_or_config_or_transport.is_a? Hash
16+
config = url_or_config_or_transport
17+
elsif transport_class?(name, url_or_config_or_transport)
18+
@transport = url_or_config_or_transport
19+
end
20+
21+
@transport ||= Puppet::ResourceApi::Transport.connect(name, config)
22+
@schema = Puppet::ResourceApi::Transport.list[name]
23+
end
24+
25+
def transport_class?(name, transport)
26+
class_name = name.split('_').map { |e| e.capitalize }.join
27+
expected = Puppet::Transport.const_get(class_name).to_s
28+
expected == transport.class.to_s
29+
end
30+
31+
def facts
32+
context = Puppet::ResourceApi::PuppetContext.new(@schema)
33+
# @transport.facts + custom_facts # look into custom facts work by TP
34+
@transport.facts(context)
35+
end
36+
37+
def respond_to_missing?(name, _include_private)
38+
(@transport.respond_to? name) || super
39+
end
40+
41+
def method_missing(method_name, *args, &block)
42+
if @transport.respond_to? method_name
43+
@transport.send(method_name, *args, &block)
44+
else
45+
super
46+
end
47+
end
48+
49+
# From https://stackoverflow.com/a/11788082/4918
50+
def self.deep_symbolize(obj)
51+
return obj.each_with_object({}) { |(k, v), memo| memo[k.to_sym] = deep_symbolize(v); } if obj.is_a? Hash
52+
return obj.each_with_object([]) { |v, memo| memo << deep_symbolize(v); } if obj.is_a? Array
53+
obj
54+
end
55+
end

0 commit comments

Comments
 (0)