Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(FM-7695) Transports - the remote content framework #157

Merged
merged 45 commits into from
Feb 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
7b0ab29
(FM-7597) RSAPI Transport register function
da-ar Dec 13, 2018
4a3e0bb
(FM-7600) RSAPI Transport connect method
da-ar Jan 8, 2019
e7ef6ac
(FM-7674) Add a Transport wrapper class
da-ar Jan 14, 2019
f776b57
Merge pull request #149 from da-ar/transport_wrapper
da-ar Jan 21, 2019
bc54be7
(FM-7691) start refactoring definition handling in contexts
DavidS Jan 22, 2019
5ecffcc
(FM-7691) Allow pre-fabbed BaseTypeDefinition as argument to BaseContext
DavidS Jan 22, 2019
9d549c0
(FM-7691) use the new capability in resource_api.rb
DavidS Jan 22, 2019
76c6eeb
(FM-7691) Load the schema before trying to validate connection_info
DavidS Jan 23, 2019
b6cbdcf
(FM-7691) Fix tests to not rely on previously registered transports
DavidS Jan 23, 2019
c7f3203
(FM-7696) remove overzealous exception handling
DavidS Jan 24, 2019
c46f50b
(FM-7691) fix connection_info/schema misnomer in tests
DavidS Jan 24, 2019
9535fe3
(FM-7691) create and pass through context when connecting a transport
DavidS Jan 24, 2019
f8282d2
(FM-7691) clean up wrapper to not expose or remember config
DavidS Jan 24, 2019
f8fcf79
(FM-7691) hide loading puppet_context from jruby
DavidS Jan 24, 2019
fc4f585
(maint) update notifications to talk to slack
DavidS Jan 24, 2019
5d89caf
(FM-7691) organise tests, and mark agent tests as such
DavidS Jan 24, 2019
8aaedab
(maint) Add some docs and remove the Style/Documentation exclusions
DavidS Jan 24, 2019
bd3a21e
(FM-7700) Add a `list` operation returning registered transports
DavidS Jan 24, 2019
ac037b2
(FM-7691) Add `context` parameter for test transport
DavidS Jan 25, 2019
e4e8cbe
(FM-7691) pass a context to transport.facts
DavidS Jan 25, 2019
4211c46
Merge pull request #150 from DavidS/FM-7691-context-refactor
da-ar Jan 28, 2019
09fc856
(FM-7690) Update transports cache to be grouped by environment
da-ar Jan 25, 2019
8e64a3d
Merge pull request #151 from da-ar/env_up
DavidS Jan 28, 2019
bd042cf
(FM-7726) implement `context.transport` to provide access
DavidS Jan 29, 2019
1341033
Merge pull request #152 from DavidS/FM-7726-context-transport
da-ar Jan 29, 2019
4122f08
(maint) lift duplicate assignment
DavidS Jan 29, 2019
3a64276
(FM-7726) deep symbolize all keys when loading credentials
DavidS Feb 1, 2019
92988d9
Merge pull request #153 from DavidS/FM-7726-context-transport
da-ar Feb 4, 2019
f052a62
Fix missing environment check in validate
da-ar Jan 31, 2019
f3b32bc
(FM-7701) Support device providers when using Transport Wrapper
da-ar Jan 31, 2019
968a148
(maint) Remove message due to breakages in existing modules
da-ar Feb 5, 2019
203f633
(maint) Update unit tests for maximum coverage
da-ar Feb 5, 2019
df0122e
Merge pull request #154 from da-ar/transport_fixes
da-ar Feb 7, 2019
2330c30
(PDK-1271) Allow a transport to be wrapped and used like a device
da-ar Feb 7, 2019
9baf1c8
Change behaviour of check_schema_keys to no longer remove unknown keys
da-ar Feb 14, 2019
8039157
Merge pull request #155 from da-ar/transport_to_device
DavidS Feb 14, 2019
1f9ede5
(FM-7698) Ensure that sensitive values are handled correctly
da-ar Feb 18, 2019
2916e59
(maint) Fix schema error message returned when checking a transport
da-ar Feb 19, 2019
88c6365
(FM-7698) Handle a transport schema type with `sensitive: true` set
da-ar Feb 19, 2019
19f9139
(maint) remove invalid comment
da-ar Feb 21, 2019
7466c20
(maint) use device wrapper to support demo transport
da-ar Feb 21, 2019
d3e2e0b
(PDK-7698) Wrap raw values flagged as sensitive in Puppet Sensitive type
da-ar Feb 21, 2019
75aae39
(maint) Fixes travis failure concerning jar-dependencies
da-ar Feb 22, 2019
95d65c0
(PDK-7698) Ensure that Sensitive values get unwrapped correctly
da-ar Feb 25, 2019
1647088
Merge pull request #156 from da-ar/recursive_sensitive
DavidS Feb 25, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
require: rubocop-rspec
inherit_from: .rubocop_todo.yml
AllCops:
TargetRubyVersion: '2.1'
Include:
Expand Down Expand Up @@ -157,4 +156,4 @@ Naming/UncommunicativeMethodParamName:

