Skip to content

Commit da8b73e

Browse files
(PUP-11209) Puppet agent silently skips unknown resources.
Previously all unknown resources were converted into a component (Puppet::Type::Component) and silently skipped. This commit adds a new resource attribute (:kind) that specifies the resource kind, which is used to differentiate between built-in types and user defined types. When converting the catalog to a RAL catalog, this new attribute is used to to check whether a resource should use the resource type's :new method, or create a component, and in the case where the server considers a resource to be compilable, but the agent can't resolve it, then we raise an error
1 parent 7b084c8 commit da8b73e

File tree

12 files changed

+207
-13
lines changed

12 files changed

+207
-13
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
test_name "agent run should fail if it finds an unknown resource type" do
2+
tag 'audit:high',
3+
'audit:integration'
4+
5+
require 'puppet/acceptance/common_utils'
6+
7+
require 'puppet/acceptance/environment_utils'
8+
extend Puppet::Acceptance::EnvironmentUtils
9+
10+
require 'puppet/acceptance/temp_file_utils'
11+
extend Puppet::Acceptance::TempFileUtils
12+
13+
step "agent should fail when it can't find a resource" do
14+
vendor_modules_path = master.tmpdir('vendor_modules')
15+
tmp_environment = mk_tmp_environment_with_teardown(master, 'tmp')
16+
17+
site_pp_content = <<-SITEPP
18+
define foocreateresource($one) {
19+
$msg = 'hello'
20+
notify { $name: message => $msg }
21+
}
22+
class example($x) {
23+
if $x == undef or $x == [] or $x == '' {
24+
notice 'foo'
25+
return()
26+
}
27+
notice 'bar'
28+
}
29+
node default {
30+
class { example: x => [] }
31+
create_resources('foocreateresource', {'blah'=>{'one'=>'two'}})
32+
mycustomtype{'foobar':}
33+
}
34+
SITEPP
35+
manifests_path = "/tmp/#{tmp_environment}/manifests"
36+
on(master, "mkdir -p '#{manifests_path}'")
37+
create_remote_file(master, "#{manifests_path}/site.pp", site_pp_content)
38+
39+
custom_type_content = <<-CUSTOMTYPE
40+
Puppet::Type.newtype(:mycustomtype) do
41+
@doc = "Create a new mycustomtype thing."
42+
43+
newparam(:name, :namevar => true) do
44+
desc "Name of mycustomtype instance"
45+
end
46+
47+
def refresh
48+
end
49+
end
50+
CUSTOMTYPE
51+
type_path = "#{vendor_modules_path}/foo/lib/puppet/type"
52+
on(master, "mkdir -p '#{type_path}'")
53+
create_remote_file(master, "#{type_path}/mycustomtype.rb", custom_type_content)
54+
55+
on(master, "chmod -R 750 '#{vendor_modules_path}' '/tmp/#{tmp_environment}'")
56+
on(master, "chown -R #{master.puppet['user']}:#{master.puppet['group']} '#{vendor_modules_path}' '/tmp/#{tmp_environment}'")
57+
58+
master_opts = {
59+
'main' => {
60+
'environment' => tmp_environment,
61+
'vendormoduledir' => vendor_modules_path
62+
}
63+
}
64+
65+
with_puppet_running_on(master, master_opts) do
66+
agents.each do |agent|
67+
teardown do
68+
agent.rm_rf(vendor_modules_path)
69+
end
70+
71+
on(agent, puppet('agent', '-t', '--environment', tmp_environment), acceptable_exit_codes: [1]) do |result|
72+
assert_match(/Error: Failed to apply catalog: Resource type 'Mycustomtype' was not found/, result.stderr)
73+
end
74+
end
75+
end
76+
end
77+
end

api/schemas/catalog.json

+3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
"line": {
4646
"type": "integer"
4747
},
48+
"kind": {
49+
"type": "string"
50+
},
4851
"file": {
4952
"type": "string"
5053
},

