Goss tests can be created by using either of following methods.
goss autoadd <resource to test>
goss add <resource to test>
- manually create YAML/JSON test file by hand.
To customize the parameters generated by goss add
and goss autoadd
YAML file you need to manually edit it.
goss add package nginx
will generate below YAML
package:
nginx:
installed: true
versions:
- 1.17.8
To test uninstall scenario you would need to manually edit it and set it as below.
package:
nginx:
installed: false
It is important to note that both YAML and JSON are formats that describe a nested data structure.
file:
/etc/httpd/conf/httpd.conf:
exists: true
service:
httpd:
enabled: true
running: true
file:
/var/www/html:
filetype: directory
exists: true
If you try to validate this file, it will only run the second file
test:
$ goss validate --format documentation
File: /var/www/html: exists: matches expectation: [true]
File: /var/www/html: filetype: matches expectation: ["directory"]
Service: httpd: enabled: matches expectation: [true]
Service: httpd: running: matches expectation: [true]
Total Duration: 0.014s
Count: 8, Failed: 0, Skipped: 0
As you can see, the first file
check has not been run because the second file
entry overwrites the previous one.
You need to make sure all the entries of the same type are under the same declaration.
file:
/etc/httpd/conf/httpd.conf:
exists: true
/var/www/html:
filetype: directory
exists: true
service:
httpd:
enabled: true
running: true
Running validate with this configuration will correctly check both files:
$ goss validate --format documentation
File: /var/www/html: exists: matches expectation: [true]
File: /var/www/html: filetype: matches expectation: ["directory"]
File: /etc/httpd/conf/httpd.conf: exists: matches expectation: [true]
Service: httpd: enabled: matches expectation: [true]
Service: httpd: running: matches expectation: [true]
Total Duration: 0.014s
Count: 10, Failed: 0, Skipped: 0
Please note that using the goss add
and goss autoadd
command will create a valid file,
but if you're writing your files by hand you'll save a lot of time by taking this in consideration.
If you want to keep your tests in separate files, the best way to obtain a single, valid, file is to create a main goss file that includes the others with the gossfile directive and then render it.
A Json draft 7 schema available at https://goss.rocks/schema.yaml makes it easier to edit simple goss.yaml files in IDEs, providing usual coding assistance such as inline documentation, completion and static analysis. See #793 for screenshots.
For example, to configure the Json schema in JetBrains intellij IDEA, follow documented instructions, with arguments such as:
schema url=https://goss.rocks/schemas/gossfile.yaml
schema version=Json schema version 7
file path pattern=*/goss.yaml
- addr
- command
- dns
- file
- gossfile
- group
- http
- interface
- kernel-param
- matching
- mount
- package
- port
- process
- service
- user
Validates if a remote address:port
are accessible.
addr:
tcp://ip-address-or-domain-name:80:
# required attributes
reachable: true
# optional attributes
# defaults to hash key
address: "tcp://ip-address-or-domain-name:80"
timeout: 500
local-address: 127.0.0.1
Validates the exit-status and output of a command. This can be used in combination with the gjson matcher to create powerful goss custom tests.
command:
'go version':
# required attributes
exit-status: 0
# optional attributes
# defaults to hash key
exec: "go version"
stdout:
- go version go1.6 linux/amd64
stderr: []
timeout: 10000 # in milliseconds
skip: false
stdout
and stderr
can be a string or pattern
The exec
attribute is the command to run; this defaults to the name of
the hash for backwards compatibility
Validates that the provided address is resolvable and the addrs it resolves to.
dns:
localhost:
# required attributes
resolvable: true
# optional attributes
# defaults to hash key
resolve: localhost
addrs:
- 127.0.0.1
- ::1
server: 8.8.8.8 # Also supports server:port
timeout: 500 # in milliseconds (Only used when server attribute is provided)
It is possible to validate the following types of DNS records, but requires the server
attribute be set:
A
AAAA
CAA
CNAME
MX
NS
PTR
SRV
TXT
To validate specific DNS address types, prepend the hostname with the type and a colon, a few examples:
dns:
# Validate a CNAME record
CNAME:c.dnstest.io:
resolvable: true
server: 208.67.222.222
addrs:
- "a.dnstest.io."
# Validate a PTR record
PTR:8.8.8.8:
resolvable: true
server: 8.8.8.8
addrs:
- "dns.google."
# Validate and SRV record
SRV:_https._tcp.dnstest.io:
resolvable: true
server: 208.67.222.222
addrs:
- "0 5 443 a.dnstest.io."
- "10 10 443 b.dnstest.io."
Please note that if you want localhost
to only resolve 127.0.0.1
you'll need to use Advanced Matchers
dns:
localhost:
resolvable: true
addrs:
consist-of: [127.0.0.1]
timeout: 500 # in milliseconds
Validates the state of a file, directory, socket, or symbolic link
file:
/etc/passwd:
# required attributes
exists: true
# optional attributes
# defaults to hash key
path: /etc/passwd
mode: "0644"
size: 2118 # in bytes
owner: root
group: root
filetype: file # file, symlink, directory, socket
contents: [] # Check file content for these patterns
md5: 7c9bb14b3bf178e82c00c2a4398c93cd # md5 checksum of file
# A stronger checksum alternatives to md5 (recommended)
sha256: 7f78ce27859049f725936f7b52c6e25d774012947d915e7b394402cfceb70c4c
sha512: cb71b1940dc879a3688bd502846bff6316dd537bbe917484964fe0f098e9245d80958258dc3bd6297bf42d5bd978cbe2c03d077d4ed45b2b1ed9cd831ceb1bd0
/etc/alternatives/mta:
# required attributes
exists: true
# optional attributes
filetype: symlink # file, symlink, directory, socket
linked-to: /usr/sbin/sendmail.sendmail
skip: false
contents
can be a string or a pattern
Import other gossfiles from this one. This is the best way to maintain a large number of tests, and/or create profiles. See render for more examples. Glob patterns can be also be used to specify matching gossfiles.
gossfile:
myapplication:
file: myapp_gossfile.yaml
skip: false
*.yaml:
skip: true
goss_httpd.yaml: {}
/etc/goss.d/*.yaml: {}
You can specify the gossfile(s) either as the resource key, or using the 'file' attribute.
If the 'skip' attribute is true, then the file is not processed. If the filename is a glob pattern, then none of the matching files are processed. Note that this is not the same as skipping the contained resources; any overrides in the referenced gossfile will not be processed, and the resource count will not be incremented. Skipping a gossfile include is the same as omitting the gossfile resource entirely.
Validates the state of a group
group:
nfsnobody:
# required attributes
exists: true
# optional attributes
# defaults to hash key
groupname: /etc/passwd
gid: 65534
skip: false
Validates HTTP response status code and content.
http:
https://www.google.com:
# required attributes
status: 200
# optional attributes
# defaults to hash key
url: https://www.google.com
allow-insecure: false
no-follow-redirects: false # Setting this to true will NOT follow redirects
timeout: 1000
request-headers: # Set request header values
- "Content-Type: text/html"
headers: [] # Check http response headers for these patterns (e.g. "Content-Type: text/html")
request-body: '{"key": "value"}' # request body
body: [] # Check http response content for these patterns
username: "" # username for basic auth
password: "" # password for basic auth
ca-file: "" # CA root certs pem file, ex: /etc/ssl/cert.pem
cert-file: "" # certificate file to use for authentication (used with key-file)
key-file: "" # private-key file to use for authentication (used with cert-file)
proxy: "" # proxy server to proxy traffic through. Proxy can also be set with environment variables http_proxy.
skip: false
method: PUT # http method
!!! note
only the first Host
header will be used to set the Request.Host
value if multiple are provided.
Validates network interface values
interface:
eth0:
# required attributes
exists: true
# optional attributes
# defaults to hash key
name: eth0
addrs:
- 172.17.0.2/16
- fe80::42:acff:fe11:2/64
mtu: 1500
Validates kernel param (sysctl) value.
kernel-param:
kernel.ostype:
# required attributes
value: Linux
# optional attributes
# defaults to hash key
name: kernel.ostype
To see the full list of current values, run sysctl -a
.
Validates mount point attributes.
mount:
/home:
# required attributes
exists: true
# optional attributes
# defaults to hash key
timeout: 1000
mountpoint: /home
opts:
- rw
- relatime
# This maps to the per-superblock options, see:
# https://man7.org/linux/man-pages/man5/proc.5.html
# https://man7.org/linux/man-pages/man2/mount.2.html
vfs-opts:
- rw
source: /dev/mapper/fedora-home
filesystem: xfs
usage: #% of blocks used in this mountpoint
lt: 95
Validates specified content against a matcher. Best used with Templates.
With Templates
Let's say we have a data.json
file that gets generated as part of some testing pipeline:
{
"instance_count": 14,
"failures": 3,
"status": "FAIL"
}
This could then be passed into goss: goss --vars data.json validate
And then validated against:
matching:
check_instance_count: # Make sure there is at least one instance
content: {{ .Vars.instance_count }}
matches:
gt: 0
check_failure_count_from_all_instance: # expect no failures
content: {{ .Vars.failures }}
matches: 0
check_status:
content: {{ .Vars.status }}
matches:
- not: FAIL
Without Templates
matching:
has_substr: # friendly test name
content: some string
matches:
match-regexp: some str
has_2:
content:
- 2
matches:
contain-element: 2
has_foo_bar_and_baz:
content:
foo: bar
baz: bing
matches:
and:
- have-key: baz
Validates the state of a package
package:
httpd:
# required attributes
installed: true
# optional attributes
# defaults to hash key
name: httpd
versions:
- 2.2.15
skip: false
!!! note
this check uses the --package <format>
parameter passed on the command line.
Validates the state of a local port.
!!! note
Goss might consider your port to be listening on tcp6
rather than tcp
,
try running goss add port ..
to see how goss detects it.
(explanation)
port:
# {tcp,tcp6,udp,udp6}:port_num
tcp:22:
# required attributes
listening: true
# optional attributes
# defaults to hash key
port: 'tcp:22'
ip: # what IP(s) is it listening on
- 0.0.0.0
skip: false
Validates if a process is running.
process:
chrome:
# required attributes
running: true
# optional attributes
# defaults to hash key
comm: chrome
skip: false
!!! note This check is inspecting the name of the binary, not the name of the process.
For example, a process with the name `nginx: master process /usr/sbin/nginx` would be checked with the process `nginx`.
To discover the binary of a pid run `cat -E /proc/<PID>/comm`.
Validates the state of a service.
service:
sshd:
# Optional attributes
# defaults to hash key
name: sshd
enabled: true
running: true
runlevels: ["3", "4", "5"] # Alpine example, runlevels: ["default"]
skip: false
runlevels
is only supported on Alpine init, sysv init, and upstart
!!! note
This will not automatically check if the process is alive, it will check the status from systemd
/upstart
/init
.
Validates the state of a user
user:
nfsnobody:
# required attributes
exists: true
# optional attributes
# defaults to hash key
username: nfsnobody
uid: 65534
gid: 65534
groups:
- nfsnobody
home: /var/lib/nfs
shell: /sbin/nologin
skip: false
!!! note
This check is inspecting the contents of local passwd file /etc/passwd
, this does not validate remote users (e.g. LDAP).
Default matchers are determined by the attribute value received from the system.
Bool, Strings and integers are compared using equality, for example:
matching:
basic_string:
content: 'foo'
matches: 'foo'
user:
nfsnobody:
exists: true
uid: 65534
Arrays are treated as a contains-elements by default, this validates that the expected test is a subset of the returned system state.
matching:
basic_array:
content:
- 'group1'
- 'group2'
- 'group3'
matches:
- 'group1'
- 'group2'
# This fails, since the returned result and it's no longer a subset
basic_array_failing:
content:
- 'group1'
- 'group2'
- 'group3'
matches:
- 'group1'
- 'group2'
- 'group2' # this 2nd group2 is not in the returned content
This is the most magical matcher for goss. It remains a default for historic and performance reasons. Some attributes return an io.Reader that is read line by line (ex. file content, command, http body). This allows goss to validate large files/content efficiently.
Each pattern is checked against the attribute output, the type of patterns are:
"foo"
- checks if any line containsfoo
"!foo"
- inverse of above, checks that no line containsfoo
"\\!foo"
- escape sequence, check if any line contains!string
"/[Rr]egex/"
- verifies that line matches regex"!/[Rr]egex/"
- inverse of above, checks that no line matches regex
!!! note Regex support is based on Golang's regex engine documented here
!!! warning
You will need the double backslash (\\
) escape for Regex special entities, for example \\s
for blank spaces.
!!! example
yaml file: /tmp/test.txt: exists: true contents: - "foo" - "!bar" - "/[Gg]oss/"
The above can be expressed as:
```yaml
file:
/tmp/test.txt:
exists: true
contents:
and:
- contain-element: "foo"
- not: {contain-element: "bar"}
- contain-element: {match-regexp: "[Gg]oss"}
```
If the system state type and the expected type don't match, goss will attempt to transform the system state type before matching it.
For example, kernel-param attribute returns a string, however, it can be tested using numeric comparisons:
!!! example "kernel-param test"
yaml kernel-param: net.core.somaxconn: value: "128"
!!! example "(failing) kernel-param test with transform"
yaml kernel-param: net.core.somaxconn: value: {gt: 200}
When a transformed test fails, it will detail the transformers used,
the -o exclude_raw
option can be used to exclude the raw, untransformed attribute value:
$ goss v
F
Failures/Skipped:
KernelParam: net.core.somaxconn: value:
Expected
128
to be >
200
the transform chain was
[{"to-numeric":{}}]
the raw value was
"128"
Total Duration: 0.001s
Count: 1, Failed: 1, Skipped: 0
$ goss v -o exclude_raw
F
Failures/Skipped:
KernelParam: net.core.somaxconn: value:
Expected
128
to be >
200
the transform chain was
[{"to-numeric":{}}]
Total Duration: 0.001s
Count: 1, Failed: 1, Skipped: 0
Goss supports advanced matchers by converting YAML input to gomega matchers.
These will convert the system attribute to a string prior to matching.
-
'55'
- Checks that the numeric is "55" when converted to string -
have-prefix: pre
- Checks if string starts with "pre" -
have-suffix: suf
- Checks if string ends with "suf" -
match-regexp: '.*'
- Checks if string matches regexp -
contain-substring: '2'
- Checks if string contains "2" -
'55'
- Checks that the numeric is "55" when converted to string -
have-prefix: pre
- Checks if string starts with "pre" -
have-suffix: suf
- Checks if string ends with "suf" -
match-regexp: '.*'
- Checks if string matches regexp -
contain-substring: '2'
- Checks if string contains "2"
!!! example
yaml matching: example: content: 42 matches: and: - '42' - have-prefix: '4' - have-suffix: '2' - match-regexp: '\d{2}' - contain-substring: '2'
These will convert the system attribute to a numeric prior to matching.
42
- If the expected type is a numbergt, ge, lt, le
- Greater than, greater than or equal, less than, etc..
!!! example
yaml matching: example: content: "42" matches: and: - 42 - 42.0 - gt: 40 - lt: 45
These will convert the system attribute to an array prior to matching. Strings are split on "\n"
contain-element: matcher
- Checks if the array contains an element that passes the matchercontain-elements: [matcher, ...]
- checks if the array is a superset of the provided matchers[matcher, ...]
- same as aboveequal: [value, ...]
- Checks if the array is exactly equal to provided arrayconsist-of: [matcher, ...]
- Checks if the array consists of the provided matchers (order does not matter)
!!! example
yaml matching: example: content: [foo, bar, moo] matches: and: - contain-elements: [foo, bar] - [foo, bar] # same as above - equal: [foo, bar, moo] # order matters, exact match - consist-of: [foo, have-prefix: m, bar] # order doesn't matter, can use matchers - contain-element: have-prefix: b
These matchers don't really fall into any of the above categories, or span multiple categories.
equal
- Useful when needing to override a default matcherhave-len: 3
- Checks if the array/string/map has length of 3have-key: "foo"
- Checks if key exists in map, useful withgjson
not: matcher
- Checks that a matcher does not matchand: [matcher, ..]
- Checks that all matchers matchor: [matcher, ..]
- Checks that any matchers match
!!! note When system returns a string it is converted into a one element array and matched
See the following for examples: [link..]fixme
Checks that all versions match semver constraint or range syntax.
This uses semver under the hood, however, wildcards
(e.g. 1.X
are not officially supported and may go away in a future release).
!!! example
yaml matching: semver: content: - 1.0.1 - 1.9.9 matches: semver-constraint: ">1.0.0 <2.0.0 !=1.5.0" semver2: content: - 1.0.1 - 1.5.0 - 1.9.9 matches: not: semver-constraint: ">1.0.0 <2.0.0 !=1.5.0" semver3: content: 1.0.1 matches: semver-constraint: ">5.0.0 || < 1.5.0"
Checks extracted gjson passes the matcher
Example:
matching:
example:
content: '{"foo": "bar", "moo" {"nested": "cow"}, "count": "15"}'
matches:
gjson:
moo.nested: cow
foo: {have-prefix: b}
count: {le: 25}
'@this': {have-key: "foo"}
moo:
and:
- {have-key: "nested"}
- {not: {have-key: "nested2"}}
Goss test files can leverage golang's text/template to allow for dynamic or conditional tests.
Available variables:
{{.Env}}
- Containing environment variables{{.Vars}}
- Containing the values defined in --vars file
Available functions:
-
Custom functions:
mkSlice "ARG1" "ARG2"
: Returns a slice of all the arguments. See examples below for usage.getEnv "var" ["default"]
: A more forgiving env var lookup. If key is missing either "" or default (if provided) is returned.readFile "fileName"
: Reads file content into a string, trims whitespace. Useful when a file contains a token. !!! note Goss will error out during the parsing phase if the file does not exist, no tests will be executed.regexMatch "(some)?reg[eE]xp"
: Tests the piped input against the regular expression argument.toLower
: Changes piped input to lowercasetoUpper
: Changes piped input to UPPERCASEfindStringSubmatch regex string
: Returns map[string]interface{} with the names of the parenthesized subexpressions, like(?P<first>[a-z])
{{ $regexDBrc := "\\'mysql:\\/\\/(?P<login>[a-z0-9]+):(?P<password>[a-z0-9]+)@localhost\\/(?P<database>roundcube_[a-z0-9]+)\\';"}} {{ $rcConf := readFile /home/user/roundcube/config.inc.php | findStringSubmatch $regexDBrc }} {{ $UserDBrc := get $rcConf "login" }} {{ $PassDBrc := get $rcConf "password" }} {{ $DBrc := get $rcConf "database" }}
If not exists named parenthesized subexps, returns stringfied array string:
{{ $regexDBrc := "\\'mysql:\\/\\/([a-z0-9]+):([a-z0-9]+)@localhost\\/(roundcube_[a-z0-9]+)\\';"}} {{ $rcConf := readFile /home/user/roundcube/config.inc.php | findStringSubmatch $regexDBrc }} {{ $UserDBrc := get $rcConf "1" }} {{ $PassDBrc := get $rcConf "2" }} {{ $DBrc := get $rcConf "3" }}
NOTE: stringfied string array begins with "1" ("0" is all the string matched)
!!! warning
gossfiles containing text/template `{{}}` controls will no longer work with `goss add/autoadd`.
One way to get around this is to split your template and static goss files and use [gossfile](#gossfile) to import.
!!! note
Some of Sprig functions have the same name as the older Custom Goss functions.
The Sprig functions are overwritten by the custom functions for backwards compatibility.
Using puppetlabs/facter or chef/ohai as external tools to provide vars.
goss --vars <(ohai) validate
goss --vars <(facter -j) validate
Using mkSlice
to define a loop locally.
file:
{{- range mkSlice "/etc/passwd" "/etc/group"}}
{{.}}:
exists: true
mode: "0644"
owner: root
group: root
filetype: file
{{end}}
Using upper
function from Sprig.
matching:
sping_basic:
content: {{ "hello!" | upper | repeat 5 }}
matches:
match-regexp: "HELLO!HELLO!HELLO!HELLO!HELLO!"
Using Env variables and a vars file:
centos:
packages:
kernel:
- "4.9.11-centos"
- "4.9.11-centos2"
debian:
packages:
kernel:
- "4.9.11-debian"
- "4.9.11-debian2"
users:
- user1
- user2
package:
# Looping over a variables defined in a vars.yaml using $OS environment variable as a lookup key
{{range $name, $vers := index .Vars .Env.OS "packages"}}
{{$name}}:
installed: true
versions:
{{range $vers}}
- {{.}}
{{end}}
{{end}}
# This test is only when the OS environment variable matches the pattern
{{if .Env.OS | regexMatch "[Cc]ent(OS|os)"}}
libselinux:
installed: true
{{end}}
# Loop over users
user:
{{range .Vars.users}}
{{.}}:
exists: true
groups:
- {{.}}
home: /home/{{.}}
shell: /bin/bash
{{end}}
package:
{{if eq .Env.OS "centos"}}
# This test is only when $OS environment variable is set to "centos"
libselinux:
installed: true
{{end}}
# To validate:
$ OS=centos goss --vars vars.yaml validate
# To render:
$ OS=centos goss --vars vars.yaml render
# To render with debugging enabled:
$ OS=centos goss --vars vars.yaml render --debug