# This cop breaks syntax highlighting in VSCode
Layout/ClosingHeredocIndentation:
Enabled: false
Enabled: false
17 changes: 0 additions & 17 deletions .rubocop_todo.yml

This file was deleted.

11 changes: 3 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ matrix:
- env: RVM="jruby-9.1.9.0" PUPPET_GEM_VERSION='~> 5' JRUBY_OPTS="--debug"
before_cache: pushd ~/.rvm && rm -rf archives rubies/ruby-2.2.7 rubies/ruby-2.3.4 && popd
cache:
bundler: true
bundler: false
directories: ~/.rvm
before_install: rvm use jruby-9.1.9.0 --install --binary --fuzzy
- rvm: 2.4.3
Expand Down Expand Up @@ -65,10 +65,5 @@ matrix:
- rvm: 2.5.1
env: PUPPET_GEM_VERSION='https://github.com/puppetlabs/puppet.git#6.0.x'
notifications:
hipchat:
rooms:
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=
template:
- '%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message}
(<a href="%{build_url}">Details</a> | <a href="%{compare_url}">PR/Diff</a>)'
format: html
slack:
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=
22 changes: 19 additions & 3 deletions lib/puppet/resource_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
require 'puppet/resource_api/property'
require 'puppet/resource_api/puppet_context' unless RUBY_PLATFORM == 'java'
require 'puppet/resource_api/read_only_parameter'
require 'puppet/resource_api/transport'
require 'puppet/resource_api/transport/wrapper'
require 'puppet/resource_api/type_definition'
require 'puppet/resource_api/value_creator'
require 'puppet/resource_api/version'
require 'puppet/type'
require 'puppet/util/network_device'

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

Expand Down Expand Up @@ -38,7 +41,12 @@ def register_type(definition)
# Keeps a copy of the provider around. Weird naming to avoid clashes with puppet's own `provider` member
define_singleton_method(:my_provider) do
@my_provider ||= Hash.new { |hash, key| hash[key] = Puppet::ResourceApi.load_provider(definition[:name]).new }
@my_provider[Puppet::Util::NetworkDevice.current.class]

if Puppet::Util::NetworkDevice.current.is_a? Puppet::ResourceApi::Transport::Wrapper
@my_provider[Puppet::Util::NetworkDevice.current.transport.class]
else
@my_provider[Puppet::Util::NetworkDevice.current.class]
end
end

# make the provider available in the instance's namespace
Expand Down Expand Up @@ -352,7 +360,7 @@ def cache_current_state(resource_hash)
end

define_singleton_method(:context) do
@context ||= PuppetContext.new(definition)
@context ||= PuppetContext.new(TypeDefinition.new(definition))
end

