Skip to content

Commit 6e7835b

Browse files
committed
(PUP-4242) Add check for manifests declaring things in the wrong namespace.
1 parent b9d87f5 commit 6e7835b

File tree

4 files changed

+152
-6
lines changed

4 files changed

+152
-6
lines changed

lib/puppet/pops/issues.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,10 @@ def self.hard_issue(issue_code, *args, &block)
486486
_("Unacceptable name. The name '%{name}' is unacceptable as the name of %{value}") % { name: name, value: label.a_an(semantic) }
487487
end
488488

489+
ILLEGAL_DEFINITION_LOCATION = issue :ILLEGAL_DEFINITION_LOCATION, :name, :file do
490+
_("Unacceptable location. The name '%{name}' is unacceptable in file '%{file}'") % { name: name, file: file }
491+
end
492+
489493
CAPTURES_REST_NOT_LAST = hard_issue :CAPTURES_REST_NOT_LAST, :param_name do
490494
_("Parameter $%{param} is not last, and has 'captures rest'") % { param: param_name }
491495
end

lib/puppet/pops/validation/checker4_0.rb

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def initialize(diagnostics_producer)
3535

3636
@check_visitor = self.class.check_visitor
3737
@acceptor = diagnostics_producer
38+
@file_to_namespace = {}
3839

3940
# Use null migration checker unless given in context
4041
@migration_checker = (Puppet.lookup(:migration_checker) { Migration::MigrationChecker.new() })
@@ -395,6 +396,8 @@ def check_NamedDefinition(o)
395396
if o.name !~ Patterns::CLASSREF_DECL
396397
acceptor.accept(Issues::ILLEGAL_DEFINITION_NAME, o, {:name=>o.name})
397398
end
399+
400+
internal_check_file_namespace(o, o.name, o.locator.file)
398401
internal_check_reserved_type_name(o, o.name)
399402
internal_check_future_reserved_word(o, o.name)
400403
end
@@ -530,6 +533,75 @@ def internal_check_future_reserved_word(o, name)
530533
end
531534
end
532535

536+
def internal_check_file_namespace(o, name, file)
537+
return if file.nil?
538+
539+
lc_file = file.downcase
540+
541+
file_namespace = @file_to_namespace[lc_file]
542+
if file_namespace.nil?
543+
return if @file_to_namespace.key?(lc_file)
544+
file_namespace = @file_to_namespace[lc_file] = namespace_for_file(lc_file)
545+
return if file_namespace.nil?
546+
end
547+
548+
if !name.downcase.start_with?(file_namespace)
549+
acceptor.accept(Issues::ILLEGAL_DEFINITION_LOCATION, o, {:name => name, :file => file})
550+
end
551+
end
552+
553+
NEVER_MATCH = '\b'.freeze
554+
555+
def namespace_for_file(file)
556+
path = Pathname.new(file)
557+
558+
return nil if path.extname != ".pp"
559+
560+
return nil if initial_manifest?(path)
561+
562+
path = path.each_filename.to_a
563+
564+
# Example definition dir: manifests in this path:
565+
# <modules dir>/<module name>/manifests/<module subdir>/<classfile>.pp
566+
definition_dir_index = find_module_definition_dir(path)
567+
568+
# How can we get this result?
569+
# If it is not an initial manifest, it must come from a module,
570+
# and from the manifests dir there. This may never get used...
571+
return NEVER_MATCH if definition_dir_index.nil? || definition_dir_index == 0
572+
573+
before_definition_dir = definition_dir_index - 1
574+
after_definition_dir = definition_dir_index + 1
575+
names = path[after_definition_dir .. -2] # Directories inside module
576+
names.unshift(path[before_definition_dir]) # Name of the module itself
577+
# Do not include name of module init file at top level of module
578+
filename = path[-1]
579+
if !(path.length == (after_definition_dir+1) && filename == 'init.pp')
580+
names.push(filename[0 .. -4]) # Remove .pp from filename
581+
end
582+
583+
names.join("::").freeze
584+
end
585+
586+
def initial_manifest?(path)
587+
manifest_setting = Puppet[:manifest]
588+
589+
# Does this ever happen outside of tests?
590+
if manifest_setting.nil?
591+
return path.basename.to_s == 'site.pp'
592+
end
593+
594+
full_path = path.expand_path.to_s
595+
full_path == manifest_setting || full_path.start_with?(manifest_setting)
596+
end
597+
598+
# Returns module root directory index in path for files under 'manifests', 'functions' and 'types'
599+
def find_module_definition_dir(path)
600+
reverse_index = path.reverse_each.find_index do |dir|
601+
dir == 'manifests' || dir == 'functions' || dir == 'types'
602+
end
603+
return reverse_index.nil? ? nil : (path.length - 1) - reverse_index
604+
end
533605

