Skip to content

Commit 329a43e

Browse files
committed
Provide stdlib::defined_with_params function
1 parent 50a6b6b commit 329a43e

File tree

4 files changed

+285
-0
lines changed

4 files changed

+285
-0
lines changed

REFERENCE.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ the provided regular expression.
115115
* [`stdlib::batch_escape`](#stdlib--batch_escape): Escapes a string so that it can be safely used in a batch shell command line.
116116
* [`stdlib::crc32`](#stdlib--crc32): Run a CRC32 calculation against a given value.
117117
* [`stdlib::deferrable_epp`](#stdlib--deferrable_epp): This function returns either a rendered template or a deferred function to render at runtime. If any of the values in the variables hash are
118+
* [`stdlib::defined_with_params`](#stdlib--defined_with_params): @summary Takes a resource reference and an optional hash of attributes. Returns `true` if a resource with the specified attributes has a
118119
* [`stdlib::end_with`](#stdlib--end_with): Returns true if str ends with one of the prefixes given. Each of the prefixes should be a String.
119120
* [`stdlib::ensure`](#stdlib--ensure): function to cast ensure parameter to resource specific value
120121
* [`stdlib::ensure_packages`](#stdlib--ensure_packages): Takes a list of packages and only installs them if they don't already exist.
@@ -3167,6 +3168,64 @@ Data type: `Hash`
31673168

31683169

31693170

3171+
### <a name="stdlib--defined_with_params"></a>`stdlib::defined_with_params`
3172+
3173+
Type: Ruby 4.x API
3174+
3175+
@summary
3176+
Takes a resource reference and an optional hash of attributes.
3177+
3178+
Returns `true` if a resource with the specified attributes has already been added
3179+
to the catalog, and `false` otherwise.
3180+
3181+
```
3182+
user { 'dan':
3183+
ensure => present,
3184+
}
3185+
3186+
if ! stdlib::defined_with_params(User[dan], {'ensure' => 'present' }) {
3187+
user { 'dan': ensure => present, }
3188+
}
3189+
```
3190+
3191+
@return [Boolean]
3192+
returns `true` or `false`
3193+
3194+
#### `stdlib::defined_with_params(Variant[String,Type[Resource]] $reference, Variant[String[0],Hash] $params)`
3195+
3196+
@summary
3197+
Takes a resource reference and an optional hash of attributes.
3198+
3199+
Returns `true` if a resource with the specified attributes has already been added
3200+
to the catalog, and `false` otherwise.
3201+
3202+
```
3203+
user { 'dan':
3204+
ensure => present,
3205+
}
3206+
3207+
if ! stdlib::defined_with_params(User[dan], {'ensure' => 'present' }) {
3208+
user { 'dan': ensure => present, }
3209+
}
3210+
```
3211+
3212+
@return [Boolean]
3213+
returns `true` or `false`
3214+
3215+
Returns: `Boolean` Returns `true` if a resource has already been added
3216+
3217+
##### `reference`
3218+
3219+
Data type: `Variant[String,Type[Resource]]`
3220+
3221+
The resource reference to check for
3222+
3223+
##### `params`
3224+
3225+
Data type: `Variant[String[0],Hash]`
3226+
3227+
The resource's attributes
3228+
31703229
### <a name="stdlib--end_with"></a>`stdlib::end_with`
31713230

31723231
Type: Ruby 4.x API
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# frozen_string_literal: true
2+
3+
# @summary
4+
# Takes a resource reference and an optional hash of attributes.
5+
#
6+
# Returns `true` if a resource with the specified attributes has already been added
7+
# to the catalog, and `false` otherwise.
8+
#
9+
# ```
10+
# user { 'dan':
11+
# ensure => present,
12+
# }
13+
#
14+
# if ! stdlib::defined_with_params(User[dan], {'ensure' => 'present' }) {
15+
# user { 'dan': ensure => present, }
16+
# }
17+
# ```
18+
#
19+
# @return [Boolean]
20+
# returns `true` or `false`
21+
Puppet::Functions.create_function(:'stdlib::defined_with_params', Puppet::Functions::InternalFunction) do
22+
# @return [Boolean]
23+
# Returns `true` if a resource has already been added
24+
#
25+
# @param reference
26+
# The resource reference to check for
27+
# @param params
28+
# The resource's attributes
29+
dispatch :defined_with_params do
30+
scope_param
31+
param 'Variant[String,Type[Resource]]', :reference
32+
param 'Variant[String[0],Hash]', :params
33+
end
34+
def defined_with_params(scope, reference, params)
35+
params = {} if params == ''
36+
ret = false
37+
38+
if Puppet::Util::Package.versioncmp(Puppet.version, '4.6.0') >= 0
39+
# Workaround for PE-20308
40+
if reference.is_a?(String)
41+
type_name, title = Puppet::Resource.type_and_title(reference, nil)
42+
type = Puppet::Pops::Evaluator::Runtime3ResourceSupport.find_resource_type_or_class(scope, type_name.downcase)
43+
elsif reference.is_a?(Puppet::Resource)
44+
type = reference.type
45+
title = reference.title
46+
elsif reference.is_a?(Puppet::Pops::Types::PResourceType)
47+
type = reference.type_name
48+
title = reference.title
49+
else
50+
raise(ArgumentError, "Reference is not understood: '#{reference.class}'")
51+
end
52+
# end workaround
53+
else
54+
type = reference.to_s
55+
title = nil
56+
end
57+
58+
resources = if title.nil? || title.empty?
59+
scope.catalog.resources.select { |r| r.type == type }
60+
else
61+
[scope.findresource(type, title)]
62+
end
63+
64+
resources.compact.each do |res|
65+
# If you call this from within a defined type, it will find itself
66+
Puppet.debug res.to_s, scope.resource.to_s, scope.resource.inspect
67+
next if res.to_s == scope.resource.to_s
68+
69+
matches = params.map do |key, value|
70+
# eql? avoids bugs caused by monkeypatching in puppet
71+
res_is_undef = res[key].eql?(:undef) || res[key].nil?
72+
value_is_undef = value.eql?(:undef) || value.nil?
73+
found_match = (res_is_undef && value_is_undef) || (res[key] == value)
74+
75+
Puppet.debug("Matching resource is #{res}") if found_match
76+
77+
found_match
78+
end
79+
ret = params.empty? || !matches.include?(false)
80+
81+
break if ret
82+
end
83+
84+
Puppet.debug("Resource #{reference} was not determined to be defined") unless ret
85+
86+
ret
87+
end
88+
end

lib/puppet/parser/functions/defined_with_params.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
reference, params = vals
2828
raise(ArgumentError, 'Must specify a reference') unless reference
2929

30+
unless ENV['STDLIB_LOG_DEPRECATIONS'] == 'false'
31+
Puppet.deprecation_warning('defined_with_params: This function is deprecated, please use stdlib::defined_with_params instead.')
32+
end
33+
3034
params = {} if !params || params == ''
3135
ret = false
3236

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe 'stdlib::defined_with_params' do
6+
describe 'when no resource is specified' do
7+
it { is_expected.to run.with_params.and_raise_error(ArgumentError) }
8+
end
9+
10+
describe 'when compared against a resource with no attributes' do
11+
let :pre_condition do
12+
'user { "dan": }'
13+
end
14+
15+
it { is_expected.to run.with_params('User[dan]', {}).and_return(true) }
16+
it { is_expected.to run.with_params('User[bob]', {}).and_return(false) }
17+
it { is_expected.to run.with_params('User[dan]', 'foo' => 'bar').and_return(false) }
18+
19+
context 'with UTF8 and double byte characters' do
20+
it { is_expected.to run.with_params('User[ĵĭмოү]', {}).and_return(false) }
21+
it { is_expected.to run.with_params('User[ポーラ]', {}).and_return(false) }
22+
end
23+
end
24+
25+
describe 'when compared against a resource with attributes' do
26+
let :pre_condition do
27+
'user { "dan": ensure => present, shell => "/bin/csh", managehome => false}'
28+
end
29+
30+
it { is_expected.to run.with_params('User[dan]', {}).and_return(true) }
31+
it { is_expected.to run.with_params('User[dan]', '').and_return(true) }
32+
it { is_expected.to run.with_params('User[dan]', 'ensure' => 'present').and_return(true) }
33+
it { is_expected.to run.with_params('User[dan]', 'ensure' => 'present', 'managehome' => false).and_return(true) }
34+
it { is_expected.to run.with_params('User[dan]', 'ensure' => 'absent', 'managehome' => false).and_return(false) }
35+
end
36+
37+
describe 'when passing undef values' do
38+
let :pre_condition do
39+
'file { "/tmp/a": ensure => present }'
40+
end
41+
let(:is_puppet_6_or_greater) { Puppet::Util::Package.versioncmp(Puppet.version, '6.0.0') >= 0 }
42+
let(:undef_value) { is_puppet_6_or_greater ? nil : :undef } # even if :undef would work on 6.0.1, :undef should not be used
43+
44+
it { is_expected.to run.with_params('File[/tmp/a]', {}).and_return(true) }
45+
it { is_expected.to run.with_params('File[/tmp/a]', 'ensure' => 'present', 'owner' => undef_value).and_return(true) }
46+
end
47+
48+
describe 'when the reference is a' do
49+
let :pre_condition do
50+
'user { "dan": }'
51+
end
52+
53+
context 'with reference' do
54+
it { is_expected.to run.with_params(Puppet::Resource.new('User[dan]'), {}).and_return(true) }
55+
end
56+
57+
if Puppet::Util::Package.versioncmp(Puppet.version, '4.6.0') >= 0
58+
context 'with array' do
59+
it 'fails' do
60+
expect {
61+
subject.execute(['User[dan]'], {})
62+
}.to raise_error(ArgumentError, %r{expects a value of type String or Type\[Resource\], got Tuple})
63+
end
64+
end
65+
end
66+
end
67+
68+
describe 'when passed a defined type' do
69+
let :pre_condition do
70+
<<-PRECOND
71+
define test::deftype(
72+
Optional $port = undef
73+
) { }
74+
75+
test::deftype { "foo": }
76+
test::deftype { "baz": port => 100 }
77+
test::deftype { "adv": port => 200 }
78+
test::deftype { "adv2": port => 200 }
79+
80+
# Unsure how to stub this out below properly
81+
if stdlib::defined_with_params(Test::Deftype, { 'port' => 200 }) {
82+
notify { 'Duplicate found somewhere': }
83+
}
84+
if stdlib::defined_with_params(Test::Deftype, { 'port' => 'nope' }) {
85+
notify { 'Should not find me': }
86+
}
87+
PRECOND
88+
end
89+
90+
it { is_expected.to run.with_params('Test::Deftype[foo]', {}).and_return(true) }
91+
it { is_expected.to run.with_params('Test::Deftype[bar]', {}).and_return(false) }
92+
it { is_expected.to run.with_params(Puppet::Resource.new('Test::Deftype[foo]'), {}).and_return(true) }
93+
94+
it {
95+
expect(subject).to run.with_params(Puppet::Resource.new('Test::Deftype[bar]'), {}).and_return(false)
96+
97+
expect(catalogue.resource('Notify[Duplicate found somewhere]')).not_to be_nil
98+
expect(catalogue.resource('Notify[Should not find me]')).to be_nil
99+
}
100+
end
101+
102+
describe 'when called from within a defined type looking for a defined type of the same type' do
103+
let :pre_condition do
104+
<<-PRECOND
105+
define test::deftype(
106+
Optional $port = undef
107+
) {
108+
if stdlib::defined_with_params(Test::Deftype, { 'port' => $port }) {
109+
fail('Ruh Roh Shaggy')
110+
}
111+
}
112+
113+
test::deftype { 'foo': }
114+
test::deftype { 'bar': port => 200 }
115+
PRECOND
116+
end
117+
118+
# Testing to make sure that the internal logic handles this case via the pre_condition
119+
it { is_expected.to run.with_params('NoOp[noop]', {}).and_return(false) }
120+
end
121+
122+
describe 'when passed a class' do
123+
let :pre_condition do
124+
'class test () { } class { "test": }'
125+
end
126+
127+
it { is_expected.to run.with_params('Class[test]', {}).and_return(true) }
128+
it { is_expected.to run.with_params('Class["bar"]', {}).and_return(false) }
129+
it { is_expected.to run.with_params('Class[bar]', {}).and_return(false) }
130+
it { is_expected.to run.with_params(Puppet::Resource.new('class', 'test'), {}).and_return(true) }
131+
it { is_expected.to run.with_params(Puppet::Resource.new('Class["bar"]'), {}).and_return(false) }
132+
it { is_expected.to run.with_params(Puppet::Resource.new('Class[bar]'), {}).and_return(false) }
133+
end
134+
end

0 commit comments

Comments
 (0)