def context
Expand Down Expand Up @@ -410,8 +418,10 @@ def load_provider(type_name)
type_name_sym = type_name.to_sym
device_name = if Puppet::Util::NetworkDevice.current.nil?
nil
else
elsif Puppet::Util::NetworkDevice.current.is_a? Puppet::ResourceApi::Transport::Wrapper
# extract the device type from the currently loaded device's class
Puppet::Util::NetworkDevice.current.schema.name
else
Puppet::Util::NetworkDevice.current.class.name.split('::')[-2].downcase
end
device_class_name = class_name_from_type_name(device_name)
Expand Down Expand Up @@ -451,6 +461,12 @@ def load_device_provider(class_name, type_name_sym, device_class_name, device_na
end
module_function :load_device_provider # rubocop:disable Style/AccessModifierDeclarations

# keeps the existing register API format. e.g. Puppet::ResourceApi.register_type
def register_transport(schema)
Puppet::ResourceApi::Transport.register(schema)
end
module_function :register_transport # rubocop:disable Style/AccessModifierDeclarations

def self.class_name_from_type_name(type_name)
type_name.to_s.split('_').map(&:capitalize).join
end
Expand Down
26 changes: 20 additions & 6 deletions lib/puppet/resource_api/base_context.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
require 'puppet/resource_api/type_definition'

# rubocop:disable Style/Documentation
module Puppet; end
module Puppet::ResourceApi; end
# rubocop:enable Style/Documentation

# This class provides access to all common external dependencies of providers and transports.
# The runtime environment will inject an appropriate implementation.
class Puppet::ResourceApi::BaseContext
attr_reader :type

def initialize(definition)
raise ArgumentError, 'BaseContext requires definition to be a Hash' unless definition.is_a?(Hash)
@typename = definition[:name]
@type = Puppet::ResourceApi::TypeDefinition.new(definition)
if definition.is_a?(Hash)
# this is only for backwards compatibility
@type = Puppet::ResourceApi::TypeDefinition.new(definition)
elsif definition.is_a? Puppet::ResourceApi::BaseTypeDefinition
@type = definition
else
raise ArgumentError, 'BaseContext requires definition to be a child of Puppet::ResourceApi::BaseTypeDefinition, not <%{actual_type}>' % { actual_type: definition.class }
end
end

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

def transport
raise 'No transport available.'
end

def failed?
@failed
end
Expand All @@ -27,7 +41,7 @@ def feature_support?(feature)
[:debug, :info, :notice, :warning, :err].each do |level|
define_method(level) do |*args|
if args.length == 1
message = "#{@context || @typename}: #{args.last}"
message = "#{@context || @type.name}: #{args.last}"
elsif args.length == 2
resources = format_titles(args.first)
message = "#{resources}: #{args.last}"
Expand Down Expand Up @@ -137,9 +151,9 @@ def send_log(_level, _message)

def format_titles(titles)
if titles.length.zero? && !titles.is_a?(String)
@typename
@type.name
else
"#{@typename}[#{[titles].flatten.compact.join(', ')}]"
"#{@type.name}[#{[titles].flatten.compact.join(', ')}]"
end
end

Expand Down
7 changes: 6 additions & 1 deletion lib/puppet/resource_api/io_context.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
require 'puppet/resource_api/base_context'

# Implement Resource API Conext to log through an IO object, defaulting to `$stderr`.
# There is no access to a device here. You can supply a transport if necessary.
class Puppet::ResourceApi::IOContext < Puppet::ResourceApi::BaseContext
def initialize(definition, target = $stderr)
attr_reader :transport

def initialize(definition, target = $stderr, transport = nil)
super(definition)
@target = target
@transport = transport
end

protected
Expand Down
6 changes: 6 additions & 0 deletions lib/puppet/resource_api/puppet_context.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
require 'puppet/resource_api/base_context'
require 'puppet/util/logging'

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

def transport
device.transport
end

def log_exception(exception, message: 'Error encountered', trace: false)
super(exception, message: message, trace: trace || Puppet[:trace])
end
Expand Down
95 changes: 95 additions & 0 deletions lib/puppet/resource_api/transport.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
module Puppet::ResourceApi; end # rubocop:disable Style/Documentation

# Remote target transport API
module Puppet::ResourceApi::Transport
def register(schema)
raise Puppet::DevError, 'requires a hash as schema, not `%{other_type}`' % { other_type: schema.class } unless schema.is_a? Hash
raise Puppet::DevError, 'requires a `:name`' unless schema.key? :name
raise Puppet::DevError, 'requires `:desc`' unless schema.key? :desc
raise Puppet::DevError, 'requires `:connection_info`' unless schema.key? :connection_info
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)

init_transports
unless @transports[@environment][schema[:name]].nil?
raise Puppet::DevError, 'Transport `%{name}` is already registered for `%{environment}`' % {
name: schema[:name],
environment: @environment,
}
end
@transports[@environment][schema[:name]] = Puppet::ResourceApi::TransportSchemaDef.new(schema)
end
module_function :register # rubocop:disable Style/AccessModifierDeclarations