benchmarks/serialization/catalog.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
"version": 1492108311,
119119
"code_id": null,
120120
"catalog_uuid": "c85cdf7e-f56d-4fc7-b513-3a00532cee91",
121-
"catalog_format": 1,
121+
"catalog_format": 2,
122122
"environment": "production",
123123
"resources": [
124124
{

lib/puppet/parser/resource.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class Puppet::Parser::Resource < Puppet::Resource
1313

1414
attr_accessor :source, :scope, :collector_id
1515
attr_accessor :virtual, :override, :translated, :catalog, :evaluated
16-
attr_accessor :file, :line
16+
attr_accessor :file, :line, :kind
1717

1818
attr_reader :exported, :parameters
1919

lib/puppet/pops/evaluator/runtime3_resource_support.rb

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def self.create_resources(file, line, scope, virtual, exported, type_name, resou
4040
:parameters => evaluated_parameters,
4141
:file => file,
4242
:line => line,
43+
:kind => Puppet::Resource.to_kind(resolved_type),
4344
:exported => exported,
4445
:virtual => virtual,
4546
# WTF is this? Which source is this? The file? The name of the context ?

lib/puppet/resource.rb

+38-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class Puppet::Resource
1111
include Puppet::Util::PsychSupport
1212

1313
include Enumerable
14-
attr_accessor :file, :line, :catalog, :exported, :virtual, :strict
14+
attr_accessor :file, :line, :catalog, :exported, :virtual, :strict, :kind
1515
attr_reader :type, :title, :parameters
1616

1717
# @!attribute [rw] sensitive_parameters
@@ -29,11 +29,16 @@ class Puppet::Resource
2929
EMPTY_ARRAY = [].freeze
3030
EMPTY_HASH = {}.freeze
3131

32-
ATTRIBUTES = [:file, :line, :exported].freeze
32+
ATTRIBUTES = [:file, :line, :exported, :kind].freeze
3333
TYPE_CLASS = 'Class'.freeze
3434
TYPE_NODE = 'Node'.freeze
3535
TYPE_SITE = 'Site'.freeze
3636

37+
CLASS_STRING = 'class'.freeze
38+
DEFINED_TYPE_STRING = 'defined_type'.freeze
39+
COMPILABLE_TYPE_STRING = 'compilable_type'.freeze
40+
UNKNOWN_TYPE_STRING = 'unknown'.freeze
41+
3742
PCORE_TYPE_KEY = '__ptype'.freeze
3843
VALUE_KEY = 'value'.freeze
3944

@@ -194,6 +199,18 @@ def builtin_type?
194199
resource_type.is_a?(Puppet::CompilableResourceType)
195200
end
196201

202+
def self.to_kind(resource_type)
203+
if resource_type == CLASS_STRING
204+
CLASS_STRING
205+
elsif resource_type.is_a?(Puppet::Resource::Type) && resource_type.type == :definition
206+
DEFINED_TYPE_STRING
207+
elsif resource_type.is_a?(Puppet::CompilableResourceType)
208+
COMPILABLE_TYPE_STRING
209+
else
210+
UNKNOWN_TYPE_STRING
211+
end
212+
end
213+
197214
# Iterate over each param/value pair, as required for Enumerable.
198215
def each
199216
parameters.each { |p,v| yield p, v }
@@ -248,6 +265,7 @@ def initialize(type, title = nil, attributes = EMPTY_HASH)
248265
src = type
249266
self.file = src.file
250267
self.line = src.line
268+
self.kind = src.kind
251269
self.exported = src.exported
252270
self.virtual = src.virtual
253271
self.set_tags(src)
@@ -310,6 +328,7 @@ def initialize(type, title = nil, attributes = EMPTY_HASH)
310328

311329
rt = resource_type
312330

331+
self.kind = self.class.to_kind(rt) unless kind
313332
if strict? && rt.nil?
314333
if self.class?
315334
raise ArgumentError, _("Could not find declared class %{title}") % { title: title }
@@ -493,10 +512,24 @@ def to_ref
493512
ref
494513
end
495514

496-
# Convert our resource to a RAL resource instance. Creates component
497-
# instances for resource types that don't exist.
515+
# Convert our resource to a RAL resource instance. Creates component
516+
# instances for resource types that are not of a compilable_type kind. In case
517+
# the resource doesn’t exist and it’s compilable_type kind, raise an error.
518+
# There are certain cases where a resource won't be in a catalog, such as
519+
# when we create a resource directly by using Puppet::Resource.new(...), so we
520+
# must check its kind before deciding whether the catalog format is of an older
521+
# version or not.
498522
def to_ral
499-
typeklass = Puppet::Type.type(self.type) || Puppet::Type.type(:component)
523+
if self.kind == COMPILABLE_TYPE_STRING
524+
typeklass = Puppet::Type.type(self.type)
525+
elsif self.catalog && self.catalog.catalog_format >= 2
526+
typeklass = Puppet::Type.type(:component)
527+
else
528+
typeklass = Puppet::Type.type(self.type) || Puppet::Type.type(:component)
529+
end
530+
531+
raise(Puppet::Error, "Resource type '#{self.type}' was not found") unless typeklass
532+
500533
typeklass.new(self)
501534
end
502535

lib/puppet/resource/catalog.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ def initialize(name = nil, environment = Puppet::Node::Environment::NONE, code_i
315315
super()
316316
@name = name
317317
@catalog_uuid = SecureRandom.uuid
318-
@catalog_format = 1
318+
@catalog_format = 2
319319
@metadata = {}
320320
@recursive_metadata = {}
321321
@classes = []

spec/fixtures/integration/application/agent/cached_deferred_catalog.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"version": 1607629733,
77
"code_id": null,
88
"catalog_uuid": "afc8472a-306b-4b24-b060-e956dffb79b8",
9-
"catalog_format": 1,
9+
"catalog_format": 2,
1010
"environment": "production",
1111
"resources": [
1212
{
@@ -50,6 +50,7 @@
5050
],
5151
"file": "",
5252
"line": 1,
53+
"kind": "compilable_type",
5354
"exported": false,
5455
"parameters": {
5556
"message": {

spec/integration/parser/pcore_resource_spec.rb

+10
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,16 @@ module Puppet
136136
expect(catalog.resource(:cap, "c")['message']).to eq('c works')
137137
end
138138

139+
it 'considers Pcore types to be builtin ' do
140+
genface.types
141+
catalog = compile_to_catalog(<<-MANIFEST)
142+
test1 { 'a':
143+
message => 'a works'
144+
}
145+
MANIFEST
146+
expect(catalog.resource(:test1, "a").kind).to eq('compilable_type')
147+
end
148+
139149
it 'the validity of attribute names are checked' do
140150
genface.types
141151
expect do

spec/unit/configurer_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,7 @@ def expects_neither_new_or_cached_catalog
742742
expect(configurer.run).to be_nil
743743
end
744744

745-
it "should proceed with the cached catalog if its environment matchs the local environment" do
745+
it "should proceed with the cached catalog if its environment matches the local environment" do
746746
expects_cached_catalog_only(catalog)
747747

748748
expect(configurer.run).to eq(0)

spec/unit/resource/catalog_spec.rb

+14-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104

105105
it "should include the current catalog_format" do
106106
catalog = Puppet::Resource::Catalog.new("host")
107-
expect(catalog.catalog_format).to eq(1)
107+
expect(catalog.catalog_format).to eq(2)
108108
end
109109

110110
describe "when compiling" do
@@ -178,6 +178,7 @@
178178
@original.add_edge(@middle, @bottom)
179179
@original.add_edge(@bottom, @bottomobject)
180180

181+
@original.catalog_format = 1
181182
@catalog = @original.to_ral
182183
end
183184

@@ -190,6 +191,18 @@
190191
end
191192
end
192193

194+
it "should raise if an unknown resource is being converted" do
195+
@new_res = Puppet::Resource.new "Unknown", "type", :kind => 'compilable_type'
196+
@resource_array = [@new_res]
197+
198+
@original.add_resource(*@resource_array)
199+
@original.add_edge(@bottomobject, @new_res)
200+
201+
@original.catalog_format = 2
202+
203+
expect { @original.to_ral }.to raise_error(Puppet::Error, "Resource type 'Unknown' was not found")
204+
end
205+
193206
it "should copy the tag list to the new catalog" do
194207
expect(@catalog.tags.sort).to eq(@original.tags.sort)
195208
end

spec/unit/resource_spec.rb

+58-2
Original file line numberDiff line numberDiff line change
@@ -694,19 +694,68 @@ def inject_and_set_defaults(resource, scope)
694694
it "should use the resource type's :new method to create the resource if the resource is of a builtin type" do
695695
resource = Puppet::Resource.new("file", basepath+"/my/file")
696696
result = resource.to_ral
697+
697698
expect(result).to be_instance_of(Puppet::Type.type(:file))
698699
expect(result[:path]).to eq(basepath+"/my/file")
699700
end
700701

701-
it "should convert to a component instance if the resource type is not of a builtin type" do
702+
it "should convert to a component instance if the resource is not a compilable_type" do
702703
resource = Puppet::Resource.new("foobar", "somename")
703704
result = resource.to_ral
704705

705706
expect(result).to be_instance_of(Puppet::Type.type(:component))
706707
expect(result.title).to eq("Foobar[somename]")
707708
end
708-
end
709709

710+
it "should convert to a component instance if the resource is a class" do
711+
resource = Puppet::Resource.new("Class", "somename")
712+
result = resource.to_ral
713+
714+
expect(result).to be_instance_of(Puppet::Type.type(:component))
715+
expect(result.title).to eq("Class[Somename]")
716+
end
717+
718+
it "should convert to component when the resource is a defined_type" do
719+
resource = Puppet::Resource.new("Unknown", "type", :kind => 'defined_type')
720+
721+
result = resource.to_ral
722+
expect(result).to be_instance_of(Puppet::Type.type(:component))
723+
end
724+
725+
it "should raise if a resource type is a compilable_type and it wasn't found" do
726+
resource = Puppet::Resource.new("Unknown", "type", :kind => 'compilable_type')
727+
728+
expect { resource.to_ral }.to raise_error(Puppet::Error, "Resource type 'Unknown' was not found")
729+
end
730+
731+
it "should use the old behaviour when the catalog_format is equal to 1" do
732+
resource = Puppet::Resource.new("Unknown", "type")
733+
catalog = Puppet::Resource::Catalog.new("mynode")
734+
735+
resource.catalog = catalog
736+
resource.catalog.catalog_format = 1
737+
738+
result = resource.to_ral
739+
expect(result).to be_instance_of(Puppet::Type.type(:component))
740+
end
741+
742+
it "should use the new behaviour and fail when the catalog_format is greater than 1" do
743+
resource = Puppet::Resource.new("Unknown", "type", :kind => 'compilable_type')
744+
catalog = Puppet::Resource::Catalog.new("mynode")
745+
746+
resource.catalog = catalog
747+
resource.catalog.catalog_format = 2
748+
749+
expect { resource.to_ral }.to raise_error(Puppet::Error, "Resource type 'Unknown' was not found")
750+
end
751+
752+
it "should use the resource type when the resource doesn't respond to kind and the resource type can be found" do
753+
resource = Puppet::Resource.new("file", basepath+"/my/file")
754+
755+
result = resource.to_ral
756+
expect(result).to be_instance_of(Puppet::Type.type(:file))
757+
end
758+
end
710759
describe "when converting to puppet code" do
711760
before do
712761
@resource = Puppet::Resource.new("one::two", "/my/file",
@@ -822,6 +871,13 @@ def inject_and_set_defaults(resource, scope)
822871
expect(Puppet::Resource.from_data_hash(JSON.parse(resource.to_json)).line).to eq(50)
823872
end
824873

874+
it "should include the kind if one is set" do
875+
resource = Puppet::Resource.new("File", "/foo")
876+
resource.kind = 'im_a_file'
877+
878+
expect(Puppet::Resource.from_data_hash(JSON.parse(resource.to_json)).kind).to eq('im_a_file')
879+
end
880+
825881
it "should include the 'exported' value if one is set" do
826882
resource = Puppet::Resource.new("File", "/foo")
827883
resource.exported = true

0 commit comments

Comments
 (0)