# Table of Contents 1. [About](#org69c4ee5) 2. [Installation methods](#org0de282f) 1. [Via deb/rpm](#org99f1862) 2. [MaOS binaries](#org47f1002) 3. [Via cargo](#org1d0629f) 4. [Guix manifest](#org422efc7) 3. [Usage](#org03891a1) 1. [Correctness of YAML files](#org5e1b701) 2. [Validity of Hiera YAML files](#orge989d6f) 3. [Linter of Puppet manifest files](#org533deba) 4. [Pretty printing manifest file](#orgbb610ea) 5. [Config file generator](#orgd106844) 6. [Hiera explorer](#org824a981) 7. [\*.pp AST dumper](#orgb8ef884) 4. [Available lints for \*.pp](#orgb030ef1) 1. [ArgumentLooksSensitive](#org18746a9) 2. [ArgumentTyped](#org49fe89c) 3. [ConstantExpressionInCondition](#orgc0f08a1) 4. [DefaultCaseIsNotLast](#org26462c5) 5. [DoNotUseUnless](#org649528e) 6. [DoubleNegation](#org0b09277) 7. [EmptyCasesList](#org78da798) 8. [EnsureAttributeIsNotTheFirst](#org1dc599c) 9. [ErbReferencesToUnknownVariable](#org6390d4c) 10. [ExecAttributes](#orgcd3755f) 11. [ExpressionInSingleQuotes](#orgdea08c8) 12. [FileModeAttributeIsString](#org1de4f41) 13. [InvalidResourceCollectionInvocation](#org99aa6c0) 14. [InvalidResourceSetInvocation](#org9baa7c1) 15. [InvalidStringEscape](#org31c4451) 16. [InvalidVariableAssignment](#org5da3c5d) 17. [LowerCaseArgumentName](#orgc014a22) 18. [LowerCaseVariable](#org7861a05) 19. [MultipleDefaultCase](#orgc437d51) 20. [MultipleResourcesWithoutDefault](#org844dda2) 21. [NegationOfEquation](#org50aeb09) 22. [NoDefaultCase](#org5750d1a) 23. [OptionalArgumentsGoesFirst](#orgf25c7e3) 24. [PerExpressionResourceDefaults](#orgff01509) 25. [ReadableArgumentsName](#orgd55110c) 26. [ReferenceToUndefinedValue](#org8cba3d6) 27. [RelationToTheLeft](#orgd97bab9) 28. [SelectorInAttributeValue](#orga58d957) 29. [SensitiveArgumentWithDefault](#org52367d2) 30. [StatementWithNoEffect](#orgde1e58a) 31. [UnconditionalExec](#orgf46ee37) 32. [UniqueArgumentsNames](#orgaaa65b7) 33. [UniqueAttributeName](#org83855a4) 34. [UnusedVariables](#org6c6fa54) 35. [UpperCaseName](#org687dd29) 36. [UselessDoubleQuotes](#orgb5a08c4) 37. [UselessParens](#orgd85a9a5) 5. [Linter for YAML files](#orgba846f3) 6. [Linter for Hiera YAML files](#org53c14e2) 1. [Reference to a module which has syntax errors](#org5e31778) 2. [Reference to class which is not found in modules/](#orgc745c51) 3. [Reference in undefined class argument](#orgc38e4a2) 4. [Single column in the name of key of root map](#org22bf944) # About Shadowplay is a utility for checking puppet syntax, a puppet manifest linter, a pretty printer, and a utility for exploring the Hiera. ![img](./doc/screenshot-emacs.png "Flycheck plugin for Emacs") # Installation methods ## Via deb/rpm Latest releases can be downloaded here: ## MaOS binaries Lastest binaries for MacOS can be downloaded here: ## Via cargo cargo install shadowplay ## Guix manifest Guix manifest is not merged into main repository yet. One can use etc/guix.scm from Shadowplay repo. All missing dependencies are also included into manifest file. # Usage ## Correctness of YAML files shadowplay check yaml hieradata/default.yaml [hieradata/another.yaml] ... In addition to the correctness of the syntax, the uniqueness of the keys in maps will be checked, as well as the correctness of the links (anchors). ## Validity of Hiera YAML files shadowplay check hiera hieradata/default.yaml ... For the specified files, YAML correctness will be checked, as well as the correctness of references to Puppet classes and class arguments. For example, there will be an error generated if an unknown class argument is used. As a side effect, it also checks the correctness of syntax of pappet manifests referenced by values ​​in Hiera. ## Linter of Puppet manifest files shadowplay --repo-path ./ check pp modules/hammer/manifests/config.pp ... The specified files will be processed by the parser, then linter checks will be applied to the resulting AST (if parsing is successful). ## Pretty printing manifest file shadowplay pretty-print-pp < /path/to/file.pp ## Config file generator Use may want to disable some lints or customize it. She can generate default config and edit it later with the command: shadowplay generate-config >/etc/shadowplay.yaml ## Hiera explorer Hiera is hierarchy of yaml files. In huge configurations it may be difficult to determine value of specific key for some host. Shadowplay provides easy solution. shadowplay get host123 sshd::install::version Command prints as much information as possible: Value: "present" Found in "./hieradata/default_CentOS7.yaml" at lines 63:63 Value lookup path was: network/host123.yaml -> host123.yaml -> host.yaml -> default_CentOS7.yaml =================================== Git information: deadbeef1234 (Evgenii Lepikhin 2022-03-29 15:06:51 +0300 63) sshd::install::version: 'present' ## \*.pp AST dumper shadowplay dump modules/sshd/manifests/install.pp Outputs AST in JSON format. Mainly for internal purposes. # Available lints for \*.pp ## ArgumentLooksSensitive Warns if argument name looks like sensitive, but argument is not typed with type Sensitive Bad: class some::class ( $secret_token, ) { } Good: class some::class ( Sensitive $secret_token, ) { } ## ArgumentTyped Warns if argument is not typed Bad: class some::class ( $config_path, ) { } Good: class some::class ( Stdlib::Absolutepath $config_path, ) { } ## ConstantExpressionInCondition Warns if constant expression is used in condition Bad: if 1 == 2 - 1 { notify('1=2-1') } Such type of conditions always evaluated into constant false or true, thus can be safely removed. Good: notify('1=2-1') ## DefaultCaseIsNotLast Warns if 'default' case is not the last Bad: case $value { 'a': { } default: { } 'b': { } } Good: case $value { 'a': { } 'b': { } default: { } } ## DoNotUseUnless Warns if 'unless' conditional statement is used Bad: unless $value { } Good: if !$value { } ## DoubleNegation Warns if double negation is used Bad: if !(!$value) { } if !($value != 1) { } Good: if $value { } if $value == 1 { } ## EmptyCasesList Warns if case { … } has no cases Bad: case $value { } ## EnsureAttributeIsNotTheFirst Warns if 'ensure' argument of resource is not the first Bad: file { '/etc/passwd': user => root, ensure => file, } Good: file { '/etc/passwd': ensure => file, user => root, } ## ErbReferencesToUnknownVariable Checks ERB templates specified in template() for undefined variables Bad: class some::class () { # here template_file.erb contains: <% @some_undefined_variable %> $value = template('some/template_file.erb') } ## ExecAttributes Checks exec { …} arguments Bad: # implicit 'command' attribute exec { 'echo Hello' : } exec { unknown_attribute => 1, } # invalid provider exec { provider => 'unknown provider value' } # 'path' is not set, 'provider' is not 'shell', thus 'command' attribute of exec {} must start with absolute path exec { command => 'echo Hello' } ## ExpressionInSingleQuotes Warns if interpolated expression found single-qouted string Bad: $value = 'Hello $world' $value = '2 + 2 = ${2+2}' ## FileModeAttributeIsString Warns if argument 'mode' of 'file' resource is not in 4-digit string form Bad: file { '/some/file': mode => '644', } file { '/some/file': mode => 644, } Good: file { '/some/file': mode => '0644', } ## InvalidResourceCollectionInvocation Checks if existing resource set is used and all arguments are known in it's class Bad: # relation to unknown resource Class['unknown_class'] -> Class['known_class'] ## InvalidResourceSetInvocation Checks if existing resource is used and all arguments are known in it's class Bad: class class1 ( $known_arg, ) { } class class2 { # Call to unknown class class { 'unknown_class': } # Call to known class with invalid argument class { 'class1': unknown_arg => 1 } # Call to known class with invalid argument class1 { 'title': unknown_arg => 1, } # Call to internal resource with invalid argument file { '/some/file': uknown_arg => 1, } } ## InvalidStringEscape Checks if only allowed characters are escaped in strings Bad: $value = '\s*\.' $value = "\s*\." Good: $value = '\\s*\\.' $value = "\\s*\\." ## InvalidVariableAssignment Warns if left part of assignment is not a variable or array of variables Bad: lookup('some::value') = 1 ## LowerCaseArgumentName Warns if argument name is not lowercase, as suggested by Puppet's style guide Bad: class some::class ( $ArgumentInCamelCase ) {} ## LowerCaseVariable Warns if variable name is not lowercase Bad: class some::class () { $VariableIsNOTInLowercase = 1 ## MultipleDefaultCase Warns if case statement has multiple 'default' cases Bad: case $val { 1: {} default: {} default: {} } ## MultipleResourcesWithoutDefault Warns if resource set contains multiple resources and no defaults specified Bad: file { '/etc/passwd': ensure => file, user => root, '/etc/group': ensure => file, user => root, group => wheel, } Good: file { default: ensure => file, user => root, '/etc/passwd': '/etc/group': group => wheel, } ## NegationOfEquation Warns on negation of equation Bad: if !($a == 1) { } if !($a =~ /./) { } Good: if $a != 1 { } if $a !~ /./ { } ## NoDefaultCase Warns if case statement has no default case Bad: case $val { 1, 2: { } 3: { } } Good: case $val { 1, 2: { } 3: { } default: { } } ## OptionalArgumentsGoesFirst Warns if optional argument specified before required class some::class ( $optional_arg = 1, $required_arg, ) { } Good: class some::class ( $required_arg, $optional_arg = 1, ) { } ## PerExpressionResourceDefaults Warns if local resource defaults are used Bad: Exec { provider => shell, } exec { 'run command': command => 'echo Hello', } ## ReadableArgumentsName Warns if argument name is not readable enough Bad: class some::class ( String $c = '/etc/config', ) { } Good: class some::class ( String $config = '/etc/config', ) { } ## ReferenceToUndefinedValue Warns if variable is not defined in current context Bad: if $some_undefined_variable { } ## RelationToTheLeft Checks for left-directed relations Bad: Class['c'] <- Class['b'] <~ Class['a'] Good: Class['a'] ~> Class['b'] -> Class['c'] ## SelectorInAttributeValue Warns if selector (… ? … : …) used in resource attribute Bad: file { '/etc/shadow': mode => $is_secure ? '0600' : '0644', } Good: $file_mode = $is_secure ? '0600' : '0644' file { '/etc/shadow': mode => $file_mode, } ## SensitiveArgumentWithDefault Warns if argument typed with Sensitive contains default value Bad: class some::class ( Sensitive $password = 'admin', ) Public available default value for sensitive data is nonsense. Good: class some::class ( Sensitive $password, ) ## StatementWithNoEffect Checks for statements without side effects Bad: if $a { if $b { 2 + 2 } } ## UnconditionalExec Warns if exec { … } is specified without unless, onlyif, creates or refreshonly attributes Bad: exec { 'run command': command => '/bin/rm -rf /var/cache/myapp', } Good: exec { 'run command': command => '/bin/rm -rf /var/cache/myapp', onlyif => 'test -e /var/cache/myapp', } ## UniqueArgumentsNames Checks for class/definition/plan arguments uniqueness Bad: class some::class ( $arg, $arg, $arg, ) { } ## UniqueAttributeName Resource attributes must be unique Bad: service { 'sshd': ensure => running, ensure => stopped, } ## UnusedVariables Checks for unused variables. Experimental lint false-positives are possible. Bad: class some::class ( $unused_argument, ) { service { 'sshd': ensure => running, } } ## UpperCaseName Warns if resource set used with uppercase letters Bad: Service { 'sshd': ensure => running, } Good: service { 'sshd': ensure => running, } ## UselessDoubleQuotes Warns if double quoted string has no interpolated expressions and no escaped single quotes Bad: $var = "simple literal" Good: $var = 'simple literal' ## UselessParens Checks for extra parens Bad: if (($var1) or ($var2)) { } Good: if $var1 or $var2 { } # Linter for YAML files Some basic checks are implemented: - File is not executable - File is empty (no root value available) - File parsed without syntax errors - Maps does not contain duplicate keys - Attempt to merge anchor which type is not array nor map # Linter for Hiera YAML files All lints of YAML files plus: ## Reference to a module which has syntax errors Linter will fail if someclass was unable to parse: some_class::argument: 1 ## Reference to class which is not found in modules/ Linter will fail if modules/someclass/init.pp does not exists: some_class::argument: 1 ## Reference in undefined class argument Linter will fail if someclass does not accept argument $argumentname: some_class::argument_name: 1 ## Single column in the name of key of root map Linter protects agains typos like: some_class:argument_name: 1