# retrieve a Hash of transport schemas, keyed by their name.
def list
init_transports
Marshal.load(Marshal.dump(@transports[@environment]))
end
module_function :list # rubocop:disable Style/AccessModifierDeclarations

def connect(name, connection_info)
validate(name, connection_info)
require "puppet/transport/#{name}"
class_name = name.split('_').map { |e| e.capitalize }.join
Puppet::Transport.const_get(class_name).new(get_context(name), wrap_sensitive(name, connection_info))
end
module_function :connect # rubocop:disable Style/AccessModifierDeclarations

def inject_device(name, transport)
transport_wrapper = Puppet::ResourceApi::Transport::Wrapper.new(name, transport)

if Puppet::Util::NetworkDevice.respond_to?(:set_device)
Puppet::Util::NetworkDevice.set_device(name, transport_wrapper)
else
Puppet::Util::NetworkDevice.instance_variable_set(:@current, transport_wrapper)
end
end
module_function :inject_device # rubocop:disable Style/AccessModifierDeclarations

def self.validate(name, connection_info)
init_transports
require "puppet/transport/schema/#{name}" unless @transports[@environment].key? name
transport_schema = @transports[@environment][name]
if transport_schema.nil?
raise Puppet::DevError, 'Transport for `%{target}` not registered with `%{environment}`' % {
target: name,
environment: @environment,
}
end
message_prefix = 'The connection info provided does not match the Transport Schema'
transport_schema.check_schema(connection_info, message_prefix)
transport_schema.validate(connection_info)
end
private_class_method :validate

def self.get_context(name)
require 'puppet/resource_api/puppet_context'
Puppet::ResourceApi::PuppetContext.new(@transports[@environment][name])
end
private_class_method :get_context

def self.init_transports
lookup = Puppet.lookup(:current_environment) if Puppet.respond_to? :lookup
@environment = if lookup.nil?
:transports_default
else
lookup.name
end
@transports ||= {}
@transports[@environment] ||= {}
end
private_class_method :init_transports

def self.wrap_sensitive(name, connection_info)
transport_schema = @transports[@environment][name]
if transport_schema
transport_schema.definition[:connection_info].each do |attr_name, options|
if options.key?(:sensitive) && (options[:sensitive] == true)
connection_info[attr_name] = Puppet::Pops::Types::PSensitiveType::Sensitive.new(connection_info[attr_name])
end
end
end
connection_info
end
private_class_method :wrap_sensitive
end
55 changes: 55 additions & 0 deletions lib/puppet/resource_api/transport/wrapper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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, :schema

def initialize(name, url_or_config_or_transport)
if url_or_config_or_transport.is_a? String
url = URI.parse(url_or_config_or_transport)
raise "Unexpected url '#{url_or_config_or_transport}' found. Only file:/// URLs for configuration supported at the moment." unless url.scheme == 'file'
raise "Trying to load config from '#{url.path}, but file does not exist." if url && !File.exist?(url.path)
config = self.class.deep_symbolize(Hocon.load(url.path, syntax: Hocon::ConfigSyntax::HOCON) || {})
elsif url_or_config_or_transport.is_a? Hash
config = url_or_config_or_transport
elsif transport_class?(name, url_or_config_or_transport)
@transport = url_or_config_or_transport
end

@transport ||= Puppet::ResourceApi::Transport.connect(name, config)
@schema = Puppet::ResourceApi::Transport.list[name]
end

def transport_class?(name, transport)
class_name = name.split('_').map { |e| e.capitalize }.join
expected = Puppet::Transport.const_get(class_name).to_s
expected == transport.class.to_s
end

def facts
context = Puppet::ResourceApi::PuppetContext.new(@schema)
# @transport.facts + custom_facts # look into custom facts work by TP
@transport.facts(context)
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
@transport.send(method_name, *args, &block)
else
super
end
end

# From https://stackoverflow.com/a/11788082/4918
def self.deep_symbolize(obj)
return obj.each_with_object({}) { |(k, v), memo| memo[k.to_sym] = deep_symbolize(v); } if obj.is_a? Hash
return obj.each_with_object([]) { |v, memo| memo << deep_symbolize(v); } if obj.is_a? Array
obj
end
end
Loading