Skip to content

Latest commit

 

History

History
843 lines (565 loc) · 12.9 KB

README.org

File metadata and controls

843 lines (565 loc) · 12.9 KB

About

Shadowplay is a utility for checking puppet syntax, a puppet manifest linter, a pretty printer, and a utility for exploring the Hiera.

./doc/screenshot-emacs.png

Installation methods

Via deb/rpm

Latest releases can be downloaded here: https://github.com/mailru/shadowplay/releases

MaOS binaries

Lastest binaries for MacOS can be downloaded here: https://github.com/mailru/shadowplay/releases

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 { }

MagicNumber

Warns if term contains magic number.

Bad:

if $port == 58271 { }

Good:

$default_service_port = 58271

if $port == $default_service_port { }

TooManyArguments

Warns if class or definition accepts too many arguments.

HugeCodeBlock

Points to too long statements lists.

DeepCode

Alerts on too deep code blocks.

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 some_class was unable to parse:

some_class::argument: 1

Reference to class which is not found in modules/

Linter will fail if modules/some_class/init.pp does not exists:

some_class::argument: 1

Reference in undefined class argument

Linter will fail if some_class does not accept argument $argument_name:

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