Skip to content

Commit a5bb802

Browse files
authored
Merge pull request #381 from puppetlabs/MODULES-11462-tags-not-being-honoured
(MODULES-11462): Implement finish method for class tag inheritance in Puppet::ResourceApi in 1.9.0 branch
2 parents 6f9f0f9 + 095363b commit a5bb802

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(attributes)
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
@@ -2330,4 +2330,142 @@ def set(_context, changes) end
23302330
described_class.register_transport(schema)
23312331
end
23322332
end
2333+
2334+
describe 'finish method for class tag inheritance', :agent_test do
2335+
let(:definition) do
2336+
{
2337+
name: 'tag_test',
2338+
desc: 'a test resource for tag inheritance',
2339+
attributes: {
2340+
name: {
2341+
type: 'String',
2342+
behaviour: :namevar,
2343+
desc: 'the title'
2344+
},
2345+
ensure: {
2346+
type: 'Enum[present, absent]',
2347+
desc: 'the ensure value'
2348+
}
2349+
}
2350+
}
2351+
end
2352+
let(:provider_class) do
2353+
Class.new do
2354+
def get(_context)
2355+
[]
2356+
end
2357+
2358+
def set(_context, _changes); end
2359+
end
2360+
end
2361+
let(:type) { Puppet::Type.type(:tag_test) }
2362+
let(:catalog) { Puppet::Resource::Catalog.new }
2363+
2364+
before do
2365+
described_class.register_type(definition)
2366+
stub_const('Puppet::Provider::TagTest', Module.new)
2367+
stub_const('Puppet::Provider::TagTest::TagTest', provider_class)
2368+
end
2369+
2370+
context 'when resource has pathbuilder with class names' do
2371+
let(:instance) { type.new(name: 'test_resource', ensure: 'present') }
2372+
2373+
before do
2374+
instance.catalog = catalog
2375+
allow(instance).to receive(:respond_to?).with(:pathbuilder).and_return(true)
2376+
allow(instance).to receive(:pathbuilder).and_return(['Stage[main]', 'Test::MyClass', 'Test::AnotherClass', 'TagTest[test_resource]'])
2377+
end
2378+
2379+
it 'tags the resource with class names from pathbuilder' do
2380+
instance.finish
2381+
expect(instance.tags.to_a).to include('test::myclass', 'test::anotherclass', 'test', 'anotherclass', 'myclass')
2382+
end
2383+
2384+
it 'does not tag resources that have brackets' do
2385+
instance.finish
2386+
expect(instance.tags.to_a).not_to include('stage[main]')
2387+
expect(instance.tags.to_a).not_to include('tagtest[test_resource]')
2388+
end
2389+
end
2390+
2391+
context 'when resource has pathbuilder with only resources (no classes)' do
2392+
let(:instance) { type.new(name: 'test_resource2', ensure: 'present') }
2393+
2394+
before do
2395+
instance.catalog = catalog
2396+
allow(instance).to receive(:respond_to?).with(:pathbuilder).and_return(true)
2397+
allow(instance).to receive(:pathbuilder).and_return(['Stage[main]', 'TagTest[test_resource2]'])
2398+
end
2399+
2400+
it 'does not tag any classes' do
2401+
initial_tags = instance.tags.to_a
2402+
instance.finish
2403+
# Should only have the default tags, no class tags added
2404+
expect(instance.tags.to_a - initial_tags).to be_empty
2405+
end
2406+
end
2407+
2408+
context 'when resource does not have pathbuilder' do
2409+
let(:instance) { type.new(name: 'test_resource3', ensure: 'present') }
2410+
2411+
before do
2412+
instance.catalog = catalog
2413+
allow(instance).to receive(:respond_to?).with(:pathbuilder).and_return(false)
2414+
end
2415+
2416+
it 'does not error' do
2417+
expect { instance.finish }.not_to raise_error
2418+
end
2419+
end
2420+
2421+
context 'when resource does not have a catalog' do
2422+
let(:instance) { type.new(name: 'test_resource4', ensure: 'present') }
2423+
2424+
before do
2425+
instance.catalog = nil
2426+
end
2427+
2428+
it 'returns early without processing' do
2429+
expect(instance).not_to receive(:pathbuilder)
2430+
instance.finish
2431+
end
2432+
end
2433+
2434+
context 'when pathbuilder returns mixed case class names' do
2435+
let(:instance) { type.new(name: 'test_resource5', ensure: 'present') }
2436+
2437+
before do
2438+
instance.catalog = catalog
2439+
allow(instance).to receive(:respond_to?).with(:pathbuilder).and_return(true)
2440+
allow(instance).to receive(:pathbuilder).and_return(['MyModule::MyClass', 'AnotherModule'])
2441+
end
2442+
2443+
it 'normalizes class names to lowercase when tagging' do
2444+
instance.finish
2445+
# Puppet's tag() method automatically lowercases
2446+
expect(instance.tags.to_a).to include('mymodule::myclass', 'anothermodule', 'mymodule', 'myclass')
2447+
end
2448+
end
2449+
2450+
context 'when pathbuilder returns nested classes' do
2451+
let(:instance) { type.new(name: 'test_resource6', ensure: 'present') }
2452+
2453+
before do
2454+
instance.catalog = catalog
2455+
allow(instance).to receive(:respond_to?).with(:pathbuilder).and_return(true)
2456+
allow(instance).to receive(:pathbuilder).and_return([
2457+
'Stage[main]',
2458+
'Profiles::Base',
2459+
'Profiles::Web',
2460+
'Profiles::Web::Apache',
2461+
'TagTest[test_resource6]'
2462+
])
2463+
end
2464+
2465+
it 'tags all classes in the containment hierarchy' do
2466+
instance.finish
2467+
expect(instance.tags.to_a).to include('profiles::base', 'profiles::web', 'profiles::web::apache', 'profiles', 'web', 'base', 'apache')
2468+
end
2469+
end
2470+
end
23332471
end

0 commit comments

Comments
 (0)