Skip to content

Commit f59fa31

Browse files
authored
Merge pull request #380 from puppetlabs/MODULES-11462-tags-not-being-honoured-with-the-newer-version-of-puppetlabs-firewall-module
(MODULES-11462): Implement finish method for class tag inheritance in Puppet::ResourceApi
2 parents 73d75d1 + a7809d0 commit f59fa31

File tree

2 files changed

+159
-0
lines changed

2 files changed

+159
-0
lines changed

lib/puppet/resource_api.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,27 @@ def initialize(attributes)
107107
super
108108
end
109109

110+
# Override finish method to ensure scope tags (like class names) are properly inherited
111+
# This is called after the resource is added to the catalog and containment is established
112+
def finish
113+
super if defined?(super)
114+
return unless @catalog
115+
116+
# Use pathbuilder to tag all containing classes
117+
# Pathbuilder returns the containment hierarchy; class names appear as plain strings
118+
# while other resources have the format "Type[title]"
119+
return unless respond_to?(:pathbuilder)
120+
121+
pathbuilder.each do |container|
122+
next unless container.is_a?(String)
123+
124+
# Classes don't contain '[' or ']' characters, resources do
125+
# Classes: "Test::Modules_11462", "Settings"
126+
# Resources: "Stage[main]", "Firewall[001 test rule]"
127+
tag(container) unless container.include?('[')
128+
end
129+
end
130+
110131
def name
111132
title
112133
end

spec/puppet/resource_api_spec.rb

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2365,6 +2365,144 @@ def set(_context, changes) end
23652365
described_class.register_transport(schema)
23662366
end
23672367
end
2368+
2369+
describe 'finish method for class tag inheritance', :agent_test do
2370+
let(:definition) do
2371+
{
2372+
name: 'tag_test',
2373+
desc: 'a test resource for tag inheritance',
2374+
attributes: {
2375+
name: {
2376+
type: 'String',
2377+
behaviour: :namevar,
2378+
desc: 'the title'
2379+
},
2380+
ensure: {
2381+
type: 'Enum[present, absent]',
2382+
desc: 'the ensure value'
2383+
}
2384+
}
2385+
}
2386+
end
2387+
let(:provider_class) do
2388+
Class.new do
2389+
def get(_context)
2390+
[]
2391+
end
2392+
2393+
def set(_context, _changes); end
2394+
end
2395+
end
2396+
let(:type) { Puppet::Type.type(:tag_test) }
2397+
let(:catalog) { Puppet::Resource::Catalog.new }
2398+
2399+
before do
2400+
described_class.register_type(definition)
2401+
stub_const('Puppet::Provider::TagTest', Module.new)
2402+
stub_const('Puppet::Provider::TagTest::TagTest', provider_class)
2403+
end
2404+
2405+
context 'when resource has pathbuilder with class names' do
2406+
let(:instance) { type.new(name: 'test_resource', ensure: 'present') }
2407+
2408+
before do
2409+
instance.catalog = catalog
2410+
allow(instance).to receive(:respond_to?).with(:pathbuilder).and_return(true)
2411+
allow(instance).to receive(:pathbuilder).and_return(['Stage[main]', 'Test::MyClass', 'Test::AnotherClass', 'TagTest[test_resource]'])
2412+
end
2413+
2414+
it 'tags the resource with class names from pathbuilder' do
2415+
instance.finish
2416+
expect(instance.tags.to_a).to include('test::myclass', 'test::anotherclass', 'test', 'anotherclass', 'myclass')
2417+
end
2418+
2419+
it 'does not tag resources that have brackets' do
2420+
instance.finish
2421+
expect(instance.tags.to_a).not_to include('stage[main]')
2422+
expect(instance.tags.to_a).not_to include('tagtest[test_resource]')
2423+
end
2424+
end
2425+
2426+
context 'when resource has pathbuilder with only resources (no classes)' do
2427+
let(:instance) { type.new(name: 'test_resource2', ensure: 'present') }
2428+
2429+
before do
2430+
instance.catalog = catalog
2431+
allow(instance).to receive(:respond_to?).with(:pathbuilder).and_return(true)
2432+
allow(instance).to receive(:pathbuilder).and_return(['Stage[main]', 'TagTest[test_resource2]'])
2433+
end
2434+
2435+
it 'does not tag any classes' do
2436+
initial_tags = instance.tags.to_a
2437+
instance.finish
2438+
# Should only have the default tags, no class tags added
2439+
expect(instance.tags.to_a - initial_tags).to be_empty
2440+
end
2441+
end
2442+
2443+
context 'when resource does not have pathbuilder' do
2444+
let(:instance) { type.new(name: 'test_resource3', ensure: 'present') }
2445+
2446+
before do
2447+
instance.catalog = catalog
2448+
allow(instance).to receive(:respond_to?).with(:pathbuilder).and_return(false)
2449+
end
2450+
2451+
it 'does not error' do
2452+
expect { instance.finish }.not_to raise_error
2453+
end
2454+
end
2455+
2456+
context 'when resource does not have a catalog' do
2457+
let(:instance) { type.new(name: 'test_resource4', ensure: 'present') }
2458+
2459+
before do
2460+
instance.catalog = nil
2461+
end
2462+
2463+
it 'returns early without processing' do
2464+
expect(instance).not_to receive(:pathbuilder)
2465+
instance.finish
2466+
end
2467+
end
2468+
2469+
context 'when pathbuilder returns mixed case class names' do
2470+
let(:instance) { type.new(name: 'test_resource5', ensure: 'present') }
2471+
2472+
before do
2473+
instance.catalog = catalog
2474+
allow(instance).to receive(:respond_to?).with(:pathbuilder).and_return(true)
2475+
allow(instance).to receive(:pathbuilder).and_return(['MyModule::MyClass', 'AnotherModule'])
2476+
end
2477+
2478+
it 'normalizes class names to lowercase when tagging' do
2479+
instance.finish
2480+
# Puppet's tag() method automatically lowercases
2481+
expect(instance.tags.to_a).to include('mymodule::myclass', 'anothermodule', 'mymodule', 'myclass')
2482+
end
2483+
end
2484+
2485+
context 'when pathbuilder returns nested classes' do
2486+
let(:instance) { type.new(name: 'test_resource6', ensure: 'present') }
2487+
2488+
before do
2489+
instance.catalog = catalog
2490+
allow(instance).to receive(:respond_to?).with(:pathbuilder).and_return(true)
2491+
allow(instance).to receive(:pathbuilder).and_return([
2492+
'Stage[main]',
2493+
'Profiles::Base',
2494+
'Profiles::Web',
2495+
'Profiles::Web::Apache',
2496+
'TagTest[test_resource6]'
2497+
])
2498+
end
2499+
2500+
it 'tags all classes in the containment hierarchy' do
2501+
instance.finish
2502+
expect(instance.tags.to_a).to include('profiles::base', 'profiles::web', 'profiles::web::apache', 'profiles', 'web', 'base', 'apache')
2503+
end
2504+
end
2505+
end
23682506
end
23692507

23702508
# rubocop:enable Lint/ConstantDefinitionInBlock

0 commit comments

Comments
 (0)