534606
RESERVED_PARAMETERS = {
535607
'name' => true,

lib/puppet/pops/validation/validator_factory_4_0.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,16 @@ def severity_producer
2727
# Configure each issue that should **not** be an error
2828
#
2929
# Validate as per the current runtime configuration
30-
p[Issues::RT_NO_STORECONFIGS_EXPORT] = Puppet[:storeconfigs] ? :ignore : :warning
31-
p[Issues::RT_NO_STORECONFIGS] = Puppet[:storeconfigs] ? :ignore : :warning
30+
p[Issues::RT_NO_STORECONFIGS_EXPORT] = Puppet[:storeconfigs] ? :ignore : :warning
31+
p[Issues::RT_NO_STORECONFIGS] = Puppet[:storeconfigs] ? :ignore : :warning
3232

3333
p[Issues::FUTURE_RESERVED_WORD] = :deprecation
3434

3535
p[Issues::DUPLICATE_KEY] = Puppet[:strict] == :off ? :ignore : Puppet[:strict]
3636
p[Issues::NAME_WITH_HYPHEN] = :error
3737
p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :ignore
38-
p[Issues::CLASS_NOT_VIRTUALIZABLE] = Puppet[:strict] == :off ? :warning : Puppet[:strict]
38+
p[Issues::CLASS_NOT_VIRTUALIZABLE] = Puppet[:strict] == :off ? :warning : Puppet[:strict]
39+
p[Issues::ILLEGAL_DEFINITION_LOCATION] = Puppet[:strict] == :off ? :ignore : Puppet[:strict]
3940
p
4041
end
4142
end

spec/unit/pops/validator/validator_spec.rb

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,47 @@ def validate(factory)
3434
expect(validate(parse('function ::aaa() {}'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_NAME)
3535
end
3636

37+
it 'should raise error for illegal class locations' do
38+
expect(validate(parse('function aaa::ccc() {}', 'aaa/manifests/bbb.pp'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
39+
expect(validate(parse('class bbb() {}', 'aaa/manifests/init.pp'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
40+
expect(validate(parse('define aaa::bbb::ccc::eee() {}', 'aaa/manifests/bbb/ddd.pp'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
41+
end
42+
43+
it 'should not raise error for legal definition locations' do
44+
expect(validate(parse('function aaa::bbb() {}', 'aaa/manifests/bbb.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
45+
expect(validate(parse('define bbb() {}', 'aaa/manifests/site.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
46+
expect(validate(parse('class aaa() {}', 'aaa/manifests/init.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
47+
expect(validate(parse('class aaa() {}', 'manifests/site.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
48+
expect(validate(parse('function aaa::bbB::ccc() {}', 'aaa/manifests/bBb.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
49+
expect(validate(parse('function aaa::bbb::ccc() {}', 'aaa/manifests/bbb/CCC.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
50+
end
51+
52+
it 'should not raise error for class locations when not parsing a file' do
53+
#nil file means eval or some other way to get puppet language source code into the catalog
54+
expect(validate(parse('function aaa::ccc() {}', nil))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
55+
end
56+
57+
it 'should not raise error for definitions inside initial --manifest' do
58+
Puppet[:manifest] = 'a/manifest/file.pp'
59+
expect(validate(parse('class aaa() {}', 'a/manifest/file.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
60+
end
61+
62+
it 'should not raise error for definitions inside initial --manifest' do
63+
Puppet[:manifest] = 'a/manifest/dir'
64+
expect(validate(parse('class aaa() {}', 'a/manifest/dir/file1.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
65+
expect(validate(parse('class bbb::ccc::ddd() {}', 'a/manifest/dir/and/more/stuff.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
66+
end
67+
68+
it 'should raise error for definitions not inside initial --manifest' do
69+
Puppet[:manifest] = 'a/manifest/somewhere/else'
70+
expect(validate(parse('class aaa() {}', 'a/manifest/dir/file1.pp'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
71+
end
72+
73+
it 'should raise error if the manifest file does not come from a well formed module path' do
74+
skip("This test path won't work on windows.") if Puppet::Util::Platform.windows?
75+
expect(validate(parse('class aaa() {}', '/manifest/dir/file1.pp'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
76+
end
77+
3778
it 'should raise error for illegal type names' do
3879
expect(validate(parse('type ::Aaa = Any'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_NAME)
3980
end
@@ -56,6 +97,13 @@ def validate(factory)
5697
expect(acceptor.error_count).to eql(0)
5798
expect(acceptor).to have_issue(Puppet::Pops::Issues::DUPLICATE_KEY)
5899
end
100+
101+
it 'produces a warning for illegal function locations' do
102+
acceptor = validate(parse('function aaa::ccc() {}', 'aaa/manifests/bbb.pp'))
103+
expect(acceptor.warning_count).to eql(1)
104+
expect(acceptor.error_count).to eql(0)
105+
expect(acceptor).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
106+
end
59107
end
60108

61109
context 'with --strict set to warning' do
@@ -80,6 +128,13 @@ def validate(factory)
80128
expect(acceptor.error_count).to eql(0)
81129
expect(acceptor).to have_issue(Puppet::Pops::Issues::CLASS_NOT_VIRTUALIZABLE)
82130
end
131+
132+
it 'produces a warning for illegal function locations' do
133+
acceptor = validate(parse('function aaa::ccc() {}', 'aaa/manifests/bbb.pp'))
134+
expect(acceptor.warning_count).to eql(1)
135+
expect(acceptor.error_count).to eql(0)
136+
expect(acceptor).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
137+
end
83138
end
84139

85140
context 'with --strict set to error' do
@@ -111,6 +166,13 @@ def validate(factory)
111166
expect(acceptor.error_count).to eql(1)
112167
expect(acceptor).to have_issue(Puppet::Pops::Issues::CLASS_NOT_VIRTUALIZABLE)
113168
end
169+
170+
it 'produces an error for illegal function locations' do
171+
acceptor = validate(parse('function aaa::ccc() {}', 'aaa/manifests/bbb.pp'))
172+
expect(acceptor.warning_count).to eql(0)
173+
expect(acceptor.error_count).to eql(1)
174+
expect(acceptor).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
175+
end
114176
end
115177

116178
context 'with --strict set to off' do
@@ -121,6 +183,13 @@ def validate(factory)
121183
expect(acceptor.error_count).to eql(0)
122184
expect(acceptor).to_not have_issue(Puppet::Pops::Issues::DUPLICATE_KEY)
123185
end
186+
187+
it 'does not produce an error or warning for illegal function locations' do
188+
acceptor = validate(parse('function aaa::ccc() {}', 'aaa/manifests/bbb.pp'))
189+
expect(acceptor.warning_count).to eql(0)
190+
expect(acceptor.error_count).to eql(0)
191+
expect(acceptor).to_not have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION)
192+
end
124193
end
125194

126195
context 'irrespective of --strict' do
@@ -145,7 +214,7 @@ def validate(factory)
145214
expect(acceptor).to have_issue(Puppet::Pops::Issues::CLASS_NOT_VIRTUALIZABLE)
146215
end
147216

148-
it 'produces a warning for exported class resource' do
217+
it 'produces a warning for exported class resource' do
149218
acceptor = validate(parse('@@class { test: }'))
150219
expect(acceptor.warning_count).to eql(1)
151220
expect(acceptor.error_count).to eql(0)
@@ -662,7 +731,7 @@ def issue(at_top)
662731
end
663732
end
664733

665-
def parse(source)
666-
Puppet::Pops::Parser::Parser.new.parse_string(source)
734+
def parse(source, path=nil)
735+
Puppet::Pops::Parser::Parser.new.parse_string(source, path)
667736
end
668737
end

0 commit comments

Comments
 (0)