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

(PDK-1007) implement enough to support purge=>true #95

Merged
merged 8 commits into from
Jun 11, 2018
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Puppet::ResourceApi [![TravisCI Build Status](https://travis-ci.org/puppetlabs/puppet-resource_api.svg?branch=master)](https://travis-ci.org/puppetlabs/puppet-resource_api) [![Appveyor Build status](https://ci.appveyor.com/api/projects/status/8o9s1ax0hs8lm5fd/branch/master?svg=true)](https://ci.appveyor.com/project/puppetlabs/puppet-resource-api/branch/master)
[![codecov](https://codecov.io/gh/puppetlabs/puppet-resource_api/branch/master/graph/badge.svg)](https://codecov.io/gh/puppetlabs/puppet-resource_api)

This is an implementation of the [Resource API](https://github.com/DavidS/puppet-specifications/blob/resourceapi/language/resource-api/README.md) proposal. Find a working example of a new-style provider in the [experimental puppetlabs-apt branch](https://github.com/DavidS/puppetlabs-apt/blob/resource-api-experiments/lib/puppet/provider/apt_key2/apt_key2.rb). There is also the corresponding [type](https://github.com/DavidS/puppetlabs-apt/blob/resource-api-experiments/lib/puppet/type/apt_key2.rb), [provider](https://github.com/DavidS/puppetlabs-apt/blob/resource-api-experiments/lib/puppet/provider/apt_key2/apt_key2.rb), and [new unit tests](https://github.com/DavidS/puppetlabs-apt/blob/resource-api-experiments/spec/unit/puppet/provider/apt_key2/apt_key2_spec.rb) for 100% coverage.
This is an implementation of the [Resource API](https://github.com/puppetlabs/puppet-specifications/blob/master/language/resource-api/README.md) proposal. Find a working example of a new-style provider in the [experimental puppetlabs-apt branch](https://github.com/DavidS/puppetlabs-apt/blob/resource-api-experiments/lib/puppet/provider/apt_key2/apt_key2.rb). There is also the corresponding [type](https://github.com/DavidS/puppetlabs-apt/blob/resource-api-experiments/lib/puppet/type/apt_key2.rb), [provider](https://github.com/DavidS/puppetlabs-apt/blob/resource-api-experiments/lib/puppet/provider/apt_key2/apt_key2.rb), and [new unit tests](https://github.com/DavidS/puppetlabs-apt/blob/resource-api-experiments/spec/unit/puppet/provider/apt_key2/apt_key2_spec.rb) for 100% coverage.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like another reference on line 179 that requires updating as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed!


## Getting started

Expand Down Expand Up @@ -176,7 +176,7 @@ After this, `puppet device` will be able to use the new provider, and supply it

### Further Reading

The [Resource API](https://github.com/DavidS/puppet-specifications/blob/resourceapi/language/resource-api/README.md) describes details of all the capabilities of this gem.
The [Resource API](https://github.com/puppetlabs/puppet-specifications/blob/master/language/resource-api/README.md) describes details of all the capabilities of this gem.

This [Introduction to Testing Puppet Modules](https://www.netways.de/index.php?id=3445#c44135) talk describes rspec usage in more detail.

Expand Down
57 changes: 35 additions & 22 deletions lib/puppet/resource_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,15 @@ def type_definition
super(attributes)
end

def name
title
end

def to_resource
to_resource_shim(super)
end

define_method(:to_resource_shim) do |resource|
# require'pry';binding.pry
resource_hash = Hash[resource.keys.map { |k| [k, resource[k]] }]
resource_hash[:title] = resource.title
ResourceShim.new(resource_hash, type_definition.name, type_definition.namevars, type_definition.attributes)
Expand Down Expand Up @@ -284,41 +287,48 @@ def call_provider(value); end
# puts 'instances'
# force autoloading of the provider
provider(type_definition.name)

my_provider.get(context).map do |resource_hash|
type_definition.check_schema(resource_hash)
Puppet::ResourceApi::TypeShim.new(resource_hash, type_definition.name, type_definition.namevars, type_definition.attributes)
result = new(title: resource_hash[type_definition.namevars.first])
result.cache_current_state(resource_hash)
result
end
end

define_method(:retrieve) do
# puts "retrieve(#{title.inspect})"
result = Puppet::Resource.new(self.class, title)

current_state = if type_definition.feature?('simple_get_filter')
my_provider.get(context, [title]).first
else
my_provider.get(context).find { |h| namevar_match?(h) }
end

type_definition.check_schema(current_state) if current_state
strict_check(current_state) if current_state && type_definition.feature?('canonicalize')
define_method(:refresh_current_state) do
@rapi_current_state = if type_definition.feature?('simple_get_filter')
my_provider.get(context, [title]).first
else
my_provider.get(context).find { |h| namevar_match?(h) }
end

if current_state
current_state.each do |k, v|
result[k] = v
end
if @rapi_current_state
type_definition.check_schema(@rapi_current_state)
strict_check(@rapi_current_state) if type_definition.feature?('canonicalize')
else
result[:title] = title
result[:ensure] = :absent if type_definition.ensurable?
@rapi_current_state = { title: title }
@rapi_current_state[:ensure] = :absent if type_definition.ensurable?
end
end

# Use this to set the current state from the `instances` method
def cache_current_state(resource_hash)
@rapi_current_state = resource_hash
strict_check(@rapi_current_state) if type_definition.feature?('canonicalize')
end

define_method(:retrieve) do
refresh_current_state unless @rapi_current_state

Puppet.debug("Current State: #{@rapi_current_state.inspect}")

result = Puppet::Resource.new(self.class, title, parameters: @rapi_current_state)
# puppet needs ensure to be a symbol
result[:ensure] = result[:ensure].to_sym if type_definition.ensurable? && result[:ensure].is_a?(String)

raise_missing_attrs

@rapi_current_state = current_state
Puppet.debug("Current State: #{@rapi_current_state.inspect}")
result
end

Expand Down Expand Up @@ -362,6 +372,9 @@ def call_provider(value); end
my_provider.set(context, title => { is: @rapi_current_state, should: target_state }) unless noop?
end
raise 'Execution encountered an error' if context.failed?

# remember that we have successfully reached our desired state
@rapi_current_state = target_state
end

define_method(:raise_missing_attrs) do
Expand Down
23 changes: 0 additions & 23 deletions lib/puppet/resource_api/glue.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,6 @@

module Puppet; end # rubocop:disable Style/Documentation
module Puppet::ResourceApi
# A trivial class to provide the functionality required to push data through the existing type/provider parts of puppet
class TypeShim
attr_reader :values, :typename, :namevars, :attr_def

def initialize(resource_hash, typename, namevars, attr_def)
# internalize and protect - needs to go deeper
@values = resource_hash.dup.freeze

@typename = typename
@namevars = namevars
@attr_def = attr_def
@resource = ResourceShim.new(@values, @typename, @namevars, @attr_def)
end

def to_resource
@resource
end

def name
@resource.title
end
end

# A trivial class to provide the functionality required to push data through the existing type/provider parts of puppet
class ResourceShim
attr_reader :values, :typename, :namevars, :attr_def
Expand Down
2 changes: 1 addition & 1 deletion misc/ANNOUNCEMENT_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Send out announcements for major new feature releases, and high-impact bugfixes to <[email protected]>, <[email protected]>, <[email protected]>, and the puppet internal mailing lists <[email protected]> and <[email protected]>.
Send out announcements for major new feature releases, and high-impact bugfixes to <puppet-[email protected]>, <puppet-[email protected]>, <[email protected]>, <[email protected]>, and the puppet internal mailing lists <[email protected]> and <[email protected]>.

Before sending, do check that all links are still valid. Feel free to adjust the text to match better with the circumstances of the release, or add other news that are relevant at the time. If you make changes, consider committing them here, for the benefit of future-you.

Expand Down
3 changes: 2 additions & 1 deletion spec/acceptance/composite_namevar_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
stdout_str, status = Open3.capture2e("puppet resource #{common_args} composite_namevar php")
expect(stdout_str.strip).to match %r{^composite_namevar \{ \'php\'}
expect(stdout_str.strip).to match %r{ensure\s*=> \'present\'}
expect(stdout_str.strip).to match %r{package\s*=> \'php\'}
expect(status).to eq 0
end
it 'properly identifies an absent resource if only the title is provided' do
Expand Down Expand Up @@ -79,7 +80,7 @@
context 'when managing a present instance' do
let(:manifest) { 'composite_namevar { php-gem: }' }

it { expect(@stdout_str).to match %r{Current State: \{:package=>"php", :manager=>"gem", :ensure=>"present"\}} }
it { expect(@stdout_str).to match %r{Current State: \{:package=>"php", :manager=>"gem", :ensure=>"present", :value=>"b"\}} }
it { expect(@status.exitstatus).to eq 0 }
end

Expand Down
23 changes: 12 additions & 11 deletions spec/acceptance/device_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@
before(:each) { skip 'No device --apply in the puppet gems yet' if ENV['PUPPET_GEM_VERSION'] }

describe 'using `puppet resource`' do
it 'reads resources from the target system' do
stdout_str, status = Open3.capture2e("puppet resource #{common_args} device_provider")
expected_values = 'device_provider { \'wibble\': \n\s+ensure => \'present\',\n\s+string => \'sample\',\n\#\s+string_ro => \'fixed\', # Read Only\n}'
expect(stdout_str.strip).to match %r{\A(DL is deprecated, please use Fiddle\n)?#{expected_values}\Z}
expect(status).to eq 0
end
it 'manages resources on the target system' do
stdout_str, status = Open3.capture2e("puppet resource #{common_args} device_provider foo ensure=present #{default_type_values}")
expect(stdout_str).to match %r{Notice: /Device_provider\[foo\]/ensure: defined 'ensure' as 'present'}
Expand All @@ -33,31 +27,38 @@
'Returned values: \{:name=>"wibble", :ensure=>"present", :string=>"sample", :string_ro=>"fixed"\}\n'\
'Canonicalized values: \{:name=>"wibble", :ensure=>"present", :string=>"changed", :string_ro=>"fixed"\}'
expect(stdout_str).to match %r{#{stdmatch}}
expect(status.success?).to be_falsey # rubocop:disable RSpec/PredicateMatcher
expect(status).to be_success
end
end

context 'with strict checking at warning level' do
let(:common_args) { '--verbose --trace --strict=warning --modulepath spec/fixtures' }

it 'deals with canonicalized resources correctly' do
stdout_str, status = Open3.capture2e("puppet resource #{common_args} device_provider wibble ensure=present #{default_type_values}")
stdout_str, status = Open3.capture2e("puppet resource #{common_args} device_provider wibble ensure=present #{default_type_values}")
stdmatch = 'Warning: device_provider\[wibble\]#get has not provided canonicalized values.\n'\
'Returned values: \{:name=>"wibble", :ensure=>"present", :string=>"sample", :string_ro=>"fixed"\}\n'\
'Canonicalized values: \{:name=>"wibble", :ensure=>"present", :string=>"changed", :string_ro=>"fixed"\}'
expect(stdout_str).to match %r{#{stdmatch}}
expect(status.success?).to be_truthy # rubocop:disable RSpec/PredicateMatcher
expect(status).to be_success
end
end

context 'with strict checking turned off' do
let(:common_args) { '--verbose --trace --strict=off --modulepath spec/fixtures' }

it 'reads resources from the target system' do
stdout_str, status = Open3.capture2e("puppet resource #{common_args} device_provider")
expected_values = 'device_provider { \'wibble\': \n\s+ensure => \'present\',\n\s+string => \'sample\',\n\#\s+string_ro => \'fixed\', # Read Only\n string_param => \'default value\',\n}'
expect(stdout_str.strip).to match %r{\A(DL is deprecated, please use Fiddle\n)?#{expected_values}\Z}
expect(status).to eq 0
end

it 'deals with canonicalized resources correctly' do
stdout_str, status = Open3.capture2e("puppet resource #{common_args} device_provider wibble ensure=present #{default_type_values}")
stdout_str, status = Open3.capture2e("puppet resource #{common_args} device_provider wibble ensure=present #{default_type_values}")
stdmatch = 'Notice: /Device_provider\[wibble\]/string: string changed \'sample\' to \'changed\''
expect(stdout_str).to match %r{#{stdmatch}}
expect(status.success?).to be_truthy # rubocop:disable RSpec/PredicateMatcher
expect(status).to be_success
end
end
end
Expand Down
1 change: 1 addition & 0 deletions spec/acceptance/namevar_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
stdout_str, status = Open3.capture2e("puppet resource #{common_args} multiple_namevar php")
expect(stdout_str.strip).to match %r{^multiple_namevar \{ \'php\'}
expect(stdout_str.strip).to match %r{ensure\s*=> \'present\'}
expect(stdout_str.strip).to match %r{package\s*=> \'php\'}
expect(status).to eq 0
end
it 'creates a previously absent resource if all namevars are provided' do
Expand Down
15 changes: 15 additions & 0 deletions spec/acceptance/purge_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require 'spec_helper'
require 'tempfile'

RSpec.describe 'purging' do
let(:common_args) { '--verbose --trace --strict=error --modulepath spec/fixtures' }

describe 'using `puppet apply`' do
it 'applies a catalog successfully' do
stdout_str, _status = Open3.capture2e("puppet apply #{common_args} -e \"resources { 'test_bool': purge => true }\"")
expect(stdout_str).to match %r{Deleting 'foo'}
expect(stdout_str).to match %r{Deleting 'bar'}
expect(stdout_str).not_to match %r{Error:}
end
end
end
8 changes: 4 additions & 4 deletions spec/acceptance/simple_get_filter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
end

context 'when using `get` to access a specific resource' do
it 'returns resource' do
it '`puppet resource` uses `instances` and does the filtering' do
stdout_str, status = Open3.capture2e("puppet resource #{common_args} test_simple_get_filter foo")

expect(stdout_str.strip).to match %r{test_string\s*=>\s*'default'}
Expand All @@ -27,10 +27,10 @@
end

context 'when using `get` to remove a specific resource' do
it 'receives names array' do
stdout_str, status = Open3.capture2e("puppet resource #{common_args} test_simple_get_filter foo ensure=absent")
it 'the `retrieve` function does the lookup' do
stdout_str, status = Open3.capture2e("puppet resource #{common_args} --noop test_simple_get_filter foo ensure=absent")

expect(stdout_str.strip).to match %r{undefined 'ensure' from 'present'}
expect(stdout_str.strip).to match %r{current_value '?present'?, should be '?absent'? \(noop\)}
expect(stdout_str.strip).to match %r{test_string\s*=>\s*'foo found'}
expect(status).to eq 0
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
class Puppet::Provider::CompositeNamevar::CompositeNamevar
def initialize
@current_values ||= [
{ package: 'php', manager: 'yum', ensure: 'present' },
{ package: 'php', manager: 'gem', ensure: 'present' },
{ package: 'mysql', manager: 'yum', ensure: 'present' },
{ package: 'mysql', manager: 'gem', ensure: 'present' },
{ package: 'foo', manager: 'bar', ensure: 'present' },
{ package: 'bar', manager: 'foo', ensure: 'present' },
{ package: 'php', manager: 'yum', ensure: 'present', value: 'a' },
{ package: 'php', manager: 'gem', ensure: 'present', value: 'b' },
{ package: 'mysql', manager: 'yum', ensure: 'present', value: 'c' },
{ package: 'mysql', manager: 'gem', ensure: 'present', value: 'd' },
{ package: 'foo', manager: 'bar', ensure: 'present', value: 'e' },
{ package: 'bar', manager: 'foo', ensure: 'present', value: 'f' },
]
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@
desc: 'The directory containing the resource you want to manage.',
behaviour: :namevar,
},
value: {
type: 'Optional[String]',
desc: 'An arbitrary string for debugging purposes',
}
},
)
37 changes: 0 additions & 37 deletions spec/puppet/resource_api/glue_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,6 @@
# `puppet resource` read/write cycle to ensure that there is nothing
# funky happening with new puppet versions.
RSpec.describe 'the dirty bits' do
describe Puppet::ResourceApi::TypeShim do
subject(:instance) do
described_class.new({ attr: 'value', attr_ro: 'fixed' }, 'typename', [:namevarname],
namevarname: { type: 'String', behaviour: :namevar, desc: 'the title' },
attr: { type: 'String', desc: 'a string parameter' },
attr_ro: { type: 'String', desc: 'a string readonly', behaviour: :read_only })
end

describe '.values' do
it { expect(instance.values).to eq(attr: 'value', attr_ro: 'fixed') }
end

describe '.typename' do
it { expect(instance.typename).to eq 'typename' }
end

describe '.namevars' do
it { expect(instance.namevars).to eq [:namevarname] }
end

describe '.to_resource' do
it { expect(instance.to_resource).to be_a Puppet::ResourceApi::ResourceShim }

describe '.values' do
it { expect(instance.to_resource.values).to eq(attr: 'value', attr_ro: 'fixed') }
end

describe '.typename' do
it { expect(instance.to_resource.typename).to eq 'typename' }
end

describe '.namevars' do
it { expect(instance.to_resource.namevars).to eq [:namevarname] }
end
end
end

describe Puppet::ResourceApi::ResourceShim do
subject(:instance) do
described_class.new({ namevarname: title, attr: 'value', attr_ro: 'fixed' }, 'typename', [:namevarname],
Expand Down
12 changes: 6 additions & 6 deletions spec/puppet/resource_api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1253,9 +1253,9 @@ def set(_context, changes)
end
end

context 'when retrieving instances through `get`' do
it('instances returns an Array') { expect(type.instances).to be_a Array }
it('returns an array of TypeShims') { expect(type.instances[0]).to be_a Puppet::ResourceApi::TypeShim }
context 'when retrieving instances' do
it('returns an Array') { expect(type.instances).to be_a Array }
it('returns an array of Type instances') { expect(type.instances[0]).to be_a Puppet::Type.type(:canonicalizer) }
it('its name is set correctly') { expect(type.instances[0].name).to eq 'somename' }
end

Expand Down Expand Up @@ -1371,9 +1371,9 @@ def set(_context, changes)
end
end

context 'when retrieving instances through `get`' do
it('instances returns an Array') { expect(type.instances).to be_a Array }
it('returns an array of TypeShims') { expect(type.instances[0]).to be_a Puppet::ResourceApi::TypeShim }
context 'when retrieving instances' do
it('returns an Array') { expect(type.instances).to be_a Array }
it('returns an array of Type instances') { expect(type.instances[0]).to be_a Puppet::Type.type(:passthrough) }
it('its name is set correctly') { expect(type.instances[0].name).to eq 'somename' }
end

Expand Down