The gossfile configuration
Syver test creation
Syver tests can be created by using either of following methods.
syver autoadd <resource to test>syver add <resource to test>- manually create YAML/JSON test file by hand.
To customize the parameters generated by syver add and syver autoadd YAML file you need to manually edit it.
syver add package nginx will generate below YAML
To test uninstall scenario you would need to manually edit it and set it as below.
Important note about goss file format
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:
$ syver 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:
$ syver 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 syver add and syver 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.
Schema
A Json draft 7 schema available at https://raw.githubusercontent.com/krameff/syver/main/docs/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://raw.githubusercontent.com/krameff/syver/main/docs/schema.yamlschema version=Json schema version 7file path pattern=*/goss.yaml
Unknown top-level keys
A "top-level key" is one of the un-indented keys at the very start of a
gossfile -- file:, port:, command:, gossfile: and so on, the ones
listed in Available tests below plus a handful of
others like discovery:. If you write one of these with a typo, or one
that a future syver understands but the version you're running does not,
that whole block is not rejected. It is skipped, silently, and syver runs
the rest of the file as if it had never been written -- same exit code,
same Count/Failed, nothing to tell you a block went untested.
A spec that silently skips a block you thought was being checked is worse than one that fails outright: nobody investigates a green result. Syver still skips the key -- nothing about what gets tested changes -- but it now says so:
$ syver -g syver.yaml validate
[WARN] syver.yaml:4: unknown top-level key "some-vendor-thing" -- ignored
[WARN] syver.yaml:7: unknown top-level key "prot" -- ignored (did you mean "port"?)
If the key is close to a real one, the warning suggests the fix. The warning
is informational only -- it does not fail the run or change Count/Failed
in the result.
Two kinds of top-level key are deliberately exempt, and neither produces a warning:
- A key prefixed
x-. This is the same convention docker-compose uses for extension fields: a documented, explicit way to say "this block is mine, leave it alone." - A key whose value carries a YAML anchor, such as a shared block
referenced elsewhere with
<<::
common-checks: is not a key syver understands on its own, but it exists
only to carry the anchor command: merges in below, so it is not warned
about. This is detected structurally (does the value define an anchor?),
not by name -- a block with no anchor is not covered by this exemption,
which is exactly what the x- prefix is for.
This check only applies to YAML gossfiles. JSON gossfiles have the same gap -- an unknown JSON top-level key is dropped the same way -- and it is not yet covered.
A --vars file is never checked against this. A vars file is arbitrary
user-supplied data with no fixed vocabulary, so every key in it is expected
to be "unknown" to syver and none of them warn.
See examples/unknown-top-level-key.yaml
for a runnable spec that shows both sides of this: a genuine typo that
warns, and a legitimate anchor-carrying block next to it that does not.
Available tests
- addr
- command
- dns
- file
- gossfile
- group
- http
- interface
- kernel-param
- matching
- mount
- package
- port
- process
- service
- user
addr
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
command
Validates the exit-status and output of a command. This can be used in combination with the gjson matcher to create powerful syver 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
A gossfile is executable input
exec is run through the system shell (sh -c on Linux and macOS, cmd /c
on Windows), so shell metacharacters, pipes and redirection all work -- and
anyone who can supply or edit a gossfile can run arbitrary commands as the
user running syver. Treat a gossfile with the same care as a shell script:
only run ones you trust, and remember that a --vars file templated into an
exec string carries the same power, since whoever controls the vars file
controls part of the command.
On timeout the command is killed along with any child processes it started, on every platform.
One exception on Linux and macOS, deliberate rather than an oversight: a
process that detaches itself into a new session -- setsid, nohup, most
daemons -- has left the group syver signals, so it survives the timeout and
is reparented to init. Syver has no portable way to find it again. Under
serve each such timeout leaves one process behind for as long as it chooses
to run, so a spec that repeatedly times out a daemonising command will
accumulate them. If a check needs to start a daemon, have it start the daemon
and exit, rather than relying on the timeout to clean up.
Windows has no such exception. The process tree is held in a Job Object,
which a process cannot leave unless it was created with
CREATE_BREAKAWAY_FROM_JOB and the job allows it, so a detaching child is
terminated with the rest. A command that succeeds is never touched: only a
timeout terminates the tree, so a check that deliberately starts a background
process and exits zero leaves it running.
timeout values
timeout is in milliseconds and defaults to 10000 when omitted or set to
0. A negative value is a mistake rather than a way to disable the bound:
syver warns once and uses the default. There is no way to run a command
unbounded.
A command whose child processes outlive it can take up to twice the timeout to return, because syver waits that long for their output streams to close before reading the result. Commands that exit normally return immediately.
timeout applies only to command. The checks syver runs on your behalf for
other resource types -- systemctl, service, rpm, dpkg-query, apk,
pacman, getent -- carry their own fixed bound of 30 seconds, which is not
configurable, because how long they take is a property of the host's tooling
rather than of your spec. On top of that syver allows a few more seconds to
collect whatever output is still arriving, so one of these takes at most about
35 seconds to give up on a host whose package or service manager has stopped
answering. Checks run in parallel, so several stuck ones mostly overlap rather
than queue -- though if more are stuck at once than syver runs in parallel,
the ones still waiting for a slot do add to the total.
dns
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:
AAAAACAACNAMEMXNSPTRSRVTXT
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
file
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
acl and acl-sid (Windows only)
acl reports the file's DACL, one list element per ACE, spelled the way icacls
prints it -- so a failing assertion can be compared against icacls <path> with
no translation step:
file:
C:\Windows\System32\drivers\etc\hosts:
exists: true
# every ACE must be present; others may exist
acl:
- 'NT AUTHORITY\SYSTEM:(I)(F)'
- 'BUILTIN\Administrators:(I)(F)'
- 'BUILTIN\Users:(I)(RX)'
C:\ProgramData\app\secrets.dat:
exists: true
# these ACEs and NO others -- the form a hardening control wants
acl:
consist-of:
- 'NT AUTHORITY\SYSTEM:(F)'
- 'BUILTIN\Administrators:(F)'
# and nothing may be inherited from the parent
acl-sid:
not:
contain-element:
contain-substring: '(I)'
An element is PRINCIPAL:(I)(DENY)(OI)(CI)(IO)(NP)(RIGHTS), with each
parenthesised group present only when it applies, in that order. (I) marks an
inherited ACE, (DENY) a deny ACE, the rest are inheritance propagation flags,
and RIGHTS is a simple-rights letter (F, M, RX, R, W, D, N) or a
comma-joined list of specific tokens (GR,GE, RD,WD,X, ...), exactly as
icacls /? documents them.
Because it is an ordinary list, every array matcher applies: a bare
list means "at least these", consist-of means "these and no others", and
contain-element with a string matcher tests one ACE. The same principal can
appear more than once, which is normal rather than exotic -- on C:\Windows,
BUILTIN\Administrators holds (M) on the directory itself and
(OI)(CI)(IO)(F) for the things inside it.
acl-sid is the same ACEs in the same order with principals as SID strings.
Prefer it for any spec that must run on more than one Windows locale:
BUILTIN\Administrators is a localised name and a spec written on English
Windows will not match a German host, whereas S-1-5-32-544 is the same
everywhere. syver add writes acl, because it exists to give you something
readable to start from; switching the key is a deliberate edit.
Both attributes report the DACL as it stands. Neither computes effective
access for a principal, which would fold in group membership and deny
precedence and could no longer be compared against icacls. Audit entries (the
SACL) are not read at all: that needs a privilege beyond what reading
permissions requires.
On any platform other than Windows both attributes error rather than reporting an empty list, so a cross-platform gossfile fails loudly instead of quietly agreeing with itself.
gossfile
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.
group
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
http
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"
request-query-params: # Set URL query parameters (properly encoded; supports duplicate keys)
foo:
- bar
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.
interface
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
kernel-param
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.
mount
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
matching
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:
This could then be passed into syver: syver --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
registry
Validates a Windows registry key or value. Windows only -- on Linux and macOS this resource is not available, see platforms.
Uses the native golang.org/x/sys/windows/registry API, so no PowerShell or
reg.exe subprocess is involved.
registry:
# a key: the trailing backslash is what makes this a key check rather
# than a check on a value named CurrentVersion
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\:
exists: true
# a value: the last path segment is read as the value name
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\CurrentVersion:
exists: true
value: "10.0"
type: REG_SZ
# use :: when the value name itself contains a backslash, or when the
# split would otherwise be ambiguous
HKLM\SOFTWARE\Vendor\Product::Install\Path:
exists: true
Supported hives, as the first path segment. Each may be written short, long,
or with the PowerShell provider colon, so a path pasted from regedit's address
bar or from Get-ItemProperty output works unedited:
| Short | Long | PowerShell |
|---|---|---|
HKLM |
HKEY_LOCAL_MACHINE |
HKLM: |
HKCU |
HKEY_CURRENT_USER |
HKCU: |
HKCR |
HKEY_CLASSES_ROOT |
HKCR: |
HKU |
HKEY_USERS |
HKU: |
HKCC |
HKEY_CURRENT_CONFIG |
HKCC: |
Hive names are case-insensitive.
Attributes:
exists-- whether the key or value is present. The only required attribute.value-- the data held by the value, always read as a string. Accepts any matcher. NoteREG_MULTI_SZis returned as its entries joined by newlines, not as a list, so usecontain-substringormatch-regexprather than list matchers likecontain-element.type-- the value's data type. EveryREG_*type is reported, includingREG_NONE,REG_LINK,REG_DWORD_BIG_ENDIANand the threeREG_RESOURCE_*hardware descriptor types.view-- which WOW64 registry view to read:32,64, ornative. Defaults tonative, which is what every spec written before this attribute existed gets, and means whatever the OS shows a 64-bit process.
Choosing a view
On 64-bit Windows some keys exist twice. A 32-bit program is redirected under
Wow6432Node and sees a different copy from a 64-bit one. view: lets a spec
say which copy it means instead of inheriting syver's own architecture:
registry:
# the redirected copy a 32-bit installer wrote, under Wow6432Node
HKLM\SOFTWARE\Vendor\Product\Version:
exists: true
view: "32"
# the native 64-bit copy. Same path, different branch -- so give the two
# entries distinct keys, or use `name:` to point both at one path
HKLM\SOFTWARE\Vendor\Product64\Version:
exists: true
view: "64"
Quote the value. Syver itself accepts view: 32 unquoted -- the YAML integer is
coerced to a string on load, and renders back out as view: "32" -- but
schema.yaml types the attribute as a string, so the unquoted
form fails schema validation against a spec syver would have run. Quoting keeps
the two in step. view is matched case-insensitively and surrounding whitespace
is ignored, so NATIVE and native are the same value.
The trailing backslash, and why it is not guessed
By default the last backslash-separated segment of the path is treated as the
value name. Use the explicit :: separator when that guess would be wrong,
for example when a value name contains backslashes.
A trailing backslash addresses the key itself rather than a value, and the difference is not cosmetic. These two ask different questions and both answer truthfully:
HKLM\...\ProfileList exists -> false is there a VALUE named ProfileList?
HKLM\...\ProfileList\ exists -> true is there a KEY named ProfileList?
syver does not guess which you meant, because guessing would move the ambiguity
somewhere you cannot see it. Instead, when a value lookup misses and a key of
that name exists in the same place, it logs a warning naming the alternative.
Asserting exists: false against the first form therefore passes and tells
you it may not be testing what you think.
A key can exist while having no default value, so exists: true on a path
ending in a backslash alongside a value lookup that reports not-found is a
coherent pair rather than a contradiction.
REG_EXPAND_SZ is compared unexpanded
A REG_EXPAND_SZ value is matched as stored. A value holding
%SystemRoot%\System32 is compared as that literal text, not as
C:\Windows\System32. This is deliberate: benchmarks state the stored form,
and expansion depends on the environment of whoever is asking.
package
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.
port
Validates the state of a local port.
Note
Syver might consider your port to be listening on tcp6 rather than tcp,
try running syver add port .. to see how syver 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
pid: # what PID(s) own the socket
- 1234
skip: false
Note
pid is only populated when the OS lets syver map the socket back to its
owning process (this needs the same access as reading /proc/<pid>/fd on
Linux). If the owner can't be resolved, that connection is left out of
pid rather than reported as PID 0.
Note
syver add/discovery does not auto-populate pid, unlike the other
attributes on this page -- PIDs get reassigned on every restart, so a
value captured at add time would already be stale by the time the
gossfile runs. Add pid by hand if you want it checked.
process
Validates if a process is running.
process:
chrome:
# required attributes
running: true
# optional attributes
# defaults to hash key
comm: chrome
status: # process state(s), e.g. detect zombie processes
- running
user: # user(s) the process is running as, e.g. flag anything running as root
- chrome
skip: false
Note
status and user are aggregated across every PID matching this
executable, so a chrome entry backed by several processes reports the
distinct set of states/owners seen, not just one of them. status values
are one of running, sleep, stop, idle, wait, lock, zombie.
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.
service
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
Windows-only service attributes
On Windows, service: reads the Service Control Manager directly and six further
attributes are available:
service:
EventLog:
enabled: true
running: true
start-type: automatic # boot, system, automatic, manual, disabled
delayed-start: false
run-as: 'NT AUTHORITY\LocalService'
display-name: Windows Event Log
pid:
gt: 0 # a range, not a value -- see below
Dnscache:
dependencies:
contain-element: nsi
start-typeis whatenabledis derived from.enabledis true forboot,systemandautomatic.delayed-startis a separate flag rather than a sixth start type: the Services UI's "Automatic (Delayed Start)" isstart-type: automaticplusdelayed-start: true.run-asis the account as the SCM stores it, not normalised. Windows itself reportsLocalSystem,localSystemandNT AUTHORITY\LocalServicein different places, and syver reports what the machine says.dependencieslists services and load-order groups that must start first, verbatim. A name beginning with+is a load-order group, not a service.display-nameis localised. Assert the service key name instead in a spec that has to run on more than one Windows language.pidis 0 when the service is not running, andsyver add servicedoes not emit it: a pid changes at every restart, so a generated spec pinning one would fail at the next reboot. Assert a range.
All six error on every other platform, so a spec that sets one on Linux fails rather than quietly comparing against an empty value.
Note
This will not automatically check if the process is alive, it will check the status from systemd/upstart/init.
user
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).
Matchers
Default Matchers
Default matchers are determined by the attribute value received from the system.
An empty list is not an expectation
stderr: [] does not assert that stderr was empty. An empty list
means "no expectation for this attribute", and the attribute is skipped
entirely -- it produces no result and does not appear in the test count.
This is the form syver add and syver autoadd emit for an attribute
they found nothing to assert about, which is why it appears throughout
generated gossfiles and in the examples on this page. It behaves the
same way on every attribute of every resource type.
To assert that output really is empty, say so:
The one exception is each resource's mandatory attribute --
file.exists, command.exit-status, http.status and so on. Those
always report, so a resource always produces at least one result.
Bool, Strings, Integers
Bool, Strings and integers are compared using equality, for example:
Arrays
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
io.Readers
This is the most magical matcher for syver. 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 syver 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 in the Go regexp/syntax package
Warning
You will need the double backslash (\\) escape for Regex special entities, for example \\s for blank spaces.
Example
The above can be expressed as:
Transforms
If the system state type and the expected type don't match, syver 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:
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:
$ syver 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
$ syver 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
Advanced Matchers
Syver supports advanced matchers by converting YAML input to gomega matchers.
An empty matcher group is a syntax error
A matcher group with nothing in it asserts nothing, so Syver rejects it rather than report a result without looking at the value:
and: [] # syntax error
or: [] # syntax error
contain-elements: [] # syntax error
gjson: {} # syntax error
Two empty forms are still accepted, because each checks the value:
consist-of: [] asserts it is empty, and a bare [] (as in stderr: [],
which syver add writes) is unchanged.
String Matchers
These will convert the system attribute to a string prior to matching.
'55'- Checks that the numeric is "55" when converted to stringhave-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 regexpcontain-substring: '2'- Checks if string contains "2"
Example
Numeric matchers
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..
Array matchers
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
Misc matchers
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 withgjsonnot: 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
semver-constraint
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
gjson
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"}}
Discovery
Discovery tests run lightweight checks and expose the results as template variables for conditional test suites. This supports portable gossfiles across heterogeneous estates (see goss-org/goss#784).
Define discovery tests under the top-level discovery key. Each test requires a register
attribute naming the variable written to output:
discovery:
package:
auditd:
register: auditd_installed
installed: true
file:
/usr/bin/clang:
register: clang_available
exists: true
Run discovery and emit vars-compatible JSON:
Output shape:
Run discovery before the main gossfile in one command (preferred):
Or put discovery tests in the same gossfile as the main suite:
discovery:
file:
/etc/hosts:
register: hosts_exists
exists: true
{{ if .Discovered.hosts_exists }}
file:
/etc/hosts:
exists: true
{{ end }}
When both --discover and an inline discovery: section are present, the --discover
file wins and inline discovery is ignored (with an INFO log).
Pipe discovery output into a main gossfile (still supported for tooling and CI export):
Use verbose documentation output on the main run:
syver --vars <(syver validate -g discovery.yaml --format discovery) \
validate -g goss.yml --format documentation
Or save discovery output to a file first:
syver validate -g discovery.yaml --format discovery > discovered.json
syver --vars discovered.json validate -g goss.yml --format documentation
Complete example (also in
integration-tests/syver/examples/discovery/):
discovery.yaml
goss.yml
Use .Discovered in templates:
Discovery checks always exit 0 when execution succeeds; boolean values reflect actual system state. The main validate run decides pass/fail.
Combine discovery with test dependencies: discovery populates .Discovered
so templates can gate which tests exist; depends-on then skips dependents when a prerequisite
fails.
{{ if .Discovered.hosts_exists }}
file:
prereq:
path: /nonexistent/goss-discover-dep-base
exists: true
command:
dependent:
depends-on:
- prereq
exec: true
exit-status: 0
{{ end }}
See integration-tests/syver/examples/discovery/goss-with-deps.yml.
Test dependencies
Tests may declare prerequisites with depends-on. Dependent tests are skipped (not failed) when a
prerequisite fails (see goss-org/goss#1043).
file:
{{.Vars.root_dir}}/bin/clang:
exists: true
command:
clang-compile-basic:
depends-on:
- {{.Vars.root_dir}}/bin/clang
exec: 'clang basic.c -o basic'
exit-status: 0
References use the gossfile map key, or type:key when ambiguous across resource types.
Independent dependency chains still run in parallel; only declared dependencies are serialized.
Templates
Syver 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 files (merged in order){{.Discovered}}- Values from discovery (--discover, inlinediscovery:, or--vars)
Available functions:
Templates can generate secrets and certificates
The template function library includes cryptographic helpers, and they have
always been available rather than being new: bcrypt, htpasswd,
derivePassword, genPrivateKey, genCA, genCAWithKey,
genSelfSignedCert, genSelfSignedCertWithKey, genSignedCert,
genSignedCertWithKey, buildCustomCert, encryptAES and decryptAES.
They return values into the rendered gossfile and write nothing to disk. If
you are reviewing a gossfile someone else wrote, this is worth knowing: a
template can mint a certificate or encrypt a string before any check runs.
Note that a gossfile can already run arbitrary commands through the
command: resource, so these functions do not widen what an author can do,
they are simply easy to miss.
-
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 lowercase
toUpper- Changes piped input to UPPERCASE
findStringSubmatch 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 := $rcConf | get "login" }} {{ $PassDBrc := $rcConf | get "password" }} {{ $DBrc := $rcConf | get "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 := $rcConf | get "1" }} {{ $PassDBrc := $rcConf | get "2" }} {{ $DBrc := $rcConf | get "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 syver add/autoadd.
One way to get around this is to split your template and static goss files and use gossfile to import.
Note
Three of the custom functions share a name with a Sprout function, and the
custom one takes priority: toUpper, toLower and regexMatch.
For toUpper and toLower this is not merely cosmetic. Sprout's versions
accept any type and return nothing when given a non-string, so
{{ toUpper 42 }} would render as an empty value. The custom versions take
a string and nothing else, so the same expression fails the run with a
template error naming the line. A check that silently renders nothing is
worse than one that stops, so the stricter behaviour is deliberate.
Examples
Using puppetlabs/facter or chef/ohai as external tools to provide vars.
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 Sprout.
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}}