Skip to content

Commit

Permalink
(PDK-513) implement supports_noop
Browse files Browse the repository at this point in the history
See https://github.com/DavidS/puppet-specifications/blob/resourceapi/language/resource-api/README.md#provider-feature-supports_noop for a definition of the feature.

Since puppet is not calling `flush` for us, this is only a example implementation,
that likely will never be exercised.
  • Loading branch information
DavidS committed Mar 13, 2018
1 parent 7490e51 commit 596ceae
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 13 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ There are still a few notable gaps between the implementation and the specificat
* Only a single runtime environment (the Puppet commands) is currently implemented.
* `auto*` definitions.

Restrictions of running under puppet:
* `supports_noop` is not effective, as puppet doesn't call into the type under noop.

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
Expand Down
11 changes: 10 additions & 1 deletion lib/puppet/resource_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ def register_type(definition)
raise Puppet::DevError, 'requires a name' unless definition.key? :name
raise Puppet::DevError, 'requires attributes' unless definition.key? :attributes

definition[:features] ||= []
supported_features = %w[supports_noop canonicalize].freeze
unknown_features = definition[:features] - supported_features
Puppet.warning("Unknown feature detected: #{unknown_features.inspect}") unless unknown_features.empty?

# prepare the ruby module for the provider
# this has to happen before Puppet::Type.newtype starts autoloading providers
# it also needs to be guarded against the namespace already being defined by something
Expand Down Expand Up @@ -207,7 +212,11 @@ def my_provider

Puppet.debug("Target State: #{target_state.inspect}")

my_provider.set(context, title => { is: @rapi_current_state, should: target_state })
if definition[:features] && definition[:features].include?('supports_noop')
my_provider.set(context, { title => { is: @rapi_current_state, should: target_state } }, noop: noop?)
else
my_provider.set(context, title => { is: @rapi_current_state, should: target_state }) unless noop?
end
raise 'Execution encountered an error' if context.failed?
end

Expand Down
29 changes: 29 additions & 0 deletions spec/acceptance/noop_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require 'spec_helper'

require 'open3'
require 'tempfile'

RSpec.describe 'exercising noop' do
let(:common_args) { '--verbose --trace --modulepath spec/fixtures' }

describe 'using `puppet resource`' do
it 'is setup correctly' do
stdout_str, status = Open3.capture2e("puppet resource #{common_args} test_noop_support")
expect(stdout_str.strip).to match %r{^test_noop_support}
expect(status).to eq 0
end

it 'executes a change' do
stdout_str, status = Open3.capture2e("puppet resource #{common_args} test_noop_support foo ensure=absent")
expect(stdout_str.strip).to match %r{noop: false}
expect(status).to eq 0
end

it 'respects --noop' do
pending 'puppet does not call flush() to trigger execution'
stdout_str, status = Open3.capture2e("puppet resource #{common_args} --noop test_noop_support foo ensure=absent")
expect(stdout_str.strip).to match %r{noop: true}
expect(status).to eq 0
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
require 'puppet/resource_api/simple_provider'

# Implementation for the test_noop_support type using the Resource API.
class Puppet::Provider::TestNoopSupport::TestNoopSupport < Puppet::ResourceApi::SimpleProvider
class Puppet::Provider::TestNoopSupport::TestNoopSupport
def get(_context)
[
{
Expand All @@ -16,15 +16,8 @@ def get(_context)
]
end

def create(context, name, should)
context.notice("Creating '#{name}' with #{should.inspect}")
end

def update(context, name, should)
context.notice("Updating '#{name}' with #{should.inspect}")
end

def delete(context, name)
context.notice("Deleting '#{name}'")
def set(context, changes, noop: false)
context.notice("noop: #{noop}")
context.notice("inspect: #{changes.inspect}")
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
docs: <<-EOS,
This type provides Puppet with the capabilities to manage ...
EOS
features: [],
features: ['supports_noop'],
attributes: {
ensure: {
type: 'Enum[present, absent]',
Expand Down
65 changes: 65 additions & 0 deletions spec/puppet/resource_api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -837,4 +837,69 @@ def set(_context, changes)
it { expect(type.apply_to).to eq(:device) }
end
end

context 'with a `supports_noop` provider', agent_test: true do
let(:definition) do
{
name: 'test_noop_support',
features: ['supports_noop'],
attributes: {
ensure: {
type: 'Enum[present, absent]',
default: 'present',
},
name: {
type: 'String',
behaviour: :namevar,
},
},
}
end
let(:type) { Puppet::Type.type(:test_noop_support) }
let(:provider_class) do
# Hide the `noop:` kwarg from older jruby, which is still on ruby-1.9 syntax.
eval(<<CODE, binding, __FILE__, __LINE__ + 1)
Class.new do
def get(_context)
[]
end
def set(_context, _changes, noop: false); end
end
CODE
end
let(:provider) { instance_double('Puppet::Provider::TestNoopSupport::TestNoopSupport', 'provider') }

before(:each) do
stub_const('Puppet::Provider::TestNoopSupport', Module.new)
stub_const('Puppet::Provider::TestNoopSupport::TestNoopSupport', provider_class)
allow(provider_class).to receive(:new).and_return(provider)
allow(provider).to receive(:get).and_return([])
end

it { expect { described_class.register_type(definition) }.not_to raise_error }

describe 'flush getting called in noop mode' do
it 'set gets called with noop:true' do
expect(provider).to receive(:set).with(anything, anything, noop: true)
instance = type.new(name: 'test', ensure: 'present', noop: true)
instance.flush
end
end
end

context 'when loading a type with unknown features' do
let(:definition) do
{
name: 'test_noop_support',
features: ['no such feature'],
attributes: {},
}
end

it 'warns about the feature' do
expect(Puppet).to receive(:warning).with(%r{Unknown feature detected:.*no such feature})
expect { described_class.register_type(definition) }.not_to raise_error
end
end
end

0 comments on commit 596ceae

Please sign in to comment.