Validates that the provided address is resolvable and the addrs it resolves to.
dns:
localhost:
# required attributes
resolvable: true
# optional attributes
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:
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
additionalProperties:
$ref: "#/definitions/dnsTest"
file:
type: object
description: "Validates the state of a file, directory, or symbolic link"
additionalProperties:
$ref: "#/definitions/fileTest"
gossfile:
type: object
description: |
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.
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.
x-intellij-html-description: |
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.
additionalProperties:
$ref: "#/definitions/gossfileTest"
group:
type: object
description: "Validates the state of a group"
additionalProperties:
$ref: "#/definitions/groupTest"
http:
type: object
description: "description: Validates network interface values"
additionalProperties:
$ref: "#/definitions/httpTest"
interface:
type: object
description: "test "
additionalProperties:
$ref: "#/definitions/interfaceTest"
kernel-param:
type: object
description: "test "
additionalProperties:
$ref: "#/definitions/kernelParamTest"
matching:
type: object
description: "Validates specified content against a matcher. Best used with Templates."
x-intellij-html-description: |
Validates specified content against a matcher. Best used with Templates.
Templates:
With 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
Templates:
Without 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-with-value:
foo: bar
- have-key: baz
additionalProperties:
$ref: "#/definitions/matchTest"
mount:
type: object
description: "Validates mount point attributes."
additionalProperties:
$ref: "#/definitions/mountTest"
package:
type: object
description: |
Validates the state of a package"
NOTE: this check uses the --package parameter passed on the command line.
additionalProperties:
$ref: "#/definitions/packageTest"
port:
type: object
description: |
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)
x-intellij-html-description: |
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
ip: # what IP(s) is it listening on
- 0.0.0.0
skip: false
additionalProperties:
$ref: "#/definitions/portTest"
process:
type: object
description: "Validates if a process is running."
additionalProperties:
$ref: "#/definitions/processTest"
service:
type: object
description: "Validates the state of a service."
additionalProperties:
$ref: "#/definitions/serviceTest"
user:
type: object
description: |
Validates the state of a user"
NOTE: This check is inspecting the contents of local passwd file /etc/passwd, this does not validate remote users (e.g. LDAP).
additionalProperties:
$ref: "#/definitions/userTest"
goss-0.4.9/docs/style.css 0000664 0000000 0000000 00000000163 14675050513 0015307 0 ustar 00root root 0000000 0000000 .green {
color: green;
}
.blue {
color: cyan;
}
.orange {
color: orange;
}
.red {
color: red;
}
goss-0.4.9/docs/vars.yaml 0000664 0000000 0000000 00000000215 14675050513 0015272 0 ustar 00root root 0000000 0000000 # Sample vars file used in goss.yaml#matching
# Used for render test and Json schema validation.
instance_count: 1
failures: 0
status: "PASS" goss-0.4.9/examples/ 0000775 0000000 0000000 00000000000 14675050513 0014323 5 ustar 00root root 0000000 0000000 goss-0.4.9/examples/goss.yaml 0000664 0000000 0000000 00000000556 14675050513 0016170 0 ustar 00root root 0000000 0000000 gossfile:
goss_awesome_gomega.yaml: {}
file:
test.txt:
exists: true
contains: |
test file
second line
command:
echo '15':
exit-status: 0
stdout:
and:
- gt: 10
- lt: 50
- match-regexp: '\d{2}'
timeout: 10000
http:
https://ifconfig.me:
status: 200
timeout: 5000
body: '{{.Vars.Ip}}'
goss-0.4.9/examples/goss_awesome_gomega.yaml 0000664 0000000 0000000 00000005016 14675050513 0021223 0 ustar 00root root 0000000 0000000 matching:
# Basic matchers
basic_string:
content: 'this is a test'
matches: 'this is a test'
basic_int:
content: 42
matches: 42
basic_array:
content:
- 'group1'
- 'group2'
- 'group3'
matches:
- 'group1'
- 'group2'
basic_reader:
as-reader: true
content: |
foo bar
moo cow
matches:
- 'foo'
- '/^m.*w$/'
- '!wtf'
- '!/^ERROR:/'
# Transformers
basic_reader_as_array:
as-reader: true
content: |
foo bar
moo cow
matches:
and:
- contain-element: {contain-substring: 'foo'}
- contain-element: {match-regexp: '^m.*w$'}
- not: {contain-substring: 'wtf'}
- not: {match-regexp: '^ERROR:'}
test_numeric_string:
content: 128
matches:
and:
- '128'
- have-prefix: '1'
- have-suffix: '8'
- match-regexp: '\d{3}'
test_string_numeric:
content: '128'
matches:
and:
- 128
- 128.0
- le: 128
- gt: 120
test_string_float:
content: '128.3'
matches:
and:
- 128.3
- le: 129
- gt: 120.2
test_array:
content:
- '45'
- '46'
- '47'
matches:
- contain-element: {match-regexp: "4."}
- '45'
- and: [{ge: 46}, {le: 50}]
test_reader_using_string_matchers:
content: |
foo bar
15
moo cow
as-reader: true
matches:
and:
- have-len: 19
- |
foo bar
15
moo cow
- have-prefix: 'foo'
- have-suffix: "cow\n"
- contain-element:
have-prefix: 'moo'
- contain-elements:
- not: 'this_doesnt_exist'
- lt: 20
- have-prefix: 'moo'
test_reader_as_single_string:
content: 'cool'
as-reader: true
matches: 'cool'
test_reader_using_int_matchers:
content: '40'
as-reader: true
matches:
and:
- le: 250
- ge: 20
test_gjson_transform:
content: '{"foo": "bar", "moo": {"nested": "cow"}, "count": "15"}'
as-reader: true
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"}}
test_gjson_using_this_and_equal:
content: '{"foo": "bar", "baz": "bing"}'
matches:
gjson:
'@this':
equal:
foo: bar
baz: bing
goss-0.4.9/examples/readme.md 0000664 0000000 0000000 00000000133 14675050513 0016077 0 ustar 00root root 0000000 0000000 # How to run this
Basically, run the following: `goss --vars-inline "Ip: $EXTERNAL_IP" v`
goss-0.4.9/examples/test.txt 0000664 0000000 0000000 00000000026 14675050513 0016041 0 ustar 00root root 0000000 0000000 test file
second line
goss-0.4.9/extras/ 0000775 0000000 0000000 00000000000 14675050513 0014013 5 ustar 00root root 0000000 0000000 goss-0.4.9/extras/dcgoss/ 0000775 0000000 0000000 00000000000 14675050513 0015275 5 ustar 00root root 0000000 0000000 goss-0.4.9/extras/dcgoss/README.md 0000664 0000000 0000000 00000007034 14675050513 0016560 0 ustar 00root root 0000000 0000000 # dcgoss
dcgoss is a convenience wrapper around goss that aims to bring the simplicity of goss to docker-compose managed
containers. It is based on `dgoss`.
## Usage
`dcgoss [run|edit] `
### Run
Run is used to validate a docker container defined in `docker-compose.yml`. It expects both a `docker-compose.yml`
and `goss.yaml` file to exist in the directory it was invoked from. Container configuration is used from the
compose file, for example:
**run:**
`docker-compose up db`
**test:**
`dcgoss run db`
`dcgoss run` will do the following:
* Start the container as defined in `docker-compose.yml`
* Stream the containers log output into the container as `/goss/docker_output.log`
* This allows writing tests or waits against the docker output
* (optional) Run `goss` with `$GOSS_WAIT_OPTS` if `./goss_wait.yaml` file exists in the current dir
* Run `goss` with `$GOSS_OPTS` using `./goss.yaml`
### Edit
Edit will launch a docker container, install goss, and drop the user into an interactive shell.
Once the user quits the interactive shell, any `goss.yaml` or `goss_wait.yaml` are copied out into the current directory.
This allows the user to leverage the `goss add|autoadd` commands to write tests as they would on a regular machine.
**Example:**
`dcgoss edit db`
### Environment vars and defaults
The following environment variables can be set to change the behavior of dcgoss.
#### DEBUG
Enables debug output of `dcgoss`.
When running in debug mode, the tmp dir with the container output will not be cleaned up.
**Default:** empty
**Example:**
`DEBUG=true dcgoss edit db`
#### GOSS_PATH
Location of the goss binary to use.
**Default:** `$(which goss)`
#### GOSS_OPTS
Options to use for the goss test run.
**Default:** `--color --format documentation`
#### GOSS_WAIT_OPTS
Options to use for the goss wait run, when `./goss_wait.yaml` exists. (Default: `-r 30s -s 1s > /dev/null`)
#### GOSS_SLEEP
Time to sleep after running container (and optionally `goss_wait.yaml`) and before running tests.
**Default:** `0.2`
#### GOSS_FILES_PATH
Location of the goss yaml files.
**Default:** `.`
**Example:**
`GOSS_FILES_PATH=db dcgoss edit db`
#### GOSS_FILE
Allows to specify a differing name for `goss.yaml`. Useful when the same image is started for different configurations.
**Example:**
`GOSS_FILE=goss_config1.yaml dcgoss run db`
#### GOSS_VARS
The name of the variables file relative to `GOSS_FILES_PATH` to copy into the
docker container and use for valiation (i.e. `dcgoss run`) and copy out of the
docker container when writing tests (i.e. `dcgoss edit`). If set, the
`--vars` flag is passed to `goss validate` commands inside the container.
If unset (or empty), the `--vars` flag is omitted, which is the normal behavior.
**Default:** `''`
#### GOSS_FILES_STRATEGY
Strategy used for copying goss files into the docker container.
If set to `'mount'` a volume with goss files is mounted and log output is streamed into the container as
`/goss/docker_output.log` file.
Other strategy is `'cp'` which uses `'docker cp'` command to copy goss files into docker container.
With the `'cp'` strategy you lose the ability to write tests or waits against the docker output.
The `'cp'` strategy is required especially when docker daemon is not on the local machine.
**Default:** `'mount'`
## Debugging test runs
When debugging test execution its beneficual to set both `DEBUG=true` and `GOSS_WAIT_OPTS=-r 60s -s 5s`
(without the redirect to `/dev/null`).
**Example:**
`DEBUG=true GOSS_FILES_PATH=db GOSS_WAIT_OPTS="-r 60s -s 5s" dcgoss run db`
goss-0.4.9/extras/dcgoss/dcgoss 0000775 0000000 0000000 00000011253 14675050513 0016507 0 ustar 00root root 0000000 0000000 #!/bin/bash
set -e
[ "$DEBUG" ] && set -x
USAGE="USAGE: $(basename "$0") [run|edit] "
GOSS_FILES_PATH="${GOSS_FILES_PATH:-.}"
info() {
echo -e "INFO: $*" >&2;
}
error() {
echo -e "ERROR: $*" >&2;
[[ -e "$service" ]] && docker logs "$service"
exit 1;
}
cleanup() {
set +e
{ kill "$log_pid" && wait "$log_pid"; } 2> /dev/null
[ "$DEBUG" ] || rm -rf "$tmp_dir"
if [[ -n "$service" ]]; then
info "Stopping container"
docker-compose stop > /dev/null
fi
}
run(){
# Copy in goss
cp "${GOSS_PATH}" "$tmp_dir/goss"
chmod 755 "$tmp_dir/goss"
[[ -e "${GOSS_FILES_PATH}/${GOSS_FILE:-goss.yaml}" ]] && install -m ugo+rw "${GOSS_FILES_PATH}/${GOSS_FILE:-goss.yaml}" "$tmp_dir"
[[ -e "${GOSS_FILES_PATH}/goss_wait.yaml" ]] && install -m ugo+rw "${GOSS_FILES_PATH}/goss_wait.yaml" "$tmp_dir"
[[ -n "${GOSS_VARS}" ]] && [[ -e "${GOSS_FILES_PATH}/${GOSS_VARS}" ]] && install -m ugo+rw "${GOSS_FILES_PATH}/${GOSS_VARS}" "$tmp_dir"
# Switch between mount or cp files strategy
GOSS_FILES_STRATEGY=${GOSS_FILES_STRATEGY:="mount"}
case "$GOSS_FILES_STRATEGY" in
mount)
info "Starting docker container"
docker-compose run -d -T --name "$service" -v "$tmp_dir:/goss" --rm "${@:2}"
;;
cp)
info "Creating docker container"
docker-compose run -d -T --name "$service" --rm "${@:2}" > /dev/null
info "Copy goss files into container"
docker cp "$tmp_dir/." "$service:/goss"
;;
*) error "Wrong goss files strategy used! Correct options are \"mount\" or \"cp\"."
esac
docker logs -f "$service" > "$tmp_dir/docker_output.log" 2>&1 &
log_pid="$!"
info "Container name: $service"
}
get_docker_file() {
set +e
if docker exec "$service" sh -c "test -e $1" > /dev/null; then
mkdir -p "${GOSS_FILES_PATH}"
info "Copied '$1' from container to '${GOSS_FILES_PATH}'"
docker cp "$service:$1" "${GOSS_FILES_PATH}"
fi
set -e
}
# Main
tmp_dir=$(mktemp -d /tmp/tmp.XXXXXXXXXX)
chmod 777 "$tmp_dir"
# shellcheck disable=SC2154
trap 'ret=$?; cleanup; info "Test ran for a total of $SECONDS seconds"; exit $ret' EXIT
service="$2"
if [[ ! -f docker-compose.yaml && ! -f docker-compose.yml ]]; then
echo "no docker-compose file found in ."
exit 1
fi
state=$(docker inspect --format '{{.State.Status}}' "$service" 2> /dev/null || true)
if [[ "$state" == running ]]; then
docker rm -f "$service" > /dev/null
elif [[ "$state" == exited ]]; then
docker rm "$service" > /dev/null
fi
GOSS_PATH="${GOSS_PATH:-$(command -v goss 2> /dev/null || true)}"
[[ "$GOSS_PATH" ]] || { error "Couldn't find goss installation, please set GOSS_PATH to it"; }
[[ "${GOSS_OPTS+x}" ]] || GOSS_OPTS="--color --format documentation"
[[ "${GOSS_WAIT_OPTS+x}" ]] || GOSS_WAIT_OPTS="-r 30s -s 1s > /dev/null"
GOSS_SLEEP="${GOSS_SLEEP:-0.2}"
case "$1" in
run)
run "$@"
[[ "$GOSS_SLEEP" ]] && { info "Sleeping for $GOSS_SLEEP"; sleep "$GOSS_SLEEP"; }
if [[ -e "${GOSS_FILES_PATH}/goss_wait.yaml" ]]; then
info "Found goss_wait.yaml, waiting for it to pass before running tests"
if [[ -z "${GOSS_VARS}" ]]; then
if ! docker exec "$service" sh -c "/goss/goss -g /goss/goss_wait.yaml render | /goss/goss -g - validate $GOSS_WAIT_OPTS"; then
error "goss_wait.yaml never passed"
fi
else
if ! docker exec "$service" sh -c "/goss/goss -g /goss/goss_wait.yaml --vars='/goss/${GOSS_VARS}' render | /goss/goss -g - validate $GOSS_WAIT_OPTS"; then
error "goss_wait.yaml never passed"
fi
fi
fi
info "Container health"
if [ "true" != "$(docker inspect -f '{{.State.Running}}' $service)" ]; then
error "The container failed to start"
fi
info "Running Tests"
if [[ -z "${GOSS_VARS}" ]]; then
docker exec "$service" sh -c "/goss/goss -g /goss/${GOSS_FILE:-goss.yaml} render | /goss/goss -g - validate $GOSS_OPTS"
else
docker exec "$service" sh -c "/goss/goss -g /goss/${GOSS_FILE:-goss.yaml} --vars='/goss/${GOSS_VARS}' render | /goss/goss -g - validate $GOSS_OPTS"
fi
;;
edit)
run "$@"
info "Run goss add/autoadd to add resources"
docker exec -it "$service" sh -c 'cd /goss; PATH="/goss:$PATH" exec sh'
[[ -n "${GOSS_FILE}" ]] && get_docker_file "/goss/${GOSS_FILE}"
get_docker_file "/goss/goss.yaml"
get_docker_file "/goss/goss_wait.yaml"
[[ -n "${GOSS_VARS}" ]] && get_docker_file "/goss/${GOSS_VARS}"
;;
*)
error "$USAGE"
esac
goss-0.4.9/extras/dcgoss/docker-compose.yml 0000664 0000000 0000000 00000001117 14675050513 0020732 0 ustar 00root root 0000000 0000000 version: '3.3'
services:
db:
image: mysql:5.7
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
wordpress:
depends_on:
- db
image: wordpress:latest
ports:
- "8000:80"
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
db_data: {}
goss-0.4.9/extras/dgoss/ 0000775 0000000 0000000 00000000000 14675050513 0015132 5 ustar 00root root 0000000 0000000 goss-0.4.9/extras/dgoss/README.md 0000664 0000000 0000000 00000012466 14675050513 0016422 0 ustar 00root root 0000000 0000000 # dgoss
dgoss is a convenience wrapper around goss that aims to bring the simplicity of goss to containers.
## Examples and Tutorials
* [blog tutorial](https://medium.com/@aelsabbahy/tutorial-how-to-test-your-docker-image-in-half-a-second-bbd13e06a4a9) -
Introduction to dgoss tutorial
* [video tutorial](https://youtu.be/PEHz5EnZ-FM) - Same as above, but in video format
* [dgoss-examples](https://github.com/aelsabbahy/dgoss-examples) - Repo containing examples of using dgoss to validate
container images
## Installation
### Linux
Follow the goss [installation instructions](https://github.com/goss-org/goss#installation)
### Mac OSX
Since goss runs on the target container, dgoss can be used on a Mac OSX system by doing the following:
```shell
# Install dgoss
curl -L https://raw.githubusercontent.com/goss-org/goss/master/extras/dgoss/dgoss -o /usr/local/bin/dgoss
chmod +rx /usr/local/bin/dgoss
# Download desired goss version to your preferred location (e.g. v0.4.8)
curl -L https://github.com/goss-org/goss/releases/download/v0.4.8/goss-linux-amd64 -o ~/Downloads/goss-linux-amd64
# Set your GOSS_PATH to the above location
export GOSS_PATH=~/Downloads/goss-linux-amd64
# Set DGOSS_TEMP_DIR to the tmp directory in your home, since /tmp is private on Mac OSX
export DGOSS_TEMP_DIR=~/tmp
# Use dgoss
dgoss edit ...
dgoss run ...
```
## Usage
`dgoss [run|edit] `
### Run
Run is used to validate a container.
It expects a `./goss.yaml` file to exist in the directory it was invoked from.
In most cases one can just substitute the runtime command (`docker` or `podman`)
for the dgoss command, for example:
**run:**
`docker run -e JENKINS_OPTS="--httpPort=8080 --httpsPort=-1" -e JAVA_OPTS="-Xmx1048m" jenkins:alpine`
**test:**
`dgoss run -e JENKINS_OPTS="--httpPort=8080 --httpsPort=-1" -e JAVA_OPTS="-Xmx1048m" jenkins:alpine`
`dgoss run` will do the following:
* Run the container with the flags you specified.
* Stream the containers log output into the container as `/goss/docker_output.log`
* This allows writing tests or waits against the container output
* (optional) Run `goss` with `$GOSS_WAIT_OPTS` if `./goss_wait.yaml` file exists in the current dir
* Run `goss` with `$GOSS_OPTS` using `./goss.yaml`
### Edit
Edit will launch a container, install goss, and drop the user into an interactive shell.
Once the user quits the interactive shell, any `goss.yaml` or `goss_wait.yaml` are copied out into the current directory.
This allows the user to leverage the `goss add|autoadd` commands to write tests as they would on a regular machine.
**Example:**
`dgoss edit -e JENKINS_OPTS="--httpPort=8080 --httpsPort=-1" -e JAVA_OPTS="-Xmx1048m" jenkins:alpine`
### Environment vars and defaults
The following environment variables can be set to change the behavior of dgoss.
#### GOSS_PATH
Location of the goss binary to use. (Default: `$(which goss)`)
#### GOSS_FILE
Name of the goss file to use. (Default: `goss.yaml`)
#### GOSS_OPTS
Options to use for the goss test run. (Default: `--color --format documentation`)
#### GOSS_WAIT_OPTS
Options to use for the goss wait run, when `./goss_wait.yaml` exists. (Default: `-r 30s -s 1s > /dev/null`)
#### GOSS_SLEEP
Time to sleep after running container (and optionally `goss_wait.yaml`) and before running tests. (Default: `0.2`)
#### GOSS_FILES_PATH
Location of the goss yaml files. (Default: `.`)
#### GOSS_ADDITIONAL_COPY_PATH
Colon-seperated list of additional directories to copy to container.
By default dgoss copies `goss.yaml` from the current working directory and
nothing else. You may need other files like scripts and configurations copied
as well. Specify `GOSS_ADDITIONAL_COPY_PATH` similar to `$PATH` as colon seperated
list of directories for each additional directory you'd like to recursively copy.
These will be copied as directories next to `goss.yaml` in the temporary
directory `DGOSS_TEMP_DIR`. (Default: `''`)
#### GOSS_VARS
The name of the variables file relative to `GOSS_FILES_PATH` to copy into the
container and use for valiation (i.e. `dgoss run`) and copy out of the
container when writing tests (i.e. `dgoss edit`). If set, the
`--vars` flag is passed to `goss validate` commands inside the container.
If unset (or empty), the `--vars` flag is omitted, which is the normal behavior.
(Default: `''`).
#### GOSS_FILES_STRATEGY
Strategy used for copying goss files into the container. If set to `'mount'` a volume with goss files is mounted
and log output is streamed into the container as `/goss/docker_output.log` file. Other strategy is `'cp'` which uses
`'docker cp'` command to copy goss files into container. With the `'cp'` strategy you lose the ability to write
tests or waits against the container output. The `'cp'` strategy is required especially when container daemon is not on the
local machine.
(Default `'mount'`)
#### CONTAINER_LOG_OUTPUT
Location of the file that contains tested container logs. Logs are retained only if the variable is set to a non-empty
string. (Default `''`)
#### DGOSS_TEMP_DIR
Location of the temporary directory used by dgoss. (Default `'$(mktemp -d /tmp/tmp.XXXXXXXXXX)'`)
#### CONTAINER_RUNTIME
Container runtime to use - `docker` or `podman`. Defaults to `docker`. Note that `podman` requires a run command to keep
the container running. This defaults to `sleep infinity` in case only an image is passed to `dgoss` commands.
goss-0.4.9/extras/dgoss/dgoss 0000775 0000000 0000000 00000012433 14675050513 0016202 0 ustar 00root root 0000000 0000000 #!/bin/bash
set -e
USAGE="USAGE: $(basename "$0") [run|edit] "
GOSS_FILES_PATH="${GOSS_FILES_PATH:-.}"
# Container runtime
CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-docker}"
info() {
echo -e "INFO: $*" >&2;
}
error() {
echo -e "ERROR: $*" >&2;
exit 1;
}
cleanup() {
set +e
{ kill "$log_pid" && wait "$log_pid"; } 2> /dev/null
if [ -n "$CONTAINER_LOG_OUTPUT" ]; then
cp "$tmp_dir/docker_output.log" "$CONTAINER_LOG_OUTPUT"
fi
rm -rf "$tmp_dir"
if [[ $id ]];then
info "Deleting container"
$CONTAINER_RUNTIME rm -vf "$id" > /dev/null
fi
}
run(){
# Copy in goss
cp "${GOSS_PATH}" "$tmp_dir/goss"
chmod 755 "$tmp_dir/goss"
[[ -e "${GOSS_FILES_PATH}/${GOSS_FILE:-goss.yaml}" ]] && cp "${GOSS_FILES_PATH}/${GOSS_FILE:-goss.yaml}" "$tmp_dir/goss.yaml" && chmod 644 "$tmp_dir/goss.yaml"
[[ -e "${GOSS_FILES_PATH}/goss_wait.yaml" ]] && cp "${GOSS_FILES_PATH}/goss_wait.yaml" "$tmp_dir" && chmod 644 "$tmp_dir/goss_wait.yaml"
[[ -n "${GOSS_VARS}" ]] && [[ -e "${GOSS_FILES_PATH}/${GOSS_VARS}" ]] && cp "${GOSS_FILES_PATH}/${GOSS_VARS}" "$tmp_dir" && chmod 644 "$tmp_dir/${GOSS_VARS}"
if [ -n "$GOSS_ADDITIONAL_COPY_PATH" ]; then
for dir in "$(echo "$GOSS_ADDITIONAL_COPY_PATH" | sed 's/:/ /g')"; do
cp -r ${dir} "${tmp_dir}/"
chmod -R 755 "$tmp_dir/$(basename ${dir})"
done
fi
# Switch between mount or cp files strategy
GOSS_FILES_STRATEGY=${GOSS_FILES_STRATEGY:="mount"}
case "$GOSS_FILES_STRATEGY" in
mount)
info "Starting $CONTAINER_RUNTIME container"
if [ "$CONTAINER_RUNTIME" == "podman" -a $# == 2 ]; then
id=$($CONTAINER_RUNTIME run -d -v "$tmp_dir:/goss:z" "${@:2}" sleep infinity)
else
id=$($CONTAINER_RUNTIME run -d -v "$tmp_dir:/goss:z" "${@:2}")
fi
;;
cp)
info "Creating $CONTAINER_RUNTIME container"
id=$($CONTAINER_RUNTIME create "${@:2}")
info "Copy goss files into container"
$CONTAINER_RUNTIME cp "$tmp_dir/." "$id:/goss"
info "Starting $CONTAINER_RUNTIME container"
$CONTAINER_RUNTIME start "$id" > /dev/null
;;
*) error "Wrong goss files strategy used! Correct options are \"mount\" or \"cp\"."
esac
$CONTAINER_RUNTIME logs -f "$id" > "$tmp_dir/docker_output.log" 2>&1 &
log_pid=$!
info "Container ID: ${id:0:8}"
}
get_docker_file() {
local cid=$1 # Docker container ID
local src=$2 # Source file path (in the container)
local dst=$3 # Destination file path
if $CONTAINER_RUNTIME exec "${cid}" sh -c "test -e ${src}" > /dev/null; then
mkdir -p "${GOSS_FILES_PATH}"
$CONTAINER_RUNTIME cp "${cid}:${src}" "${dst}"
info "Copied '${src}' from container to '${dst}'"
fi
}
# Main
tmp_dir=$(mktemp -d ${DGOSS_TEMP_DIR:-/tmp}/tmp.XXXXXXXXXX)
chmod 777 "$tmp_dir"
trap 'ret=$?;cleanup;exit $ret' EXIT
GOSS_PATH="${GOSS_PATH:-$(which goss 2> /dev/null || true)}"
[[ $GOSS_PATH ]] || { error "Couldn't find goss installation, please set GOSS_PATH to it"; }
[[ ${GOSS_OPTS+x} ]] || GOSS_OPTS="--color --format documentation"
[[ ${GOSS_WAIT_OPTS+x} ]] || GOSS_WAIT_OPTS="-r 30s -s 1s > /dev/null"
GOSS_SLEEP=${GOSS_SLEEP:-0.2}
[[ $CONTAINER_RUNTIME =~ ^(docker|podman)$ ]] || { error "Runtime must be one of docker or podman"; }
case "$1" in
run)
run "$@"
if [[ -e "${GOSS_FILES_PATH}/goss_wait.yaml" ]]; then
info "Found goss_wait.yaml, waiting for it to pass before running tests"
if [[ -z "${GOSS_VARS}" ]]; then
if ! $CONTAINER_RUNTIME exec "$id" sh -c "/goss/goss -g /goss/goss_wait.yaml validate $GOSS_WAIT_OPTS"; then
$CONTAINER_RUNTIME logs "$id" >&2
error "goss_wait.yaml never passed"
fi
else
if ! $CONTAINER_RUNTIME exec "$id" sh -c "/goss/goss -g /goss/goss_wait.yaml --vars='/goss/${GOSS_VARS}' validate $GOSS_WAIT_OPTS"; then
$CONTAINER_RUNTIME logs "$id" >&2
error "goss_wait.yaml never passed"
fi
fi
fi
[[ $GOSS_SLEEP ]] && { info "Sleeping for $GOSS_SLEEP"; sleep "$GOSS_SLEEP"; }
info "Container health"
if [ "true" != "$($CONTAINER_RUNTIME inspect -f '{{.State.Running}}' "$id")" ]; then
$CONTAINER_RUNTIME logs "$id" >&2
error "the container failed to start"
fi
info "Running Tests"
if [[ -z "${GOSS_VARS}" ]]; then
$CONTAINER_RUNTIME exec "$id" sh -c "/goss/goss -g /goss/goss.yaml validate $GOSS_OPTS"
else
$CONTAINER_RUNTIME exec "$id" sh -c "/goss/goss -g /goss/goss.yaml --vars='/goss/${GOSS_VARS}' validate $GOSS_OPTS"
fi
;;
edit)
run "$@"
info "Run goss add/autoadd to add resources"
$CONTAINER_RUNTIME exec -it "$id" sh -c 'cd /goss; PATH="/goss:$PATH" exec sh'
get_docker_file "$id" "/goss/goss.yaml" "${GOSS_FILES_PATH}/${GOSS_FILE:-goss.yaml}"
get_docker_file "$id" "/goss/goss_wait.yaml" "${GOSS_FILES_PATH}/goss_wait.yaml"
if [[ -n "${GOSS_VARS}" ]]; then
get_docker_file "$id" "/goss/${GOSS_VARS}" "${GOSS_FILES_PATH}/${GOSS_VARS}"
fi
;;
*)
error "$USAGE"
esac
goss-0.4.9/extras/kgoss/ 0000775 0000000 0000000 00000000000 14675050513 0015141 5 ustar 00root root 0000000 0000000 goss-0.4.9/extras/kgoss/README.md 0000664 0000000 0000000 00000012613 14675050513 0016423 0 ustar 00root root 0000000 0000000 # kgoss
kgoss is a wrapper for goss that aims to bring the simplicity of testing
with goss to containers running in pods in Kubernetes.
kgoss is a script which when invoked copies and runs goss (the binary) within a
Linux container. goss itself is only supported on Linux, but since it need only
run in the target container, the kgoss script can be used from any
bash-compatible shell, including Terminal on Mac and git-bash on Windows. On
Windows, [winpty][] is used for interactive connections to the pod under test.
[winpty]: https://github.com/rprichard/winpty
## Install
Installing kgoss requires copying the kgoss file to a directory in your PATH
and copying the goss file to your home folder (or a path set as `GOSS_PATH`),
as follows.
### Manual / UI
You can manually install kgoss and goss by going through the Web UI, getting
the files and putting them in the right path. To get each of them:
* **kgoss**: Run `curl -sSLO
https://raw.githubusercontent.com/goss-org/goss/master/extras/kgoss/kgoss`.
* **goss**: Download the `goss-linux-amd64` asset from
and rename it `goss`. Place it
in your HOME directory, e.g. `C:\Users\` on Windows; or set the
environment variable `GOSS_PATH` to its path.
### Automatic / CLI
To install from the command line or automatically, use the following commands.
[jq][] is required to parse the API response and find the release asset's
download URL.
[jq]: https://stedolan.github.io/jq
First get a GitHub personal access token for accessing the GitHub API from
. Input it in the first
line below. Set `dest_dir` to a directory in your `PATH` env var.
```shell
token=
username=$(whoami)
dest_dir=${HOME}/bin
host=raw.githubusercontent.com
repo=goss-org/goss
# for private repos, replace:
# host=github.yourcompany.com
# repo=org-name/goss
## install kgoss
curl -sSL -u "${username}:${token}" -H 'Accept: application/vnd.github.v3.raw' -o "${dest_dir}/kgoss" \
https://${host}/api/v3/repos/${repo}/contents/extras/kgoss/kgoss
chmod a+rx "${dest_dir}/kgoss"
## install goss
if [[ ! $(which jq) ]]; then echo "jq is required, get from https://stedolan.github.io/jq"; fi
version=v0.4.8
arch=amd64
host=github.com
# for private repos, leave `host` blank or same as above:
# host=github.yourcompany.com
dl_url=$(curl -sSL -u "${username}:${token}" https://${host}/api/v3/repos/${repo}/releases \
| jq -r ".[] | select (.name == \"${version}\") | .assets[] | select (.name == \"goss-linux-${arch}\") | .url")
curl -sSL -u "${username}:${token}" -H 'Accept: application/octet-stream' -o "${dest_dir}/goss" $dl_url
chmod a+rx "${dest_dir}/goss"
# If `goss` is not in your path, export a GOSS_PATH variable:
export GOSS_PATH=${dest_dir}/goss
# Now you can use kgoss as described below:
# kgoss edit ...
# kgoss run ...
```
## Use
`kgoss [run|edit] -i [-p | -c "command to run" | -a "args to pass"] [-d "directory to include"]* [-e "k=v"]*`
If none of `-p|-c|-a` are specified the container is run with its configured entry point.
`-d` and `-e` can be specified multiple (or zero) times to add additional
directories and env vars.
By default kgoss copies `goss.yaml` from the current working directory and
nothing else. You may need other files like scripts and configurations copied
as well. Specify `-d ` for each additional directory you'd like
to recursively copy. These will be copied as directories next to `goss.yaml`
in the target container's `GOSS_CONTAINER_PATH`.
To find `goss.yaml` in another directory specify that directory's path in `GOSS_FILES_PATH`.
### Run
The `run` command is used to validate a container. It expects a
`./goss.yaml` file to exist in the directory it was invoked from.
**Example:**
`kgoss run -e JENKINS_OPTS="--httpPort=8080 --httpsPort=-1" -e JAVA_OPTS="-Xmx1048m" -i jenkins:alpine`
`kgoss run` will do the following:
* Run the container with the start commands specified by `-c`, `-a`, or `-p`.
* Run `goss` with `$GOSS_WAIT_OPTS` if `./goss_wait.yaml` file exists in the current dir.
* Run `goss` with `$GOSS_OPTS` using `./goss.yaml` from `GOSS_FILES_PATH`.
### Edit
Edit will launch a container, install goss, and drop the user into an
interactive shell. Once the user quits the interactive shell, any `goss.yaml`
or `goss_wait.yaml` are copied out into the current directory. This allows the
user to leverage the `goss add|autoadd` commands to write tests as they would
on a regular machine.
**Example:**
`kgoss edit -e JENKINS_OPTS="--httpPort=8080 --httpsPort=-1" -e JAVA_OPTS="-Xmx1048m" -i jenkins:alpine`
## Environment variables
The following environment variables effect the behavior of kgoss.
Variable | Description | Default
---------|-------------|--------
GOSS\_PATH | Local location of a compatible goss binary to use in container | `$(which goss)`
GOSS\_FILES\_PATH | Location of the goss yaml files | `.`
GOSS\_KUBECTL\_BIN | Kubenetes client tool to use | `$(which kubectl)`
GOSS\_KUBECTL\_OPTS | Options to inject more options such as "--namespace=default" | ""
GOSS\_OPTS | Options to use for the goss test run. | `--color --format documentation`
GOSS\_WAIT\_OPTS | Options to use for the goss wait run, when `./goss_wait.yaml` exists. | `-r 30s -s 1s > /dev/null`
GOSS\_VARS | Variables file relative to `GOSS_FILES_PATH` to copy and use | ""
GOSS\_CONTAINER\_PATH | Path within container to put goss binary and YAML files | `/tmp/goss`
goss-0.4.9/extras/kgoss/kgoss 0000775 0000000 0000000 00000022024 14675050513 0016215 0 ustar 00root root 0000000 0000000 #! /usr/bin/env bash
set -eo pipefail
info() {
echo -e "[INFO]: $*" >&2
}
error() {
echo -e "[ERROR]: $*" >&2
exit 1
}
usage() {
>&2 cat <<-'EOF'
Usage: $(basename $0) [command] [options]
## Commands:
* `run` executes goss in the pod/container with ./goss.yaml as input (by
default).
* `edit` opens a prompt inside the container to run `goss add ...`
and copies out files when complete.
## Options:
-i="image_url:tag" - full URL of container image
-d="additional directories to copy to container" - may be specified zero to
many times
-e="envvar_key=value" - may be specified zero to many times
-p - (flag) pause container on entry
-c="cmd to run" - command to execute as container entry point
-a="args to entrypoint"
If -p, -c and -a are not specified, container will run its ENTRYPOINT.
-e and -d can be specified multiple times.
## Environment variables and default values:
GOSS_KUBECTL_BIN="$(which kubectl)": location of kubectl-compatible binary
GOSS_KUBECTL_OPTS="": hook to inject more options such as "--namespace=default"
GOSS_PATH="$(which goss)": location of goss binary
GOSS_FILES_PATH=".": location of goss.yaml and other configuration files
GOSS_VARS="": path to a goss.vars file
GOSS_OPTS="--color --format documentation": options passed to goss
GOSS_WAIT_OPTS="-r 30s -s 1s > /dev/null": options passed to goss
GOSS_CONTAINER_PATH="/tmp/goss": path to copy files in container, and working dir for tests
EOF
exit 2
}
# GOSS_PATH
if [[ -z "${GOSS_PATH}" ]]; then
if [[ $(which goss 2> /dev/null) ]]; then
GOSS_PATH=$(which goss 2> /dev/null)
elif [[ -e "${HOME}/goss" ]]; then
GOSS_PATH="${HOME}/goss"
elif [[ -e "${HOME}/bin/goss" ]]; then
GOSS_PATH="${HOME}/bin/goss"
else
error "Couldn't find goss, please set GOSS_PATH to it"
fi
fi
# GOSS_KUBECTL_BIN
GOSS_KUBECTL_BIN=${GOSS_KUBECTL_BIN:-$(which kubectl 2> /dev/null || true)}
if [[ -z "$GOSS_KUBECTL_BIN" ]]; then error "kgoss requires kubectl in your PATH"; fi
k=${GOSS_KUBECTL_BIN}
GOSS_FILES_PATH="${GOSS_FILES_PATH:-.}"
GOSS_OPTS=${GOSS_OPTS:-"--color --format documentation"}
GOSS_WAIT_OPTS=${GOSS_WAIT_OPTS:-"-r 30s -s 1s > /dev/null"}
GOSS_CONTAINER_PATH=${GOSS_CONTAINER_PATH:-/tmp/goss}
GOSS_KUBECTL_OPTS=${GOSS_KUBECTL_OPTS:-""}
kgoss_cmd=run
image=
pause=0
cmd=''
args=''
to_exec=''
envs=''
include_goss_files_dir=0
dirs_array=()
cleanup() {
set +ex
rm -rf "$tmp_dir"
if [[ -n "$id" ]]; then
info "Deleting pod/container"
${k} delete pod "$id" ${GOSS_KUBECTL_OPTS} > /dev/null
fi
}
# parse checks for a bare `-d` flag and if set includes GOSS_FILES_PATH in dirs
# to upload to pod
parse() {
# handle deprecated bare `-d`
i=0
original_args=("$@")
new_args=()
re='^-'
for arg in "${original_args[@]}"; do
if [[ "${arg}" == '-d' ]]; then
# check if next word starts with '-'
if [[ "${original_args[$(($i+1))]}" =~ $re ]]; then
# since it does, mark to copy whole dir and remove this arg
include_goss_files_dir=1
i=$(($i+1))
continue
fi
fi
i=$(($i+1))
new_args+=("${arg}")
done
# end handle `-d`
# now call original parse_internal func
parse_internal "${new_args[@]}"
}
parse_internal() {
info "Parsing command line"
kgoss_cmd=$1; shift
if [[ ( ! "${kgoss_cmd}" == "run" ) && ( ! "${kgoss_cmd}" == 'edit' ) ]]; then usage; fi
envs_array=()
while getopts 'i:pc::a::d::e::' arg; do
case $arg in
i)
image="${OPTARG}"
info "using image: $image"
;;
p)
pause=1
;;
c)
cmd="${OPTARG}"
;;
a)
args="${OPTARG}"
;;
d)
dirs_array+=("${OPTARG}")
;;
e)
envs_array+=("${OPTARG}")
;;
*)
info "invalid option specified"
usage
;;
esac
done
for envvar in "${envs_array[@]}"; do
envs+=" --env=${envvar}"
done
# if -p (pause) is set, then -c (command) and -a (args) should be empty and
# we inject a pause
if [[ $pause == 1 ]]; then
if [[ ! ( -z "$cmd" && -z "$args" ) ]]; then
error "cannot specify -p and -c or -a"
fi
to_exec="--command -- sleep 1h"
else
# if not -p (pause), then either:
# * one of -c (command) or -a (args) should be set
# * neither should be set and we default to entrypoint
if [[ -n "$cmd" && -n "$args" ]]; then
error "cannot specify both -c and -a"
fi
if [[ -n "$cmd" ]]; then
to_exec="--command -- $cmd"
fi
if [[ -n $"args" ]]; then
to_exec="-- $args"
fi
fi
info "going to execute (may be blank): ${to_exec}"
}
# initialize starts the pod to be tested and copies goss files into it
initialize () {
info "Preparing files to copy into container"
cp "${GOSS_PATH}" "$tmp_dir/goss" && chmod 0775 "$tmp_dir/goss"
[[ -e "${GOSS_FILES_PATH}/goss.yaml" ]] && cp "${GOSS_FILES_PATH}/goss.yaml" "$tmp_dir"
[[ -e "${GOSS_FILES_PATH}/goss_wait.yaml" ]] && cp "${GOSS_FILES_PATH}/goss_wait.yaml" "$tmp_dir"
[[ ! -z "${GOSS_VARS}" ]] && [[ -e "${GOSS_FILES_PATH}/${GOSS_VARS}" ]] && cp "${GOSS_FILES_PATH}/${GOSS_VARS}" "$tmp_dir"
if [[ ${include_goss_files_dir} == 1 ]]; then cp -r ${GOSS_FILES_PATH}/* "${tmp_dir}"; fi
for dir in "${dirs_array[@]}"; do
cp -r ${dir} "${tmp_dir}/"
done
GOSS_FILES_STRATEGY=${GOSS_FILES_STRATEGY:="cp"}
case "$GOSS_FILES_STRATEGY" in
cp)
info "Creating Kubernetes pod/container to test"
test_pod_name=kgoss-tester-${RANDOM}
set -x
id=$(${k} run ${GOSS_KUBECTL_OPTS} $test_pod_name --image-pull-policy=Always --restart=Never \
--labels='app=kgoss-test' --output=jsonpath={.metadata.name} ${envs} \
--image=${image} ${to_exec} )
set +x
info "Waiting for container to be ready"
${k} wait pod/${test_pod_name} --for=condition=Ready --timeout=60s ${GOSS_KUBECTL_OPTS}
info "Copying goss files into pod/container"
${k} cp ${GOSS_KUBECTL_OPTS} $tmp_dir/. ${id}:${GOSS_CONTAINER_PATH}/
info "Marking copied files as executable"
${k} exec ${GOSS_KUBECTL_OPTS} "$id" -- sh -c "chmod -R a+x ${GOSS_CONTAINER_PATH}/"
;;
*) error "Wrong kgoss files strategy used! Only \"cp\" is supported."
esac
info "Using pod/container: ${id}"
}
# get_pod_file copies the specified file from the pod to a local path
get_pod_file() {
if ${k} exec ${GOSS_KUBECTL_OPTS} "$id" -- sh -c "test -e ${GOSS_CONTAINER_PATH}/$1" &> /dev/null; then
mkdir -p "${GOSS_FILES_PATH}"
info "Copied '$1' from pod/container to '${GOSS_FILES_PATH}'"
${k} cp ${GOSS_KUBECTL_OPTS} "${id}:${GOSS_CONTAINER_PATH}/$1" "${GOSS_FILES_PATH}/$1"
fi
}
main() {
kernel="$(uname -s)"
case "${kernel}" in
MINGW*) prefix="winpty" ;;
*) prefix="" ;;
esac
tmp_dir=$(mktemp -d /tmp/tmp.XXXXXXXXXX)
chmod 777 "$tmp_dir"
trap 'ret=$?; cleanup; exit $ret' EXIT
parse "$@"
initialize
# execute
case $kgoss_cmd in
run)
# wait for goss_wait.yaml if present
if [[ -e "${GOSS_FILES_PATH}/goss_wait.yaml" ]]; then
info "Found goss_wait.yaml, waiting for it to pass before running tests"
if [[ -z "${GOSS_VARS}" ]]; then
if ! ${k} exec ${GOSS_KUBECTL_OPTS} "$id" -- sh -c "${GOSS_CONTAINER_PATH}/goss -g ${GOSS_CONTAINER_PATH}/goss_wait.yaml validate $GOSS_WAIT_OPTS" ; then
error "goss_wait.yaml never passed"
fi
else
if ! ${k} exec ${GOSS_KUBECTL_OPTS} "$id" -- sh -c "${GOSS_CONTAINER_PATH}/goss -g ${GOSS_CONTAINER_PATH}/goss_wait.yaml --vars='${GOSS_CONTAINER_PATH}/${GOSS_VARS}' validate $GOSS_WAIT_OPTS" ; then
error "goss_wait.yaml never passed"
fi
fi
fi
# running tests in pod/container
info "Running tests within pod/container"
if [[ -z "${GOSS_VARS}" ]]; then
${k} exec ${GOSS_KUBECTL_OPTS} "$id" -- sh -c "cd ${GOSS_CONTAINER_PATH}; ${GOSS_CONTAINER_PATH}/goss -g ${GOSS_CONTAINER_PATH}/goss.yaml validate $GOSS_OPTS"
else
${k} exec ${GOSS_KUBECTL_OPTS} "$id" -- sh -c "cd ${GOSS_CONTAINER_PATH}; ${GOSS_CONTAINER_PATH}/goss -g ${GOSS_CONTAINER_PATH}/goss.yaml --vars='${GOSS_CONTAINER_PATH}/${GOSS_VARS}' validate $GOSS_OPTS"
fi
;;
edit)
info "When prompt appears you can run \`goss add\` to add resources"
${prefix} ${k} exec ${GOSS_KUBECTL_OPTS} -it "$id" -- sh -c "cd ${GOSS_CONTAINER_PATH}; PATH=\"${GOSS_CONTAINER_PATH}:$PATH\" exec sh" || true
echo "Copying goss.yaml and goss_wait.yaml files back to local dir"
get_pod_file "goss.yaml"
get_pod_file "goss_wait.yaml"
[[ ! -z "${GOSS_VARS}" ]] && get_pod_file "${GOSS_VARS}"
;;
*)
echo "invalid kgoss command, valid commands are 'run' and 'edit'"
usage
;;
esac
}
main "$@"
goss-0.4.9/go.mod 0000664 0000000 0000000 00000004454 14675050513 0013622 0 ustar 00root root 0000000 0000000 module github.com/goss-org/goss
go 1.22
require (
github.com/Masterminds/sprig/v3 v3.3.0
github.com/achanda/go-sysctl v0.0.0-20160222034550-6be7678c45d2
github.com/blang/semver/v4 v4.0.0
github.com/cheekybits/genny v1.0.0
github.com/fatih/color v1.17.0
github.com/goss-org/GOnetstat v0.0.0-20230101144325-22be0bd9e64d
github.com/goss-org/go-ps v0.0.0-20230609005227-7b318e6a56e5
github.com/hashicorp/logutils v1.0.0
github.com/miekg/dns v1.1.61
github.com/moby/sys/mountinfo v0.7.1
github.com/oleiade/reflections v1.0.1
github.com/onsi/gomega v1.33.1
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pmezard/go-difflib v1.0.0
github.com/prometheus/client_golang v1.19.1
github.com/prometheus/common v0.55.0
github.com/samber/lo v1.46.0
github.com/stretchr/testify v1.9.0
github.com/tidwall/gjson v1.17.1
github.com/urfave/cli v1.22.14
gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.5.1
)
require (
dario.cat/mergo v1.0.1 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.23.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
)
goss-0.4.9/go.sum 0000664 0000000 0000000 00000030132 14675050513 0013637 0 ustar 00root root 0000000 0000000 dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/achanda/go-sysctl v0.0.0-20160222034550-6be7678c45d2 h1:NYoPVh1XuUB5VBWLXRKoqzQhl4bajIxh+XuURbJ0uwc=
github.com/achanda/go-sysctl v0.0.0-20160222034550-6be7678c45d2/go.mod h1:DCNKSpXhum14Y258jSbRmJvcesbzEdBPincz7yJUx3k=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/goss-org/GOnetstat v0.0.0-20230101144325-22be0bd9e64d h1:50mlZKtg8BUvBtFs0ioVpSgMMwcKaJefg/2pZ+lQf98=
github.com/goss-org/GOnetstat v0.0.0-20230101144325-22be0bd9e64d/go.mod h1:MBdRlloGIbpQVDuH5Gxg3hjqwZBCZsmFqbYPaeR6r0M=
github.com/goss-org/go-ps v0.0.0-20230609005227-7b318e6a56e5 h1:NW0Jo4leMIrQxNOyOkBu4yBnygI37m0Ey0EUUgvzr+8=
github.com/goss-org/go-ps v0.0.0-20230609005227-7b318e6a56e5/go.mod h1:FYj70SLmogHdTTDGnIVaaK0iczROlsxmoMCwfAUuIE8=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g=
github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM=
github.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60=
github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g=
github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ=
github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
goss-0.4.9/goss_config.go 0000664 0000000 0000000 00000011330 14675050513 0015332 0 ustar 00root root 0000000 0000000 package goss
import (
"log"
"reflect"
"github.com/goss-org/goss/resource"
)
type GossConfig struct {
Files resource.FileMap `json:"file,omitempty" yaml:"file,omitempty"`
Packages resource.PackageMap `json:"package,omitempty" yaml:"package,omitempty"`
Addrs resource.AddrMap `json:"addr,omitempty" yaml:"addr,omitempty"`
Ports resource.PortMap `json:"port,omitempty" yaml:"port,omitempty"`
Services resource.ServiceMap `json:"service,omitempty" yaml:"service,omitempty"`
Users resource.UserMap `json:"user,omitempty" yaml:"user,omitempty"`
Groups resource.GroupMap `json:"group,omitempty" yaml:"group,omitempty"`
Commands resource.CommandMap `json:"command,omitempty" yaml:"command,omitempty"`
DNS resource.DNSMap `json:"dns,omitempty" yaml:"dns,omitempty"`
Processes resource.ProcessMap `json:"process,omitempty" yaml:"process,omitempty"`
Gossfiles resource.GossfileMap `json:"gossfile,omitempty" yaml:"gossfile,omitempty"`
KernelParams resource.KernelParamMap `json:"kernel-param,omitempty" yaml:"kernel-param,omitempty"`
Mounts resource.MountMap `json:"mount,omitempty" yaml:"mount,omitempty"`
Interfaces resource.InterfaceMap `json:"interface,omitempty" yaml:"interface,omitempty"`
HTTPs resource.HTTPMap `json:"http,omitempty" yaml:"http,omitempty"`
Matchings resource.MatchingMap `json:"matching,omitempty" yaml:"matching,omitempty"`
}
func NewGossConfig() *GossConfig {
return &GossConfig{
Files: make(resource.FileMap),
Packages: make(resource.PackageMap),
Addrs: make(resource.AddrMap),
Ports: make(resource.PortMap),
Services: make(resource.ServiceMap),
Users: make(resource.UserMap),
Groups: make(resource.GroupMap),
Commands: make(resource.CommandMap),
DNS: make(resource.DNSMap),
Processes: make(resource.ProcessMap),
Gossfiles: make(resource.GossfileMap),
KernelParams: make(resource.KernelParamMap),
Mounts: make(resource.MountMap),
Interfaces: make(resource.InterfaceMap),
HTTPs: make(resource.HTTPMap),
Matchings: make(resource.MatchingMap),
}
}
// Merge consumes all the resources in g2 into c, duplicate resources
// will be overwritten with the ones in g2
func (c *GossConfig) Merge(g2 GossConfig) {
for k, v := range g2.Files {
mergeType(c.Files, "file", k, v)
}
for k, v := range g2.Packages {
mergeType(c.Packages, "package", k, v)
}
for k, v := range g2.Addrs {
mergeType(c.Addrs, "addr", k, v)
}
for k, v := range g2.Ports {
mergeType(c.Ports, "port", k, v)
}
for k, v := range g2.Services {
mergeType(c.Services, "service", k, v)
}
for k, v := range g2.Users {
mergeType(c.Users, "user", k, v)
}
for k, v := range g2.Groups {
mergeType(c.Groups, "group", k, v)
}
for k, v := range g2.Commands {
mergeType(c.Commands, "command", k, v)
}
for k, v := range g2.DNS {
mergeType(c.DNS, "dns", k, v)
}
for k, v := range g2.Processes {
mergeType(c.Processes, "process", k, v)
}
for k, v := range g2.KernelParams {
mergeType(c.KernelParams, "kernel-param", k, v)
}
for k, v := range g2.Mounts {
mergeType(c.Mounts, "mount", k, v)
}
for k, v := range g2.Interfaces {
mergeType(c.Interfaces, "interface", k, v)
}
for k, v := range g2.HTTPs {
mergeType(c.HTTPs, "http", k, v)
}
for k, v := range g2.Matchings {
mergeType(c.Matchings, "matching", k, v)
}
}
func mergeType[V any](m map[string]V, t, k string, v V) {
if _, ok := m[k]; ok {
log.Printf("[WARN] Duplicate key detected: '%s: %s'. The value from a later-loaded goss file has overwritten the previous value.", t, k)
}
m[k] = v
}
func (c *GossConfig) Resources() []resource.Resource {
var tests []resource.Resource
gm := genericConcatMaps(c.Commands,
c.HTTPs,
c.Addrs,
c.DNS,
c.Packages,
c.Services,
c.Files,
c.Processes,
c.Users,
c.Groups,
c.Ports,
c.KernelParams,
c.Mounts,
c.Interfaces,
c.Matchings,
)
for _, m := range gm {
for _, t := range m {
// FIXME: Can this be moved to a safer compile-time check?
tests = append(tests, t.(resource.Resource))
}
}
return tests
}
func genericConcatMaps(maps ...any) (ret []map[string]any) {
for _, slice := range maps {
im := interfaceMap(slice)
ret = append(ret, im)
}
return ret
}
func interfaceMap(slice any) map[string]any {
m := reflect.ValueOf(slice)
if m.Kind() != reflect.Map {
panic("InterfaceSlice() given a non-slice type")
}
ret := make(map[string]any)
for _, k := range m.MapKeys() {
ret[k.Interface().(string)] = m.MapIndex(k).Interface()
}
return ret
}
func mergeGoss(g1, g2 GossConfig) GossConfig {
g1.Gossfiles = nil
g1.Merge(g2)
return g1
}
goss-0.4.9/goss_test.go 0000664 0000000 0000000 00000006640 14675050513 0015054 0 ustar 00root root 0000000 0000000 package goss
import (
"bytes"
"encoding/json"
"os"
"testing"
"github.com/goss-org/goss/outputs"
"github.com/goss-org/goss/resource"
"github.com/goss-org/goss/util"
)
func checkErr(t *testing.T, err error, format string, a ...any) {
t.Helper()
if err == nil {
return
}
t.Fatalf(format+": "+err.Error(), a...)
}
func TestConfigMerge(t *testing.T) {
var g1json = `file:
/etc/passwd:
exists: true
mode: "0644"
size: 1722
owner: root
group: root
filetype: file
contains: []`
var g2json = `service:
sshd:
enabled: true
running: true
`
g1, err := ReadJSONData([]byte(g1json), true)
checkErr(t, err, "reading g1 failed")
_, ok := g1.Services["sshd"]
if ok {
t.Fatalf("did not expect sshd service")
}
g2, err := ReadJSONData([]byte(g2json), true)
checkErr(t, err, "reading g1 failed")
g1.Merge(g2)
_, ok = g1.Files["/etc/passwd"]
if !ok {
t.Fatalf("expected passwd file, got none")
}
_, ok = g1.Services["sshd"]
if !ok {
t.Fatalf("expected sshd service, got none")
}
}
func TestUseAsPackage(t *testing.T) {
output := &bytes.Buffer{}
// temp spec file
fh, err := os.CreateTemp("", "*.yaml")
checkErr(t, err, "temp file failed")
fh.Close()
// new config that doesnt spam output etc
cfg, err := util.NewConfig(util.WithFormatOptions("pretty"), util.WithResultWriter(output), util.WithSpecFile(fh.Name()))
checkErr(t, err, "new config failed")
// adds the os tmp dir to the goss spec file
err = AddResources(fh.Name(), "File", []string{os.TempDir()}, cfg)
checkErr(t, err, "could not add resource %q", os.TempDir())
// validate and sanity check, compare structured vs direct results etc
results, err := ValidateResults(cfg)
checkErr(t, err, "check failed")
found := 0
passed := 0
for rg := range results {
for _, r := range rg {
found++
if r.Result == resource.SUCCESS {
passed++
}
}
}
code, err := Validate(cfg)
checkErr(t, err, "check failed")
if code != 0 {
t.Fatalf("check failed, expected 0 got %d", code)
}
res := &outputs.StructuredOutput{}
err = json.Unmarshal(output.Bytes(), res)
checkErr(t, err, "unmarshal failed")
if res.Summary.Failed != 0 {
t.Fatalf("expected 0 failed, got %d", res.Summary.Failed)
}
if len(res.Results) != found {
t.Fatalf("expected %d results for %d", found, len(res.Results))
}
okcount := 0
for _, r := range res.Results {
if r.Result == resource.SUCCESS {
okcount++
}
}
if okcount != passed {
t.Fatalf("expected %d passed but got %d", passed, okcount)
}
}
func TestSkipResourcesByType(t *testing.T) {
output := &bytes.Buffer{}
// temp spec file
fh, err := os.CreateTemp("", "*.yaml")
checkErr(t, err, "temp file failed")
fh.Close()
// new config that doesnt spam output etc
cfg, err := util.NewConfig(util.WithFormatOptions("pretty"), util.WithResultWriter(output), util.WithSpecFile(fh.Name()), util.WithDisabledResourceTypes("file"))
checkErr(t, err, "new config failed")
// adds the os tmp dir to the goss spec file
err = AddResources(fh.Name(), "File", []string{os.TempDir()}, cfg)
checkErr(t, err, "could not add resource %q", os.TempDir())
// validate and sanity check, compare structured vs direct results etc
results, err := ValidateResults(cfg)
checkErr(t, err, "check failed")
skipped := 0
for rg := range results {
for _, r := range rg {
if r.Skipped {
skipped++
}
}
}
if skipped != 5 {
t.Fatalf("Expected to skip 5 tests, skipped %d", skipped)
}
}
goss-0.4.9/install.sh 0000664 0000000 0000000 00000002631 14675050513 0014511 0 ustar 00root root 0000000 0000000 #!/bin/sh
{
set -e
LATEST_URL="https://github.com/goss-org/goss/releases/latest"
LATEST_EFFECTIVE=$(curl -s -L -o /dev/null ${LATEST_URL} -w '%{url_effective}')
LATEST=${LATEST_EFFECTIVE##*/}
DGOSS_VER=$GOSS_VER
if [ -z "$GOSS_VER" ]; then
GOSS_VER=${GOSS_VER:-$LATEST}
DGOSS_VER='master'
fi
if [ -z "$GOSS_VER" ]; then
echo "ERROR: Could not automatically detect latest version, set GOSS_VER env var and re-run"
exit 1
fi
GOSS_DST=${GOSS_DST:-/usr/local/bin}
INSTALL_LOC="${GOSS_DST%/}/goss"
DGOSS_INSTALL_LOC="${GOSS_DST%/}/dgoss"
touch "$INSTALL_LOC" || { echo "ERROR: Cannot write to $GOSS_DST set GOSS_DST elsewhere or use sudo"; exit 1; }
arch=""
if [ "$(uname -m)" = "x86_64" ]; then
arch="amd64"
elif [ "$(uname -m)" = "aarch32" ]; then
arch="arm"
elif [ "$(uname -m)" = "aarch64" ] || [ "$(uname -m)" = "arm64" ]; then
arch="arm64"
else
arch="386"
fi
url="https://github.com/goss-org/goss/releases/download/$GOSS_VER/goss-linux-$arch"
echo "Downloading $url"
curl -L "$url" -o "$INSTALL_LOC"
chmod +rx "$INSTALL_LOC"
echo "Goss $GOSS_VER has been installed to $INSTALL_LOC"
echo "goss --version"
"$INSTALL_LOC" --version
dgoss_url="https://raw.githubusercontent.com/goss-org/goss/$DGOSS_VER/extras/dgoss/dgoss"
echo "Downloading $dgoss_url"
curl -L "$dgoss_url" -o "$DGOSS_INSTALL_LOC"
chmod +rx "$DGOSS_INSTALL_LOC"
echo "dgoss $DGOSS_VER has been installed to $DGOSS_INSTALL_LOC"
}
goss-0.4.9/integration-tests/ 0000775 0000000 0000000 00000000000 14675050513 0016170 5 ustar 00root root 0000000 0000000 goss-0.4.9/integration-tests/Dockerfile_alpine3 0000664 0000000 0000000 00000000672 14675050513 0021602 0 ustar 00root root 0000000 0000000 FROM alpine:3.19
LABEL org.opencontainers.image.authors="Ahmed"
# install apache2 and remove un-needed services
RUN apk update && \
apk add --no-cache openrc apache2=2.4.59-r0 bash ca-certificates tinyproxy && \
sed -i 's/Listen 80/Listen 0.0.0.0:80/g' /etc/apache2/httpd.conf && \
rc-update add apache2 && \
rc-update add tinyproxy && \
rm -rf /etc/init.d/networking /etc/init.d/hwdrivers /var/cache/apk/* /tmp/*
RUN mkfifo /pipe
goss-0.4.9/integration-tests/Dockerfile_alpine3.md5 0000664 0000000 0000000 00000000065 14675050513 0022262 0 ustar 00root root 0000000 0000000 3c4e7fbf89cd2edfeae94728e247213d Dockerfile_alpine3
goss-0.4.9/integration-tests/Dockerfile_arch 0000664 0000000 0000000 00000000167 14675050513 0021163 0 ustar 00root root 0000000 0000000 FROM archlinux:base
MAINTAINER @siddharthist
RUN ln -s /does_not_exist /foo && \
chmod 700 ~root
RUN mkfifo /pipe
goss-0.4.9/integration-tests/Dockerfile_arch.md5 0000664 0000000 0000000 00000000062 14675050513 0021641 0 ustar 00root root 0000000 0000000 8fc3ce0c000f89ab09488cccb3ba8e66 Dockerfile_arch
goss-0.4.9/integration-tests/Dockerfile_centos7 0000664 0000000 0000000 00000001656 14675050513 0021634 0 ustar 00root root 0000000 0000000 FROM centos:7.2.1511
LABEL org.opencontainers.image.authors="Ahmed"
ENV container docker
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/sys/fs/cgroup" ]
CMD ["/usr/sbin/init"]
RUN yum -y --disablerepo='*' --enablerepo=base,extras install httpd epel-release && yum clean all
RUN yum -y --disablerepo='*' --enablerepo=base,epel install tinyproxy && yum clean all
RUN systemctl enable httpd
RUN systemctl enable tinyproxy
RUN chmod 700 ~root
RUN mkfifo /pipe
goss-0.4.9/integration-tests/Dockerfile_centos7.md5 0000664 0000000 0000000 00000000065 14675050513 0022311 0 ustar 00root root 0000000 0000000 148b069bc0a023068cbcdfe8b24fe036 Dockerfile_centos7
goss-0.4.9/integration-tests/Dockerfile_rockylinux9 0000664 0000000 0000000 00000001651 14675050513 0022545 0 ustar 00root root 0000000 0000000 FROM rockylinux:9
ENV container docker
RUN dnf install -y systemd httpd diffutils 'dnf-command(config-manager)' && \
dnf config-manager --set-enabled crb && \
dnf install -y epel-release && \
dnf install -y tinyproxy && \
dnf remove -y 'dnf-command(config-manager)' epel-release
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
CMD ["/usr/sbin/init"]
RUN systemctl enable httpd
RUN systemctl enable tinyproxy
RUN chmod 700 ~root
RUN mkfifo /pipe
goss-0.4.9/integration-tests/Dockerfile_trusty 0000664 0000000 0000000 00000001056 14675050513 0021616 0 ustar 00root root 0000000 0000000 FROM ubuntu-upstart:trusty
LABEL org.opencontainers.image.authors="Ahmed"
RUN apt-get update && \
apt-get install -y apache2=2.4.7-1ubuntu4.22 tinyproxy && \
apt-get remove -y vim-tiny && \
apt-get clean
RUN sed -i '/reload|force-reload)/i status) pidof tinyproxy > /dev/null && echo "tinyproxy is running";;' /etc/init.d/tinyproxy
RUN sed -i '/start)/a\ touch /var/log/tinyproxy/tinyproxy.log /var/run/tinyproxy/tinyproxy.pid' /etc/init.d/tinyproxy
RUN update-rc.d apache2 defaults
RUN update-rc.d tinyproxy defaults
RUN mkfifo /pipe
goss-0.4.9/integration-tests/Dockerfile_trusty.md5 0000664 0000000 0000000 00000000064 14675050513 0022300 0 ustar 00root root 0000000 0000000 9db0e607ec52f1fd1290785721733180 Dockerfile_trusty
goss-0.4.9/integration-tests/Dockerfile_wheezy 0000664 0000000 0000000 00000001052 14675050513 0021553 0 ustar 00root root 0000000 0000000 FROM debian:wheezy
LABEL org.opencontainers.image.authors="Ahmed"
RUN echo 'deb http://archive.debian.org/debian wheezy main' > /etc/apt/sources.list
RUN echo 'deb http://archive.debian.org/debian-security wheezy/updates main' >> /etc/apt/sources.list
RUN apt-get -o Acquire::Check-Valid-Until=false update && apt-get install --yes --force-yes \
apache2 apache2-doc apache2-utils chkconfig vim-tiny ca-certificates tinyproxy && \
apt-get remove -y vim-tiny && apt-get clean
RUN chkconfig apache2 on
RUN chkconfig tinyproxy on
RUN mkfifo /pipe
goss-0.4.9/integration-tests/Dockerfile_wheezy.md5 0000664 0000000 0000000 00000000064 14675050513 0022241 0 ustar 00root root 0000000 0000000 3775dbcd23497095da8f5b7ddb62a540 Dockerfile_wheezy
goss-0.4.9/integration-tests/Find-AvailablePort.ps1 0000664 0000000 0000000 00000000673 14675050513 0022226 0 ustar 00root root 0000000 0000000 param(
# Start port scanning at
[int] $startAt = 1025,
# End port scanning at
[int] $endAt = 65535
)
for ($port=$startAt; $port -lt $endAt; $port++) {
$listener = New-Object System.Net.Sockets.TcpListener([System.Net.IPAddress]::Any, $port)
try {
$listener.Start()
write-output "$port"
break
}
catch {
write-host "$port busy"
}
finally {
$listener.Stop()
}
}
goss-0.4.9/integration-tests/goss/ 0000775 0000000 0000000 00000000000 14675050513 0017143 5 ustar 00root root 0000000 0000000 goss-0.4.9/integration-tests/goss/alpine3/ 0000775 0000000 0000000 00000000000 14675050513 0020476 5 ustar 00root root 0000000 0000000 goss-0.4.9/integration-tests/goss/alpine3/goss-aa-expected.yaml 0000664 0000000 0000000 00000000176 14675050513 0024517 0 ustar 00root root 0000000 0000000 package:
apache2:
installed: true
versions:
- 2.4.59-r0
service:
apache2:
enabled: true
running: true
goss-0.4.9/integration-tests/goss/alpine3/goss-expected-q.yaml 0000664 0000000 0000000 00000004211 14675050513 0024370 0 ustar 00root root 0000000 0000000 file:
/etc/passwd:
exists: true
contents: []
/tmp/goss/foobar:
exists: false
contents: []
package:
apache2:
installed: true
foobar:
installed: false
vim-tiny:
installed: false
addr:
tcp://httpbin:22:
reachable: false
timeout: 1000
tcp://httpbin:80:
reachable: true
timeout: 1000
udp://8.8.8.8:53:
reachable: true
timeout: 1000
port:
tcp:80:
listening: true
tcp:9999:
listening: false
tcp6:80:
listening: false
service:
apache2:
enabled: true
running: true
foobar:
enabled: false
running: false
user:
foobar:
exists: false
www-data:
exists: false
group:
foobar:
exists: false
www-data:
exists: true
command:
echo 'hi':
exit-status: 0
stdout: ""
stderr: ""
timeout: 10000
foobar:
exit-status: 127
stdout: ""
stderr: ""
timeout: 10000
dns:
CAA:dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
CNAME:c.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
MX:dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
NS:dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
PTR:54.243.154.1:
resolvable: true
timeout: 1000
server: 8.8.8.8
SRV:_https._tcp.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
TXT:txt._test.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
ip6.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
localhost:
resolvable: true
timeout: 1000
process:
apache2:
running: false
foobar:
running: false
kernel-param:
kernel.ostype:
value: Linux
mount:
/dev:
exists: true
timeout: 1000
http:
http://google.com:
status: 301
allow-insecure: false
no-follow-redirects: true
timeout: 5000
body: []
https://www.apple.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
proxy: http://127.0.0.1:8888
https://www.google.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
goss-0.4.9/integration-tests/goss/alpine3/goss-expected.yaml 0000664 0000000 0000000 00000005563 14675050513 0024145 0 ustar 00root root 0000000 0000000 file:
/etc/passwd:
exists: true
mode: "0644"
owner: root
group: root
filetype: file
contents: []
/tmp/goss/foobar:
exists: false
contents: []
package:
apache2:
installed: true
versions:
- 2.4.59-r0
foobar:
installed: false
vim-tiny:
installed: false
addr:
tcp://httpbin:22:
reachable: false
timeout: 1000
tcp://httpbin:80:
reachable: true
timeout: 1000
udp://8.8.8.8:53:
reachable: true
timeout: 1000
port:
tcp:80:
listening: true
ip:
- 0.0.0.0
tcp:9999:
listening: false
ip: []
tcp6:80:
listening: false
ip: []
service:
apache2:
enabled: true
running: true
foobar:
enabled: false
running: false
user:
foobar:
exists: false
www-data:
exists: false
group:
foobar:
exists: false
www-data:
exists: true
gid: 82
command:
echo 'hi':
exit-status: 0
stdout:
- hi
stderr: ""
timeout: 10000
foobar:
exit-status: 127
stdout: ""
stderr:
- 'sh: foobar: not found'
timeout: 10000
dns:
CAA:dnstest.io:
resolvable: true
addrs:
- 0 issue comodoca.com
- 0 issue letsencrypt.org
- 0 issuewild ;
timeout: 1000
server: 8.8.8.8
CNAME:c.dnstest.io:
resolvable: true
addrs:
- a.dnstest.io.
timeout: 1000
server: 8.8.8.8
MX:dnstest.io:
resolvable: true
addrs:
- 10 b.dnstest.io.
- 5 a.dnstest.io.
timeout: 1000
server: 8.8.8.8
NS:dnstest.io:
resolvable: true
addrs:
- ns1.dnstest.io.
- ns2.dnstest.io.
timeout: 1000
server: 8.8.8.8
PTR:54.243.154.1:
resolvable: true
addrs:
- ec2-54-243-154-1.compute-1.amazonaws.com.
timeout: 1000
server: 8.8.8.8
SRV:_https._tcp.dnstest.io:
resolvable: true
addrs:
- 0 5 443 a.dnstest.io.
- 10 10 443 b.dnstest.io.
timeout: 1000
server: 8.8.8.8
TXT:txt._test.dnstest.io:
resolvable: true
addrs:
- Hello DNS
timeout: 1000
server: 8.8.8.8
ip6.dnstest.io:
resolvable: true
addrs:
- 2404:6800:4001:807::200e
timeout: 1000
server: 8.8.8.8
localhost:
resolvable: true
addrs:
- 127.0.0.1
- ::1
timeout: 1000
process:
apache2:
running: false
foobar:
running: false
kernel-param:
kernel.ostype:
value: Linux
mount:
/dev:
exists: true
opts:
- rw
- nosuid
vfs-opts:
- rw
source: tmpfs
filesystem: tmpfs
timeout: 1000
http:
http://google.com:
status: 301
allow-insecure: false
no-follow-redirects: true
timeout: 5000
body: []
https://www.apple.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
proxy: http://127.0.0.1:8888
https://www.google.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
goss-0.4.9/integration-tests/goss/alpine3/goss.yaml 0000664 0000000 0000000 00000000737 14675050513 0022344 0 ustar 00root root 0000000 0000000 ---
service:
autofs:
enabled: false
running: false
user:
apache:
exists: true
uid: 100
gid: 101
groups:
- apache
home: "/var/www"
group:
apache:
exists: true
gid: 101
process:
httpd:
running: true
port:
tcp:80:
listening: true
ip:
- "0.0.0.0"
addr:
tcp://127.0.0.1:80:
reachable: true
timeout: 500
local-address: 127.0.0.1
gossfile:
"../goss-s*.yaml": {}
bypath:
file: "../goss-dummy.yaml"
goss-0.4.9/integration-tests/goss/arch/ 0000775 0000000 0000000 00000000000 14675050513 0020060 5 ustar 00root root 0000000 0000000 goss-0.4.9/integration-tests/goss/arch/goss.yaml 0000664 0000000 0000000 00000000464 14675050513 0021723 0 ustar 00root root 0000000 0000000 ---
package:
curl:
installed: true
pacman:
installed: true
foobar:
installed: false
user:
root:
exists: true
uid: 0
gid: 0
home: "/root"
file:
"/foo":
exists: true
filetype: symlink
gossfile:
"../goss-shared.yaml": {}
bypath:
file: "../goss-dummy.yaml"
goss-0.4.9/integration-tests/goss/centos7/ 0000775 0000000 0000000 00000000000 14675050513 0020525 5 ustar 00root root 0000000 0000000 goss-0.4.9/integration-tests/goss/centos7/goss-aa-expected.yaml 0000664 0000000 0000000 00000000342 14675050513 0024541 0 ustar 00root root 0000000 0000000 package:
httpd:
installed: true
versions:
- 2.4.6-95.el7.centos
port:
tcp:80:
listening: true
ip:
- 0.0.0.0
service:
httpd:
enabled: true
running: true
process:
httpd:
running: true
goss-0.4.9/integration-tests/goss/centos7/goss-expected-q.yaml 0000664 0000000 0000000 00000004175 14675050513 0024430 0 ustar 00root root 0000000 0000000 file:
/etc/passwd:
exists: true
contents: []
/tmp/goss/foobar:
exists: false
contents: []
package:
foobar:
installed: false
httpd:
installed: true
vim-tiny:
installed: false
addr:
tcp://httpbin:22:
reachable: false
timeout: 1000
tcp://httpbin:80:
reachable: true
timeout: 1000
udp://8.8.8.8:53:
reachable: true
timeout: 1000
port:
tcp:80:
listening: true
tcp:9999:
listening: false
tcp6:80:
listening: false
service:
foobar:
enabled: false
running: false
httpd:
enabled: true
running: true
user:
apache:
exists: true
foobar:
exists: false
group:
apache:
exists: true
foobar:
exists: false
command:
echo 'hi':
exit-status: 0
stdout: ""
stderr: ""
timeout: 10000
foobar:
exit-status: 127
stdout: ""
stderr: ""
timeout: 10000
dns:
CAA:dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
CNAME:c.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
MX:dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
NS:dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
PTR:54.243.154.1:
resolvable: true
timeout: 1000
server: 8.8.8.8
SRV:_https._tcp.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
TXT:txt._test.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
ip6.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
localhost:
resolvable: true
timeout: 1000
process:
foobar:
running: false
httpd:
running: true
kernel-param:
kernel.ostype:
value: Linux
mount:
/dev:
exists: true
timeout: 1000
http:
http://google.com:
status: 301
allow-insecure: false
no-follow-redirects: true
timeout: 5000
body: []
https://www.apple.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
proxy: http://127.0.0.1:8888
https://www.google.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
goss-0.4.9/integration-tests/goss/centos7/goss-expected.yaml 0000664 0000000 0000000 00000005736 14675050513 0024176 0 ustar 00root root 0000000 0000000 file:
/etc/passwd:
exists: true
mode: "0644"
owner: root
group: root
filetype: file
contents: []
/tmp/goss/foobar:
exists: false
contents: []
package:
foobar:
installed: false
httpd:
installed: true
versions:
- 2.4.6-95.el7.centos
vim-tiny:
installed: false
addr:
tcp://httpbin:22:
reachable: false
timeout: 1000
tcp://httpbin:80:
reachable: true
timeout: 1000
udp://8.8.8.8:53:
reachable: true
timeout: 1000
port:
tcp:80:
listening: true
ip:
- 0.0.0.0
tcp:9999:
listening: false
ip: []
tcp6:80:
listening: false
ip: []
service:
foobar:
enabled: false
running: false
httpd:
enabled: true
running: true
user:
apache:
exists: true
uid: 48
gid: 48
groups:
- apache
home: /usr/share/httpd
shell: /sbin/nologin
foobar:
exists: false
group:
apache:
exists: true
gid: 48
foobar:
exists: false
command:
echo 'hi':
exit-status: 0
stdout:
- hi
stderr: ""
timeout: 10000
foobar:
exit-status: 127
stdout: ""
stderr:
- 'sh: foobar: command not found'
timeout: 10000
dns:
CAA:dnstest.io:
resolvable: true
addrs:
- 0 issue comodoca.com
- 0 issue letsencrypt.org
- 0 issuewild ;
timeout: 1000
server: 8.8.8.8
CNAME:c.dnstest.io:
resolvable: true
addrs:
- a.dnstest.io.
timeout: 1000
server: 8.8.8.8
MX:dnstest.io:
resolvable: true
addrs:
- 10 b.dnstest.io.
- 5 a.dnstest.io.
timeout: 1000
server: 8.8.8.8
NS:dnstest.io:
resolvable: true
addrs:
- ns1.dnstest.io.
- ns2.dnstest.io.
timeout: 1000
server: 8.8.8.8
PTR:54.243.154.1:
resolvable: true
addrs:
- ec2-54-243-154-1.compute-1.amazonaws.com.
timeout: 1000
server: 8.8.8.8
SRV:_https._tcp.dnstest.io:
resolvable: true
addrs:
- 0 5 443 a.dnstest.io.
- 10 10 443 b.dnstest.io.
timeout: 1000
server: 8.8.8.8
TXT:txt._test.dnstest.io:
resolvable: true
addrs:
- Hello DNS
timeout: 1000
server: 8.8.8.8
ip6.dnstest.io:
resolvable: true
addrs:
- 2404:6800:4001:807::200e
timeout: 1000
server: 8.8.8.8
localhost:
resolvable: true
addrs:
- 127.0.0.1
- ::1
timeout: 1000
process:
foobar:
running: false
httpd:
running: true
kernel-param:
kernel.ostype:
value: Linux
mount:
/dev:
exists: true
opts:
- rw
- nosuid
vfs-opts:
- rw
source: tmpfs
filesystem: tmpfs
timeout: 1000
http:
http://google.com:
status: 301
allow-insecure: false
no-follow-redirects: true
timeout: 5000
body: []
https://www.apple.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
proxy: http://127.0.0.1:8888
https://www.google.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
goss-0.4.9/integration-tests/goss/centos7/goss.yaml 0000664 0000000 0000000 00000000737 14675050513 0022373 0 ustar 00root root 0000000 0000000 service:
autofs:
enabled: false
running: false
user:
apache:
exists: true
uid: 48
gid: 48
groups:
- apache
home: "/usr/share/httpd"
group:
apache:
exists: true
gid: 48
process:
httpd:
running: true
port:
tcp:80:
listening: true
ip:
- '0.0.0.0'
addr:
tcp://127.0.0.1:80:
reachable: true
timeout: 500
local-address: 127.0.0.1
gossfile:
"../goss-s*.yaml": {}
bypath:
file: "../goss-dummy.yaml"
goss-0.4.9/integration-tests/goss/darwin/ 0000775 0000000 0000000 00000000000 14675050513 0020427 5 ustar 00root root 0000000 0000000 goss-0.4.9/integration-tests/goss/darwin/commands/ 0000775 0000000 0000000 00000000000 14675050513 0022230 5 ustar 00root root 0000000 0000000 goss-0.4.9/integration-tests/goss/darwin/commands/add.goss.yaml 0000664 0000000 0000000 00000000360 14675050513 0024615 0 ustar 00root root 0000000 0000000 ---
# TODO: coverage for the add {test} permutations
command:
"add addr 127.0.0.1":
exit-status: 0
exec: release/goss-darwin-amd64 --use-alpha=1 add addr 127.0.0.1
stdout:
- "timeout: 500"
stderr: []
timeout: 5000
goss-0.4.9/integration-tests/goss/darwin/commands/autoadd.goss.yaml 0000664 0000000 0000000 00000000446 14675050513 0025513 0 ustar 00root root 0000000 0000000 ---
command:
"autoadd /Users/travis":
exit-status: 0
exec: "release/goss-darwin-amd64 --use-alpha=1 autoadd /Users/travis"
stdout:
- 'file:'
- ' exists: true'
- ' filetype: directory'
stderr: []
timeout: 5000
# needs implementation
skip: true
goss-0.4.9/integration-tests/goss/darwin/commands/help.goss.yaml 0000664 0000000 0000000 00000000216 14675050513 0025015 0 ustar 00root root 0000000 0000000 ---
command:
help:
exit-status: 0
exec: "release/goss-darwin-amd64 help"
stdout:
- alpha
stderr: []
timeout: 5000
goss-0.4.9/integration-tests/goss/darwin/commands/validate-input.yaml 0000664 0000000 0000000 00000000060 14675050513 0026036 0 ustar 00root root 0000000 0000000 ---
file:
non-existent.txt:
exists: false
goss-0.4.9/integration-tests/goss/darwin/commands/validate.goss.yaml 0000664 0000000 0000000 00000000503 14675050513 0025655 0 ustar 00root root 0000000 0000000 ---
# TODO: coverage for the add {test} permutations
command:
"validate":
exit-status: 0
exec: "release/goss-darwin-amd64 --use-alpha=1 -g integration-tests/goss/darwin/commands/validate-input.yaml validate"
stdout:
- 'Count: 1'
- 'Failed: 0'
- 'Skipped: 0'
stderr: []
timeout: 5000
goss-0.4.9/integration-tests/goss/darwin/tests/ 0000775 0000000 0000000 00000000000 14675050513 0021571 5 ustar 00root root 0000000 0000000 goss-0.4.9/integration-tests/goss/darwin/tests/addr.goss.yaml 0000664 0000000 0000000 00000000375 14675050513 0024346 0 ustar 00root root 0000000 0000000 ---
addr:
tcp://google.com:443:
reachable: true
timeout: 1000
# TODO: needs implementation (or figure out a likely listening port on macOS/travis)
# tcp://127.0.0.1:135:
# reachable: true
# timeout: 1000
# local-address: true
goss-0.4.9/integration-tests/goss/darwin/tests/command.goss.yaml 0000664 0000000 0000000 00000000214 14675050513 0025042 0 ustar 00root root 0000000 0000000 ---
command:
hello world:
exit-status: 0
exec: "echo hello world"
stdout:
- hello world
stderr: []
timeout: 10000
goss-0.4.9/integration-tests/goss/darwin/tests/dns.goss.yaml 0000664 0000000 0000000 00000000143 14675050513 0024211 0 ustar 00root root 0000000 0000000 ---
dns:
localhost:
resolvable: true
addrs:
- "127.0.0.1"
- ::1
timeout: 500
goss-0.4.9/integration-tests/goss/darwin/tests/file.goss.yaml 0000664 0000000 0000000 00000000631 14675050513 0024346 0 ustar 00root root 0000000 0000000 ---
file:
integration-tests/goss/testdata/static-file.txt:
exists: true
mode: "0644"
# user: "" # TODO: not working on Darwin
# group: "" # TODO: not working on Darwin
size: 20
filetype: file
md5: 9dcea4037b1439a2a96e4d206eda63a4
sha256: e73d885411a52a0d29142e830e104e0cc9252fbb1dc3c92a430ef7c369f089ef
contents:
- "nothing to see here"
- "/nothing.*here/"
goss-0.4.9/integration-tests/goss/darwin/tests/gossfile.goss.yaml 0000664 0000000 0000000 00000000751 14675050513 0025245 0 ustar 00root root 0000000 0000000 ---
# paths are relative to the goss file that includes the gossfile directive.
gossfile:
addr.goss.yaml: {}
command.goss.yaml: {}
dns.goss.yaml: {}
file.goss.yaml: {}
# don't use gossfile; avoid self-referencing
# gossfile.goss.yaml: {}
group.goss.yaml: {}
http.goss.yaml: {}
interface.goss.yaml: {}
# kernel-param.na-goss.yaml: {}
mount.goss.yaml: {}
package.goss.yaml: {}
port.goss.yaml: {}
process.goss.yaml: {}
service.goss.yaml: {}
user.goss.yaml: {}
goss-0.4.9/integration-tests/goss/darwin/tests/group.goss.yaml 0000664 0000000 0000000 00000000147 14675050513 0024565 0 ustar 00root root 0000000 0000000 ---
group:
_developers:
exists: true
gid: 0
# TODO: needs implementation
skip: true
goss-0.4.9/integration-tests/goss/darwin/tests/http.goss.yaml 0000664 0000000 0000000 00000000437 14675050513 0024412 0 ustar 00root root 0000000 0000000 ---
http:
https://google.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 10000
request-headers:
- "Content-Type: text/html"
headers:
- "Content-Type: text/html"
body:
- "google"
username: ""
password: ""
goss-0.4.9/integration-tests/goss/darwin/tests/interface.goss.yaml 0000664 0000000 0000000 00000000206 14675050513 0025365 0 ustar 00root root 0000000 0000000 ---
interface:
eth0:
exists: true
addrs:
- '127.0.0.1'
mtu: 1500
# TODO: needs implementation
skip: true
goss-0.4.9/integration-tests/goss/darwin/tests/kernel-param.na-goss.yaml 0000664 0000000 0000000 00000000216 14675050513 0026400 0 ustar 00root root 0000000 0000000 ---
kernel-param:
notapplicable.on-darwin:
value: foobar
# TODO: need implementation or signal no support on Darwin
skip: true
goss-0.4.9/integration-tests/goss/darwin/tests/mount.goss.yaml 0000664 0000000 0000000 00000000236 14675050513 0024572 0 ustar 00root root 0000000 0000000 ---
mount:
'/':
exists: true
filesystem: hdfs
opts: []
source: ''
usage:
lt: 95
# TODO: needs implementation
skip: true
goss-0.4.9/integration-tests/goss/darwin/tests/package.goss.yaml 0000664 0000000 0000000 00000000403 14675050513 0025017 0 ustar 00root root 0000000 0000000 ---
package:
golang:
# required attributes
installed: true
# optional attributes
versions:
- 1.14.1
# needs implementation
# needs discussion + design
# support question for:
# * homebrew
# * macports
skip: true
goss-0.4.9/integration-tests/goss/darwin/tests/port.goss.yaml 0000664 0000000 0000000 00000000160 14675050513 0024410 0 ustar 00root root 0000000 0000000 ---
port:
tcp:135:
listening: true
ip:
- 0.0.0.0
# TODO: needs implementation
skip: true
goss-0.4.9/integration-tests/goss/darwin/tests/process.goss.yaml 0000664 0000000 0000000 00000000047 14675050513 0025106 0 ustar 00root root 0000000 0000000 ---
process:
bash:
running: true
goss-0.4.9/integration-tests/goss/darwin/tests/service.goss.yaml 0000664 0000000 0000000 00000000155 14675050513 0025070 0 ustar 00root root 0000000 0000000 ---
service:
launchd:
enabled: true
running: true
# TODO: needs implementation
skip: true
goss-0.4.9/integration-tests/goss/darwin/tests/user.goss.yaml 0000664 0000000 0000000 00000000303 14675050513 0024401 0 ustar 00root root 0000000 0000000 ---
user:
travis:
exists: true
uid: 65534
gid: 65534
groups:
- _developers
home: /Users/travis
shell: /sbin/nologin
# TODO: needs implementation
skip: true
goss-0.4.9/integration-tests/goss/generate_goss.sh 0000775 0000000 0000000 00000006027 14675050513 0022334 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
SCRIPT_DIR=$(readlink -f $(dirname $0))
OS=$1
ARCH=$2
[[ $3 == "-q" ]] && args=("--exclude-attr" "*")
goss() {
$SCRIPT_DIR/$OS/goss-linux-$ARCH -g $SCRIPT_DIR/${OS}/goss-generated-$ARCH.yaml "$@"
# Validate that duplicates are ignored
$SCRIPT_DIR/$OS/goss-linux-$ARCH -g $SCRIPT_DIR/${OS}/goss-generated-$ARCH.yaml "$@"
}
rm -f $SCRIPT_DIR/${OS}/goss*generated*-$ARCH.yaml
for x in /etc/passwd /tmp/goss/foobar;do
goss a "${args[@]}" file $x
done
[[ $OS == "centos7" || $OS == "rockylinux9" ]] && package="httpd" || package="apache2"
[[ $OS == "centos7" || $OS == "rockylinux9" ]] && user="apache" || user="www-data"
goss a "${args[@]}" package $package foobar vim-tiny
goss a "${args[@]}" addr --timeout 1s httpbin:80 httpbin:22
goss a "${args[@]}" addr --timeout 1s udp://8.8.8.8:53
goss a "${args[@]}" port tcp:80 tcp6:80 9999
goss a "${args[@]}" service $package foobar
goss a "${args[@]}" user $user foobar
goss a "${args[@]}" group $user foobar
goss a "${args[@]}" command "echo 'hi'" foobar
goss a "${args[@]}" dns --timeout 1s --server 8.8.8.8 CNAME:c.dnstest.io
goss a "${args[@]}" dns --timeout 1s --server 8.8.8.8 MX:dnstest.io
goss a "${args[@]}" dns --timeout 1s --server 8.8.8.8 NS:dnstest.io
goss a "${args[@]}" dns --timeout 1s --server 8.8.8.8 PTR:54.243.154.1
goss a "${args[@]}" dns --timeout 1s --server 8.8.8.8 SRV:_https._tcp.dnstest.io
goss a "${args[@]}" dns --timeout 1s --server 8.8.8.8 TXT:txt._test.dnstest.io
goss a "${args[@]}" dns --timeout 1s --server 8.8.8.8 CAA:dnstest.io
goss a "${args[@]}" dns --timeout 1s --server 8.8.8.8 ip6.dnstest.io
goss a "${args[@]}" dns --timeout 1s localhost
goss a "${args[@]}" process $package foobar
goss a "${args[@]}" kernel-param kernel.ostype
goss a "${args[@]}" mount /dev
# Make tests consistent across different docker setups
sed -i '/- seclabel/d' $SCRIPT_DIR/${OS}/goss-generated-$ARCH.yaml
sed -i '/- size=/d' $SCRIPT_DIR/${OS}/goss-generated-$ARCH.yaml
sed -i '/- mode=/d' $SCRIPT_DIR/${OS}/goss-generated-$ARCH.yaml
sed -i '/- inode64/d' $SCRIPT_DIR/${OS}/goss-generated-$ARCH.yaml
goss a "${args[@]}" http https://www.google.com
goss a "${args[@]}" http https://www.apple.com -x http://127.0.0.1:8888
goss a "${args[@]}" http http://google.com -r
# Auto-add
# Validate that empty configs don't get created
$SCRIPT_DIR/$OS/goss-linux-$ARCH -g $SCRIPT_DIR/${OS}/goss-aa-generated-$ARCH.yaml aa nosuchresource
if [[ -f $SCRIPT_DIR/${OS}/goss-aa-generated-$ARCH.yaml ]]
then
echo "Error! Empty config file exists!" && exit 1
fi
$SCRIPT_DIR/$OS/goss-linux-$ARCH -g $SCRIPT_DIR/${OS}/goss-aa-generated-$ARCH.yaml aa $package
# Validate that duplicates are ignored
$SCRIPT_DIR/$OS/goss-linux-$ARCH -g $SCRIPT_DIR/${OS}/goss-aa-generated-$ARCH.yaml aa $package
#Â Validate that we can aa none existent resources without destroying the file
$SCRIPT_DIR/$OS/goss-linux-$ARCH -g $SCRIPT_DIR/${OS}/goss-aa-generated-$ARCH.yaml aa nosuchresource
if [[ ! -f $SCRIPT_DIR/${OS}/goss-aa-generated-$ARCH.yaml ]]
then
echo "Error! Config file removed by aa!" && exit 1
fi
goss-0.4.9/integration-tests/goss/goss-dummy.yaml 0000664 0000000 0000000 00000000150 14675050513 0022127 0 ustar 00root root 0000000 0000000
---
command:
includetest:
exec: echo 'hi'
exit-status: 0
stdout:
- hi
stderr: []
goss-0.4.9/integration-tests/goss/goss-serve.yaml 0000664 0000000 0000000 00000000151 14675050513 0022121 0 ustar 00root root 0000000 0000000 ---
command:
hello world:
exec: echo 'hi'
exit-status: 0
stdout:
- hi
stderr: []
goss-0.4.9/integration-tests/goss/goss-service.yaml 0000664 0000000 0000000 00000000511 14675050513 0022435 0 ustar 00root root 0000000 0000000 ---
service:
foobar:
enabled: false
running: false
{{ if .Env.OS | regexMatch "centos[7]|rockylinux[9]" }}
httpd:
{{else}}
apache2:
{{end}}
{{ if .Env.OS | regexMatch "trusty" }}
enabled: false
{{else}}
enabled: true
{{end}}
running: true
skippable:
enabled: true
running: true
skip: true
goss-0.4.9/integration-tests/goss/goss-shared.yaml 0000664 0000000 0000000 00000013440 14675050513 0022250 0 ustar 00root root 0000000 0000000 ---
command:
echo 'hi':
exit-status: 0
stdout:
- hi
stderr: []
foobar:
exit-status: 127
stdout: []
stderr:
- not found
command-override:
exec: true
exit-status: 0
commandskip:
exec: false
exit-status: 0
skip: true
file:
{{range mkSlice "/etc/PAsswD" "/etc/group"}}
{{. | toLower}}:
exists: true
mode: '0644'
owner: root
uid: 0
group: root
gid: 0
filetype: file
contents:
- root
{{end}}
"/goss/hellogoss.txt":
exists: true
md5: 7c9bb14b3bf178e82c00c2a4398c93cd
sha256: 7f78ce27859049f725936f7b52c6e25d774012947d915e7b394402cfceb70c4c
sha512: 372864ab83187de41ca57c5c77cd4a99220ccadc8b8ddb18367893fd3e58764193a599edbf63a48c0c44f1e923606a00929b46de3bda1744fd722b9d42829206
"/tmp/goss/foobar":
exists: false
contents: []
"~root":
exists: true
mode: '0700'
"/tmp":
exists: true
mode: '1777'
"/dev/random":
exists: true
filetype: character-device
"/pipe":
exists: true
filetype: pipe
"/does/not/exist":
exists: true
contents:
- skip-this-test
skip: true
package:
foobar:
installed: false
{{- range $name, $ver := index .Vars .Env.OS "packages"}}
{{$name}}:
installed: true
versions:
- {{$ver}}
{{end}}
service:
{{- range $name, $runlevels := index .Vars .Env.OS "services"}}
{{$name}}:
enabled: true
running: true
runlevels: {{toJson $runlevels}}
{{end}}
addr:
tcp://httpbin:22:
reachable: false
timeout: 1000
tcp://httpbin:80:
reachable: true
timeout: 5000
tcp://httpbin:999:
reachable: false
timeout: 5000
local-address: 127.0.0.1
port:
tcp:9999:
listening: false
user:
root:
exists: true
foobar:
exists: false
group:
foobar:
exists: false
dns:
CAA:dnstest.io:
resolvable: true
addrs:
- 0 issue comodoca.com
- 0 issue letsencrypt.org
- 0 issuewild ;
timeout: 2000
server: 8.8.8.8
CNAME:c.dnstest.io:
resolvable: true
addrs:
- a.dnstest.io.
timeout: 2000
server: 8.8.8.8
c.dnstest.io:
resolvable: true
addrs:
- 192.30.252.153
timeout: 2000
server: 8.8.8.8:53
MX:dnstest.io:
resolvable: true
addrs:
- 10 b.dnstest.io.
- 5 a.dnstest.io.
timeout: 2000
server: 8.8.8.8:53
NS:dnstest.io:
resolvable: true
addrs:
- ns1.dnstest.io.
- ns2.dnstest.io.
timeout: 2000
server: 8.8.8.8
PTR:54.243.154.1:
resolvable: true
addrs:
- ec2-54-243-154-1.compute-1.amazonaws.com.
timeout: 2000
server: 8.8.8.8
SRV:_https._tcp.dnstest.io:
resolvable: true
addrs:
- 0 5 443 a.dnstest.io.
- 10 10 443 b.dnstest.io.
timeout: 2000
server: 8.8.8.8
TXT:txt._test.dnstest.io:
resolvable: true
addrs:
- Hello DNS
timeout: 2000
server: 8.8.8.8
ip6.dnstest.io:
resolvable: true
addrs:
- 2404:6800:4001:807::200e
timeout: 2000
server: 8.8.8.8
localhost:
resolvable: true
addrs:
- 127.0.0.1
timeout: 2000
dnstest.io:
resolvable: true
server: 8.8.8.8
timeout: 2000
process:
foobar:
running: false
kernel-param:
kernel.ostype:
value: Linux
mount:
"/dev":
exists: true
timeout: 1000
opts:
- rw
- nosuid
vfs-opts:
- mode=755
source: tmpfs
filesystem: tmpfs
"/":
exists: true
usage:
and:
- lt: 95
- gt: 0
interface:
eth0:
exists: true
addrs:
contain-element:
have-prefix: '172.'
http:
{{ if index .Vars .Env.OS "proxy" }}
http://httpbin/anything:
status: 200
timeout: 60000
proxy: {{ index .Vars .Env.OS "proxy" }}
{{ end }}
http://httpbin/headers:
status: 200
timeout: 60000
request-headers:
- "Foo: bar"
headers: ["Content-Type: application/json"]
body:
- '"Foo": "bar"'
- '/"User-Agent": "goss/v?[0-9]+.[0-9]+.[0-9]+"/'
http://httpbin/headers?host:
status: 200
timeout: 60000
request-headers:
# This is causing intermittent errors depending on the httpbin server hit
# need to see if there's a good way around this, maybe local httpbin?
- "Host: httpbin"
headers: ["Content-Type: application/json"]
body: ['"Host": "httpbin"']
http://httpbin/basic-auth/username/secret:
status: 200
timeout: 60000
username: username
password: secret
http://httpbin/basic-auth/username/secret?failure:
status: 401
timeout: 60000
username: username
password: wrong
http://httpbin/put:
status: 200
method: PUT
timeout: 60000
request-body: '{"key": "value"}'
body:
- '"key": "value"'
anything-with-get:
url: http://httpbin/anything
status: 200
timeout: 60000
body: []
anything-with-put:
url: http://httpbin/anything
status: 200
method: GET
timeout: 60000
request-body: "request-body"
body: ["request-body"]
matching:
has_substr:
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
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"
vars_inline_simple:
content: {{ .Vars.inline }}
matches:
match-regexp: bar
vars_inline_overwrite:
content: {{ .Vars.overwrite }}
matches:
match-regexp: bar
sping_basic:
content: {{ "hello!" | upper | repeat 5 }}
matches:
match-regexp: "HELLO!HELLO!HELLO!HELLO!HELLO!"
gossfile:
"nonexistent-file.yaml":
skip: true
bypath:
file: "goss-dummy.yaml"
goss-0.4.9/integration-tests/goss/goss-wait.yaml 0000664 0000000 0000000 00000000202 14675050513 0021736 0 ustar 00root root 0000000 0000000 ---
addr:
tcp://localhost:80:
reachable: true
timeout: 500
tcp://localhost:8888:
reachable: true
timeout: 500
goss-0.4.9/integration-tests/goss/hellogoss.txt 0000664 0000000 0000000 00000000015 14675050513 0021677 0 ustar 00root root 0000000 0000000 Goss Rocks!!
goss-0.4.9/integration-tests/goss/rockylinux9/ 0000775 0000000 0000000 00000000000 14675050513 0021443 5 ustar 00root root 0000000 0000000 goss-0.4.9/integration-tests/goss/rockylinux9/goss-aa-expected.yaml 0000664 0000000 0000000 00000000340 14675050513 0025455 0 ustar 00root root 0000000 0000000 package:
httpd:
installed: true
versions:
- 2.4.57-11.el9_4.1
port:
tcp:80:
listening: true
ip:
- 0.0.0.0
service:
httpd:
enabled: true
running: true
process:
httpd:
running: true
goss-0.4.9/integration-tests/goss/rockylinux9/goss-expected-q.yaml 0000664 0000000 0000000 00000004175 14675050513 0025346 0 ustar 00root root 0000000 0000000 file:
/etc/passwd:
exists: true
contents: []
/tmp/goss/foobar:
exists: false
contents: []
package:
foobar:
installed: false
httpd:
installed: true
vim-tiny:
installed: false
addr:
tcp://httpbin:22:
reachable: false
timeout: 1000
tcp://httpbin:80:
reachable: true
timeout: 1000
udp://8.8.8.8:53:
reachable: true
timeout: 1000
port:
tcp:80:
listening: true
tcp:9999:
listening: false
tcp6:80:
listening: false
service:
foobar:
enabled: false
running: false
httpd:
enabled: true
running: true
user:
apache:
exists: true
foobar:
exists: false
group:
apache:
exists: true
foobar:
exists: false
command:
echo 'hi':
exit-status: 0
stdout: ""
stderr: ""
timeout: 10000
foobar:
exit-status: 127
stdout: ""
stderr: ""
timeout: 10000
dns:
CAA:dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
CNAME:c.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
MX:dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
NS:dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
PTR:54.243.154.1:
resolvable: true
timeout: 1000
server: 8.8.8.8
SRV:_https._tcp.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
TXT:txt._test.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
ip6.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
localhost:
resolvable: true
timeout: 1000
process:
foobar:
running: false
httpd:
running: true
kernel-param:
kernel.ostype:
value: Linux
mount:
/dev:
exists: true
timeout: 1000
http:
http://google.com:
status: 301
allow-insecure: false
no-follow-redirects: true
timeout: 5000
body: []
https://www.apple.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
proxy: http://127.0.0.1:8888
https://www.google.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
goss-0.4.9/integration-tests/goss/rockylinux9/goss-expected.yaml 0000664 0000000 0000000 00000005744 14675050513 0025113 0 ustar 00root root 0000000 0000000 file:
/etc/passwd:
exists: true
mode: "0644"
owner: root
group: root
filetype: file
contents: []
/tmp/goss/foobar:
exists: false
contents: []
package:
foobar:
installed: false
httpd:
installed: true
versions:
- 2.4.57-11.el9_4.1
vim-tiny:
installed: false
addr:
tcp://httpbin:22:
reachable: false
timeout: 1000
tcp://httpbin:80:
reachable: true
timeout: 1000
udp://8.8.8.8:53:
reachable: true
timeout: 1000
port:
tcp:80:
listening: true
ip:
- 0.0.0.0
tcp:9999:
listening: false
ip: []
tcp6:80:
listening: false
ip: []
service:
foobar:
enabled: false
running: false
httpd:
enabled: true
running: true
user:
apache:
exists: true
uid: 48
gid: 48
groups:
- apache
home: /usr/share/httpd
shell: /sbin/nologin
foobar:
exists: false
group:
apache:
exists: true
gid: 48
foobar:
exists: false
command:
echo 'hi':
exit-status: 0
stdout:
- hi
stderr: ""
timeout: 10000
foobar:
exit-status: 127
stdout: ""
stderr:
- 'sh: line 1: foobar: command not found'
timeout: 10000
dns:
CAA:dnstest.io:
resolvable: true
addrs:
- 0 issue comodoca.com
- 0 issue letsencrypt.org
- 0 issuewild ;
timeout: 1000
server: 8.8.8.8
CNAME:c.dnstest.io:
resolvable: true
addrs:
- a.dnstest.io.
timeout: 1000
server: 8.8.8.8
MX:dnstest.io:
resolvable: true
addrs:
- 10 b.dnstest.io.
- 5 a.dnstest.io.
timeout: 1000
server: 8.8.8.8
NS:dnstest.io:
resolvable: true
addrs:
- ns1.dnstest.io.
- ns2.dnstest.io.
timeout: 1000
server: 8.8.8.8
PTR:54.243.154.1:
resolvable: true
addrs:
- ec2-54-243-154-1.compute-1.amazonaws.com.
timeout: 1000
server: 8.8.8.8
SRV:_https._tcp.dnstest.io:
resolvable: true
addrs:
- 0 5 443 a.dnstest.io.
- 10 10 443 b.dnstest.io.
timeout: 1000
server: 8.8.8.8
TXT:txt._test.dnstest.io:
resolvable: true
addrs:
- Hello DNS
timeout: 1000
server: 8.8.8.8
ip6.dnstest.io:
resolvable: true
addrs:
- 2404:6800:4001:807::200e
timeout: 1000
server: 8.8.8.8
localhost:
resolvable: true
addrs:
- 127.0.0.1
- ::1
timeout: 1000
process:
foobar:
running: false
httpd:
running: true
kernel-param:
kernel.ostype:
value: Linux
mount:
/dev:
exists: true
opts:
- rw
- nosuid
vfs-opts:
- rw
source: tmpfs
filesystem: tmpfs
timeout: 1000
http:
http://google.com:
status: 301
allow-insecure: false
no-follow-redirects: true
timeout: 5000
body: []
https://www.apple.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
proxy: http://127.0.0.1:8888
https://www.google.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
goss-0.4.9/integration-tests/goss/rockylinux9/goss.yaml 0000664 0000000 0000000 00000000737 14675050513 0023311 0 ustar 00root root 0000000 0000000 service:
autofs:
enabled: false
running: false
user:
apache:
exists: true
uid: 48
gid: 48
groups:
- apache
home: "/usr/share/httpd"
group:
apache:
exists: true
gid: 48
process:
httpd:
running: true
port:
tcp:80:
listening: true
ip:
- '0.0.0.0'
addr:
tcp://127.0.0.1:80:
reachable: true
timeout: 500
local-address: 127.0.0.1
gossfile:
"../goss-s*.yaml": {}
bypath:
file: "../goss-dummy.yaml"
goss-0.4.9/integration-tests/goss/testdata/ 0000775 0000000 0000000 00000000000 14675050513 0020754 5 ustar 00root root 0000000 0000000 goss-0.4.9/integration-tests/goss/testdata/static-file.txt 0000664 0000000 0000000 00000000024 14675050513 0023715 0 ustar 00root root 0000000 0000000 nothing to see here
goss-0.4.9/integration-tests/goss/trusty/ 0000775 0000000 0000000 00000000000 14675050513 0020515 5 ustar 00root root 0000000 0000000 goss-0.4.9/integration-tests/goss/trusty/goss-aa-expected.yaml 0000664 0000000 0000000 00000000346 14675050513 0024535 0 ustar 00root root 0000000 0000000 package:
apache2:
installed: true
versions:
- 2.4.7-1ubuntu4.22
port:
tcp:80:
listening: true
ip:
- 0.0.0.0
service:
apache2:
enabled: true
running: true
process:
apache2:
running: true
goss-0.4.9/integration-tests/goss/trusty/goss-expected-q.yaml 0000664 0000000 0000000 00000004207 14675050513 0024414 0 ustar 00root root 0000000 0000000 file:
/etc/passwd:
exists: true
contents: []
/tmp/goss/foobar:
exists: false
contents: []
package:
apache2:
installed: true
foobar:
installed: false
vim-tiny:
installed: false
addr:
tcp://httpbin:22:
reachable: false
timeout: 1000
tcp://httpbin:80:
reachable: true
timeout: 1000
udp://8.8.8.8:53:
reachable: true
timeout: 1000
port:
tcp:80:
listening: true
tcp:9999:
listening: false
tcp6:80:
listening: false
service:
apache2:
enabled: true
running: true
foobar:
enabled: false
running: false
user:
foobar:
exists: false
www-data:
exists: true
group:
foobar:
exists: false
www-data:
exists: true
command:
echo 'hi':
exit-status: 0
stdout: ""
stderr: ""
timeout: 10000
foobar:
exit-status: 127
stdout: ""
stderr: ""
timeout: 10000
dns:
CAA:dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
CNAME:c.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
MX:dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
NS:dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
PTR:54.243.154.1:
resolvable: true
timeout: 1000
server: 8.8.8.8
SRV:_https._tcp.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
TXT:txt._test.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
ip6.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
localhost:
resolvable: true
timeout: 1000
process:
apache2:
running: true
foobar:
running: false
kernel-param:
kernel.ostype:
value: Linux
mount:
/dev:
exists: true
timeout: 1000
http:
http://google.com:
status: 301
allow-insecure: false
no-follow-redirects: true
timeout: 5000
body: []
https://www.apple.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
proxy: http://127.0.0.1:8888
https://www.google.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
goss-0.4.9/integration-tests/goss/trusty/goss-expected.yaml 0000664 0000000 0000000 00000005737 14675050513 0024167 0 ustar 00root root 0000000 0000000 file:
/etc/passwd:
exists: true
mode: "0644"
owner: root
group: root
filetype: file
contents: []
/tmp/goss/foobar:
exists: false
contents: []
package:
apache2:
installed: true
versions:
- 2.4.7-1ubuntu4.22
foobar:
installed: false
vim-tiny:
installed: false
addr:
tcp://httpbin:22:
reachable: false
timeout: 1000
tcp://httpbin:80:
reachable: true
timeout: 1000
udp://8.8.8.8:53:
reachable: true
timeout: 1000
port:
tcp:80:
listening: true
ip:
- 0.0.0.0
tcp:9999:
listening: false
ip: []
tcp6:80:
listening: false
ip: []
service:
apache2:
enabled: true
running: true
foobar:
enabled: false
running: false
user:
foobar:
exists: false
www-data:
exists: true
uid: 33
gid: 33
groups:
- www-data
home: /var/www
shell: /usr/sbin/nologin
group:
foobar:
exists: false
www-data:
exists: true
gid: 33
command:
echo 'hi':
exit-status: 0
stdout:
- hi
stderr: ""
timeout: 10000
foobar:
exit-status: 127
stdout: ""
stderr:
- 'sh: 1: foobar: not found'
timeout: 10000
dns:
CAA:dnstest.io:
resolvable: true
addrs:
- 0 issue comodoca.com
- 0 issue letsencrypt.org
- 0 issuewild ;
timeout: 1000
server: 8.8.8.8
CNAME:c.dnstest.io:
resolvable: true
addrs:
- a.dnstest.io.
timeout: 1000
server: 8.8.8.8
MX:dnstest.io:
resolvable: true
addrs:
- 10 b.dnstest.io.
- 5 a.dnstest.io.
timeout: 1000
server: 8.8.8.8
NS:dnstest.io:
resolvable: true
addrs:
- ns1.dnstest.io.
- ns2.dnstest.io.
timeout: 1000
server: 8.8.8.8
PTR:54.243.154.1:
resolvable: true
addrs:
- ec2-54-243-154-1.compute-1.amazonaws.com.
timeout: 1000
server: 8.8.8.8
SRV:_https._tcp.dnstest.io:
resolvable: true
addrs:
- 0 5 443 a.dnstest.io.
- 10 10 443 b.dnstest.io.
timeout: 1000
server: 8.8.8.8
TXT:txt._test.dnstest.io:
resolvable: true
addrs:
- Hello DNS
timeout: 1000
server: 8.8.8.8
ip6.dnstest.io:
resolvable: true
addrs:
- 2404:6800:4001:807::200e
timeout: 1000
server: 8.8.8.8
localhost:
resolvable: true
addrs:
- 127.0.0.1
- ::1
timeout: 1000
process:
apache2:
running: true
foobar:
running: false
kernel-param:
kernel.ostype:
value: Linux
mount:
/dev:
exists: true
opts:
- rw
- nosuid
vfs-opts:
- rw
source: tmpfs
filesystem: tmpfs
timeout: 1000
http:
http://google.com:
status: 301
allow-insecure: false
no-follow-redirects: true
timeout: 5000
body: []
https://www.apple.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
proxy: http://127.0.0.1:8888
https://www.google.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
goss-0.4.9/integration-tests/goss/trusty/goss.yaml 0000664 0000000 0000000 00000000743 14675050513 0022360 0 ustar 00root root 0000000 0000000 ---
service:
tinyproxy:
enabled: true
running: true
user:
www-data:
exists: true
uid: 33
gid: 33
groups:
- www-data
home: "/var/www"
group:
www-data:
exists: true
gid: 33
process:
apache2:
running: true
port:
tcp:80:
listening: true
ip:
- 0.0.0.0
addr:
tcp://127.0.0.1:80:
reachable: true
timeout: 500
local-address: 127.0.0.1
gossfile:
"../goss-s*.yaml": {}
bypath:
file: "../goss-dummy.yaml"
goss-0.4.9/integration-tests/goss/vars.yaml 0000664 0000000 0000000 00000001153 14675050513 0021002 0 ustar 00root root 0000000 0000000 ---
alpine3:
proxy: http://127.0.0.1:8888
packages:
apache2: "2.4.59-r0"
services:
apache2: [sysinit]
arch:
packages:
centos7:
proxy: http://127.0.0.1:8888
packages:
httpd: "2.4.6-95.el7.centos"
services:
httpd: []
rockylinux9:
proxy: http://127.0.0.1:8888
packages:
httpd: "2.4.57-11.el9_4.1"
services:
httpd: []
trusty:
proxy: http://127.0.0.1:8888
packages:
apache2: "2.4.7-1ubuntu4.22"
services:
apache2: ["3"]
wheezy:
proxy: http://127.0.0.1:8888
packages:
apache2: "2.2.22-13+deb7u13"
services:
apache2: ["2", "3", "5", "4"]
overwrite: foo
goss-0.4.9/integration-tests/goss/wheezy/ 0000775 0000000 0000000 00000000000 14675050513 0020456 5 ustar 00root root 0000000 0000000 goss-0.4.9/integration-tests/goss/wheezy/goss-aa-expected.yaml 0000664 0000000 0000000 00000000346 14675050513 0024476 0 ustar 00root root 0000000 0000000 package:
apache2:
installed: true
versions:
- 2.2.22-13+deb7u13
port:
tcp:80:
listening: true
ip:
- 0.0.0.0
service:
apache2:
enabled: true
running: true
process:
apache2:
running: true
goss-0.4.9/integration-tests/goss/wheezy/goss-expected-q.yaml 0000664 0000000 0000000 00000004207 14675050513 0024355 0 ustar 00root root 0000000 0000000 file:
/etc/passwd:
exists: true
contents: []
/tmp/goss/foobar:
exists: false
contents: []
package:
apache2:
installed: true
foobar:
installed: false
vim-tiny:
installed: false
addr:
tcp://httpbin:22:
reachable: false
timeout: 1000
tcp://httpbin:80:
reachable: true
timeout: 1000
udp://8.8.8.8:53:
reachable: true
timeout: 1000
port:
tcp:80:
listening: true
tcp:9999:
listening: false
tcp6:80:
listening: false
service:
apache2:
enabled: true
running: true
foobar:
enabled: false
running: false
user:
foobar:
exists: false
www-data:
exists: true
group:
foobar:
exists: false
www-data:
exists: true
command:
echo 'hi':
exit-status: 0
stdout: ""
stderr: ""
timeout: 10000
foobar:
exit-status: 127
stdout: ""
stderr: ""
timeout: 10000
dns:
CAA:dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
CNAME:c.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
MX:dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
NS:dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
PTR:54.243.154.1:
resolvable: true
timeout: 1000
server: 8.8.8.8
SRV:_https._tcp.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
TXT:txt._test.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
ip6.dnstest.io:
resolvable: true
timeout: 1000
server: 8.8.8.8
localhost:
resolvable: true
timeout: 1000
process:
apache2:
running: true
foobar:
running: false
kernel-param:
kernel.ostype:
value: Linux
mount:
/dev:
exists: true
timeout: 1000
http:
http://google.com:
status: 301
allow-insecure: false
no-follow-redirects: true
timeout: 5000
body: []
https://www.apple.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
proxy: http://127.0.0.1:8888
https://www.google.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
goss-0.4.9/integration-tests/goss/wheezy/goss-expected.yaml 0000664 0000000 0000000 00000005725 14675050513 0024125 0 ustar 00root root 0000000 0000000 file:
/etc/passwd:
exists: true
mode: "0644"
owner: root
group: root
filetype: file
contents: []
/tmp/goss/foobar:
exists: false
contents: []
package:
apache2:
installed: true
versions:
- 2.2.22-13+deb7u13
foobar:
installed: false
vim-tiny:
installed: false
addr:
tcp://httpbin:22:
reachable: false
timeout: 1000
tcp://httpbin:80:
reachable: true
timeout: 1000
udp://8.8.8.8:53:
reachable: true
timeout: 1000
port:
tcp:80:
listening: true
ip:
- 0.0.0.0
tcp:9999:
listening: false
ip: []
tcp6:80:
listening: false
ip: []
service:
apache2:
enabled: true
running: true
foobar:
enabled: false
running: false
user:
foobar:
exists: false
www-data:
exists: true
uid: 33
gid: 33
groups:
- www-data
home: /var/www
shell: /bin/sh
group:
foobar:
exists: false
www-data:
exists: true
gid: 33
command:
echo 'hi':
exit-status: 0
stdout:
- hi
stderr: ""
timeout: 10000
foobar:
exit-status: 127
stdout: ""
stderr:
- 'sh: 1: foobar: not found'
timeout: 10000
dns:
CAA:dnstest.io:
resolvable: true
addrs:
- 0 issue comodoca.com
- 0 issue letsencrypt.org
- 0 issuewild ;
timeout: 1000
server: 8.8.8.8
CNAME:c.dnstest.io:
resolvable: true
addrs:
- a.dnstest.io.
timeout: 1000
server: 8.8.8.8
MX:dnstest.io:
resolvable: true
addrs:
- 10 b.dnstest.io.
- 5 a.dnstest.io.
timeout: 1000
server: 8.8.8.8
NS:dnstest.io:
resolvable: true
addrs:
- ns1.dnstest.io.
- ns2.dnstest.io.
timeout: 1000
server: 8.8.8.8
PTR:54.243.154.1:
resolvable: true
addrs:
- ec2-54-243-154-1.compute-1.amazonaws.com.
timeout: 1000
server: 8.8.8.8
SRV:_https._tcp.dnstest.io:
resolvable: true
addrs:
- 0 5 443 a.dnstest.io.
- 10 10 443 b.dnstest.io.
timeout: 1000
server: 8.8.8.8
TXT:txt._test.dnstest.io:
resolvable: true
addrs:
- Hello DNS
timeout: 1000
server: 8.8.8.8
ip6.dnstest.io:
resolvable: true
addrs:
- 2404:6800:4001:807::200e
timeout: 1000
server: 8.8.8.8
localhost:
resolvable: true
addrs:
- 127.0.0.1
- ::1
timeout: 1000
process:
apache2:
running: true
foobar:
running: false
kernel-param:
kernel.ostype:
value: Linux
mount:
/dev:
exists: true
opts:
- rw
- nosuid
vfs-opts:
- rw
source: tmpfs
filesystem: tmpfs
timeout: 1000
http:
http://google.com:
status: 301
allow-insecure: false
no-follow-redirects: true
timeout: 5000
body: []
https://www.apple.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
proxy: http://127.0.0.1:8888
https://www.google.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 5000
body: []
goss-0.4.9/integration-tests/goss/wheezy/goss.yaml 0000664 0000000 0000000 00000000744 14675050513 0022322 0 ustar 00root root 0000000 0000000 ---
service:
autofs:
enabled: false
running: false
user:
www-data:
exists: true
uid: 33
gid: 33
groups:
- www-data
home: "/var/www"
group:
www-data:
exists: true
gid: 33
process:
apache2:
running: true
port:
tcp:80:
listening: true
ip:
- '0.0.0.0'
addr:
tcp://127.0.0.1:80:
reachable: true
timeout: 500
local-address: 127.0.0.1
gossfile:
"../goss-s*.yaml": {}
bypath:
file: "../goss-dummy.yaml"
goss-0.4.9/integration-tests/goss/windows/ 0000775 0000000 0000000 00000000000 14675050513 0020635 5 ustar 00root root 0000000 0000000 goss-0.4.9/integration-tests/goss/windows/commands/ 0000775 0000000 0000000 00000000000 14675050513 0022436 5 ustar 00root root 0000000 0000000 goss-0.4.9/integration-tests/goss/windows/commands/add.goss.yaml 0000664 0000000 0000000 00000000362 14675050513 0025025 0 ustar 00root root 0000000 0000000 ---
# TODO: coverage for the add {test} permutations
command:
"add addr 127.0.0.1":
exit-status: 0
exec: release\goss-windows-amd64 --use-alpha=1 add addr 127.0.0.1
stdout:
- "timeout: 500"
stderr: []
timeout: 5000
goss-0.4.9/integration-tests/goss/windows/commands/autoadd.goss.yaml 0000664 0000000 0000000 00000000414 14675050513 0025714 0 ustar 00root root 0000000 0000000 ---
command:
"autoadd Administrator":
exit-status: 0
exec: release\goss-windows-amd64 --use-alpha=1 autoadd Administrator
stdout:
- 'user:'
- ' name: Administrator'
stderr: []
timeout: 5000
# needs implementation
skip: true
goss-0.4.9/integration-tests/goss/windows/commands/help.goss.yaml 0000664 0000000 0000000 00000000215 14675050513 0025222 0 ustar 00root root 0000000 0000000 ---
command:
help:
exit-status: 0
exec: release\goss-windows-amd64 help
stdout:
- alpha
stderr: []
timeout: 5000
goss-0.4.9/integration-tests/goss/windows/commands/validate-input.yaml 0000664 0000000 0000000 00000000060 14675050513 0026244 0 ustar 00root root 0000000 0000000 ---
file:
non-existent.txt:
exists: false
goss-0.4.9/integration-tests/goss/windows/commands/validate.goss.yaml 0000664 0000000 0000000 00000000506 14675050513 0026066 0 ustar 00root root 0000000 0000000 ---
# TODO: coverage for the add {test} permutations
command:
"validate":
exit-status: 0
exec: "release\\goss-windows-amd64 --use-alpha=1 -g integration-tests/goss/windows/commands/validate-input.yaml validate"
stdout:
- 'Count: 1'
- 'Failed: 0'
- 'Skipped: 0'
stderr: []
timeout: 5000
goss-0.4.9/integration-tests/goss/windows/tests/ 0000775 0000000 0000000 00000000000 14675050513 0021777 5 ustar 00root root 0000000 0000000 goss-0.4.9/integration-tests/goss/windows/tests/addr.goss.yaml 0000664 0000000 0000000 00000000236 14675050513 0024550 0 ustar 00root root 0000000 0000000 ---
addr:
tcp://google.com:443:
reachable: true
timeout: 1000
tcp://127.0.0.1:135:
reachable: true
timeout: 1000
local-address: true
goss-0.4.9/integration-tests/goss/windows/tests/command.goss.yaml 0000664 0000000 0000000 00000001644 14675050513 0025260 0 ustar 00root root 0000000 0000000 ---
command:
hello world:
exit-status: 0
exec: "echo hello world"
stdout:
- hello world
stderr: []
timeout: 10000
wrap a powershell - expect 0 because travis does not restrict anonymous logins:
exec: powershell -noprofile -noninteractive -command (get-itemproperty -path 'HKLM:/SYSTEM/CurrentControlSet/Control/Lsa/').restrictanonymous
exit-status: 0
stdout:
- "0"
stderr: []
timeout: 10000
wrap a powershell with quotes - expect 0 because travis does not restrict anonymous logins:
exec: powershell -noprofile -noninteractive -command "(get-itemproperty -path 'HKLM:/SYSTEM/CurrentControlSet/Control/Lsa/').restrictanonymous"
exit-status: 0
stdout:
- "0"
stderr: []
timeout: 10000
powershell with quotes:
exec: powershell /c "(echo '{"b":2, "a":1}' | ConvertFrom-json).a"
exit-status: 0
stdout:
- "1"
stderr: []
timeout: 10000
goss-0.4.9/integration-tests/goss/windows/tests/dns.goss.yaml 0000664 0000000 0000000 00000000143 14675050513 0024417 0 ustar 00root root 0000000 0000000 ---
dns:
localhost:
resolvable: true
addrs:
- "127.0.0.1"
- ::1
timeout: 500
goss-0.4.9/integration-tests/goss/windows/tests/file.goss.yaml 0000664 0000000 0000000 00000000664 14675050513 0024562 0 ustar 00root root 0000000 0000000 ---
file:
integration-tests\goss\testdata\static-file.txt:
exists: true
# mode: "0000" # not applicable on Windows
# user: "" # not applicable on Windows
# group: "" # not applicable on Windows
size: 21
filetype: file
md5: dc9a07ca9789f866d21d544fe5651954
sha256: aa8b1b4a0d9bf174f5019c8f8a9568858ee2bdf8e0ad16aec54417d49b48df49
contents:
- "nothing to see here"
- "/nothing.*here/"
goss-0.4.9/integration-tests/goss/windows/tests/gossfile.goss.yaml 0000664 0000000 0000000 00000000751 14675050513 0025453 0 ustar 00root root 0000000 0000000 ---
# paths are relative to the goss file that includes the gossfile directive.
gossfile:
addr.goss.yaml: {}
command.goss.yaml: {}
dns.goss.yaml: {}
file.goss.yaml: {}
# don't use gossfile; avoid self-referencing
# gossfile.goss.yaml: {}
group.goss.yaml: {}
http.goss.yaml: {}
interface.goss.yaml: {}
# kernel-param.na-goss.yaml: {}
mount.goss.yaml: {}
package.goss.yaml: {}
port.goss.yaml: {}
process.goss.yaml: {}
service.goss.yaml: {}
user.goss.yaml: {}
goss-0.4.9/integration-tests/goss/windows/tests/group.goss.yaml 0000664 0000000 0000000 00000000202 14675050513 0024763 0 ustar 00root root 0000000 0000000 ---
group:
'Local Users':
exists: true
gid: 0 # not applicable on Windows
skip: true # TODO: implement on Windows
goss-0.4.9/integration-tests/goss/windows/tests/http.goss.yaml 0000664 0000000 0000000 00000000437 14675050513 0024620 0 ustar 00root root 0000000 0000000 ---
http:
https://google.com:
status: 200
allow-insecure: false
no-follow-redirects: false
timeout: 10000
request-headers:
- "Content-Type: text/html"
headers:
- "Content-Type: text/html"
body:
- "google"
username: ""
password: ""
goss-0.4.9/integration-tests/goss/windows/tests/interface.goss.yaml 0000664 0000000 0000000 00000000724 14675050513 0025600 0 ustar 00root root 0000000 0000000 ---
interface:
'Loopback Pseudo-Interface 1':
exists: false
addrs:
- '127.0.0.1'
mtu: 1500
skip: true
# https://docs.microsoft.com/en-us/powershell/module/nettcpip/get-netipinterface?view=win10-ps
# Get-NetIPInterface
# https://docs.microsoft.com/en-us/powershell/module/netadapter/get-netadapter?view=win10-ps
# Get-NetAdapter - and would then need to choose one with a name that will work in CI, and skip this test when running locally, etc
goss-0.4.9/integration-tests/goss/windows/tests/kernel-param.na-goss.yaml 0000664 0000000 0000000 00000000153 14675050513 0026606 0 ustar 00root root 0000000 0000000 ---
# Not applicable on Windows
kernel-param:
notapplicable.on-windows:
value: foobar
skip: true
goss-0.4.9/integration-tests/goss/windows/tests/mount.goss.yaml 0000664 0000000 0000000 00000000322 14675050513 0024774 0 ustar 00root root 0000000 0000000 ---
mount:
'c:':
exists: true
filesystem: ntfs
opts: [] # not applicable on Windows
source: '' # not applicable on Windows
usage:
lt: 95
skip: true # needs implementation
goss-0.4.9/integration-tests/goss/windows/tests/package.goss.yaml 0000664 0000000 0000000 00000000546 14675050513 0025235 0 ustar 00root root 0000000 0000000 ---
package:
golang:
# required attributes
installed: true
# optional attributes
versions:
- 1.14.1
# needs implementation
# needs discussion + design
# support question for:
# * chocolatey https://chocolatey.org
# * scoop https://scoop.sh/
# * winget-cli https://github.com/microsoft/winget-cli
skip: true
goss-0.4.9/integration-tests/goss/windows/tests/port.goss.yaml 0000664 0000000 0000000 00000000152 14675050513 0024617 0 ustar 00root root 0000000 0000000 ---
port:
tcp:135:
listening: true
ip:
- 0.0.0.0
# needs implementation
skip: true
goss-0.4.9/integration-tests/goss/windows/tests/process.goss.yaml 0000664 0000000 0000000 00000000204 14675050513 0025307 0 ustar 00root root 0000000 0000000 ---
process:
'wininit.exe':
running: true
# note - must use .exe suffix on Windows currently
wininit:
running: false
goss-0.4.9/integration-tests/goss/windows/tests/service.goss.yaml 0000664 0000000 0000000 00000000145 14675050513 0025275 0 ustar 00root root 0000000 0000000 ---
service:
MSDTC:
enabled: true
running: true
# needs implementation
skip: true
goss-0.4.9/integration-tests/goss/windows/tests/user.goss.yaml 0000664 0000000 0000000 00000000373 14675050513 0024616 0 ustar 00root root 0000000 0000000 ---
user:
Administrator:
exists: true
uid: 65534 # not applicable on Windows
gid: 65534 # not applicable on Windows
groups:
- nfsnobody
home: /var/lib/nfs
shell: /sbin/nologin
# needs implementation
skip: true
goss-0.4.9/integration-tests/run-serve-tests.sh 0000775 0000000 0000000 00000007751 14675050513 0021627 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
# shellcheck source=../ci/lib/setup.sh
source "$(dirname "${BASH_SOURCE[0]}")/../ci/lib/setup.sh" || exit 67
platform_spec="${1:?Must supply name of release binary to build e.g. goss-linux-amd64}"
# Split platform_spec into platform/arch segments
IFS='- ' read -r -a segments <<< "${platform_spec}"
os="${segments[0]}"
arch="${segments[1]}"
find_open_port() {
local startAt="${1:?"Supply start of port range"}"
local endAt="${2:?"Supply end of port range"}"
local how_many="${3:-"1"}"
if [[ "$(go env GOOS)" == "windows" ]]; then
# ss (see unix implementation below) doesn't exist on Windows, so fall back on just choosing a random number inside the range (since netstat is _slow_).
# Thanks also to https://blog.netspi.com/15-ways-to-bypass-the-powershell-execution-policy/
powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "integration-tests/Find-AvailablePort.ps1 -startAt ${startAt} -endAt ${endAt}"
elif [[ "$(go env GOOS)" == "darwin" ]]; then
jot -n -r 1 1025 65535
else
# Thanks to https://unix.stackexchange.com/questions/55913/whats-the-easiest-way-to-find-an-unused-local-port
comm -23 \
<(seq "${startAt}" "${endAt}" | sort) \
<(ss -tan | tail -n +2 | awk '{print $4}' | cut -d':' -f2 | sort -u) |
shuf -n "${how_many}" ||
shuf -i "${startAt}-${endAt}" -n "${how_many}"
fi
}
cleanup() {
binary_name="$(basename "${GOSS_BINARY}")"
log_info "Killing goss serve process to clean up, exit code for tests was ${?}..."
if [[ "${os}" == "darwin" ]]; then
killall "${binary_name}"
elif [[ "${os}" == "linux" ]]; then
killall "${binary_name}"
elif [[ "${os}" == "windows" ]]; then
# Can't use killall, doesn't exist on Windows. Also would interfere with concurrent runs.
ps -W |
awk "/${binary_name}/,NF=1" |
xargs kill
fi
exit "${ret:-0}"
}
trap cleanup EXIT
repo_root="$(git rev-parse --show-toplevel)"
export GOSS_BINARY="${repo_root}/release/goss-${platform_spec}"
log_info "Using: '${GOSS_BINARY}', cwd: '$(pwd)'"
export GOSS_USE_ALPHA=1
open_port="$(find_open_port 1025 65335)"
echo "${open_port}"
args=(
"-g=${repo_root}/integration-tests/goss/goss-serve.yaml"
"serve"
"--listen-addr=127.0.0.1:${open_port}"
)
log_action "\nTesting \`${GOSS_BINARY} ${args[*]}\` ...\n"
"${GOSS_BINARY}" "${args[@]}" &
base_url="http://127.0.0.1:${open_port}"
[[ "$(go env GOOS)" == "darwin" ]] && sleep 2
assert_response_contains() {
local url="${1:?"1st arg: url"}"
local test_name="${2:?"2nd arg: test name"}"
local expectation="${3:?"3rd arg: response body match"}"
local accept_header="${4:-""}"
curl_args=("--silent")
[[ -n "${accept_header:-}" ]] && curl_args+=("-H" "Accept: ${accept_header}")
curl_args+=("${url}")
log_info "curl ${curl_args[*]}"
curl="curl"
[[ "$(go env GOOS)" == "windows" ]] && curl="curl.exe"
response="$(${curl} "${curl_args[@]}")"
if grep --quiet "${expectation}" <<<"${response}"; then
log_success "Passed: ${test_name}"
return 0
fi
log_error "Failed: ${test_name}"
log_error " Expected: ${expectation}"
log_error " Response: ${response}"
return 1
}
failure="false"
on_test_failure() {
failure="true"
}
# /healthz endpoint
assert_response_contains "${base_url}/healthz" "no accept header" "Count: 2, Failed: 0, Skipped: 0" "" || on_test_failure
assert_response_contains "${base_url}/healthz" "tap accept header" "Count: 2, Failed: 0, Skipped: 0" "application/vnd.goss-documentation" || on_test_failure
assert_response_contains "${base_url}/healthz" "json accept header" "\"failed-count\":0" "application/json" || on_test_failure
assert_response_contains "${base_url}/healthz" "prometheus accept header" "goss_tests_outcomes_total" "application/vnd.goss-prometheus" || on_test_failure
# /metrics - specific prometheus metrics endpoint
assert_response_contains "${base_url}/metrics" "prometheus accept header" "goss_tests_outcomes_total" "" || on_test_failure
[[ "${failure}" == "true" ]] && log_fatal "Test(s) failed, check output above."
goss-0.4.9/integration-tests/run-tests-alpha.sh 0000775 0000000 0000000 00000001671 14675050513 0021563 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
# shellcheck source=../ci/lib/setup.sh
source "$(dirname "${BASH_SOURCE[0]}")/../ci/lib/setup.sh" || exit 67
platform_spec="${1:?"Must supply name of release binary to build e.g. goss-linux-amd64"}"
# Split platform_spec into platform/arch segments
IFS='- ' read -r -a segments <<< "${platform_spec}"
os="${segments[0]}"
arch="${segments[1]}"
if [[ "${segments[0]}" == "alpha" ]]; then
os="${segments[1]}"
arch="${segments[2]}"
fi
repo_root="$(git rev-parse --show-toplevel)"
export GOSS_BINARY="${repo_root}/release/goss-${platform_spec}"
log_info "Using: '${GOSS_BINARY}', cwd: '$(pwd)', os: ${os}"
readarray -t goss_test_files < <(find integration-tests -type f -name "*.goss.yaml" | grep "${os}" | sort | uniq)
export GOSS_USE_ALPHA=1
for file in "${goss_test_files[@]}"; do
args=(
"-g=${file}"
"validate"
)
log_action -e "\nTesting \`${GOSS_BINARY} ${args[*]}\` ...\n"
"${GOSS_BINARY}" "${args[@]}"
done
goss-0.4.9/integration-tests/run-validate-tests.sh 0000775 0000000 0000000 00000002412 14675050513 0022261 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
# shellcheck source=../ci/lib/setup.sh
source "$(dirname "${BASH_SOURCE[0]}")/../ci/lib/setup.sh" || exit 67
platform_spec="${1:?"Must supply name of release binary to build e.g. goss-linux-amd64"}"
# Split platform_spec into platform/arch segments
IFS='- ' read -r -a segments <<< "${platform_spec}"
os="${segments[0]}"
arch="${segments[1]}"
if [[ "${os}" == "linux" ]]; then
echo "OS is ${os}. This script is not for running tests on the different flavours of linux."
echo "Linux is exercised via the integration-tests/test.sh currently, because linux can be"
echo "verified via docker containers; macOS and Windows cannot."
echo "This script is for macOS and Windows, and runs tests that are expected to pass on"
echo "Travis-CI provided images, running nakedly (no containerisation) on the hosts there."
exit 1
fi
repo_root="$(git rev-parse --show-toplevel)"
export GOSS_BINARY="${repo_root}/release/goss-${platform_spec}"
log_info "Using: '${GOSS_BINARY}', cwd: '$(pwd)', os: ${os}"
export GOSS_USE_ALPHA=1
for file in `find integration-tests -type f -name "*.goss.yaml" | grep "${os}" | sort | uniq`; do
args=(
"-g=${file}"
"validate"
)
log_action "\nTesting \`${GOSS_BINARY} ${args[*]}\` ...\n"
"${GOSS_BINARY}" "${args[@]}"
done
goss-0.4.9/integration-tests/test.sh 0000775 0000000 0000000 00000006255 14675050513 0017516 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
# shellcheck source=../ci/lib/setup.sh
source "$(dirname "${BASH_SOURCE[0]}")/../ci/lib/setup.sh" || exit 67
# preserve current behaviour
set -x
os="${1:?"Need OS as 1st arg. e.g. alpine arch centos7 rockylinux9 trusty wheezy"}"
arch="${2:?"Need arch as 2nd arg. e.g. amd64 386"}"
vars_inline="{inline: bar, overwrite: bar}"
container_repository="aelsabbahy"
# setup places us inside repo-root; this preserves current behaviour with least change.
cd integration-tests
cp "../release/goss-linux-$arch" "goss/$os/"
# Run build if Dockerfile has changed but hasn't been pushed to dockerhub
if ! md5sum -c "Dockerfile_${os}.md5"; then
docker build -t "$container_repository/goss_${os}:latest" - < "Dockerfile_$os"
# Pull if image doesn't exist locally
elif ! docker images | grep "$container_repository/goss_$os";then
docker pull "$container_repository/goss_$os"
fi
container_name="goss_int_test_${os}_${arch}"
docker_exec() {
docker exec "$container_name" "$@"
}
# Cleanup any old containers
if docker ps -a | grep "$container_name";then
docker rm -vf "$container_name"
fi
# Setup local httbin
# FIXME: this is a quick hack to fix intermittent CI issues
network=goss-test
docker network create --driver bridge --subnet '172.19.0.0/16' $network
docker run -d --name httpbin --network $network kennethreitz/httpbin
opts=(--env OS=$os --cap-add SYS_ADMIN -v "$PWD/goss:/goss" -d --name "$container_name" --security-opt seccomp:unconfined --security-opt label:disable --privileged)
id=$(docker run "${opts[@]}" --network $network "$container_repository/goss_$os" /sbin/init)
ip=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' "$id")
trap "rv=\$?; docker rm -vf $id;docker rm -vf httpbin;docker network rm $network; exit \$rv" INT TERM EXIT
# Give httpd time to start up, adding 1 second to see if it helps with intermittent CI failures
[[ $os != "arch" ]] && docker_exec "/goss/$os/goss-linux-$arch" -g "/goss/goss-wait.yaml" validate -r 10s -s 100ms && sleep 1
#out=$(docker exec "$container_name" bash -c "time /goss/$os/goss-linux-$arch -g /goss/$os/goss.yaml validate")
out=$(docker_exec "/goss/$os/goss-linux-$arch" --vars "/goss/vars.yaml" --vars-inline "$vars_inline" -g "/goss/$os/goss.yaml" validate)
echo "$out"
if [[ $os == "arch" ]]; then
egrep -q 'Count: 104, Failed: 0, Skipped: 3' <<<"$out"
else
egrep -q 'Count: 125, Failed: 0, Skipped: 5' <<<"$out"
fi
if [[ ! $os == "arch" ]]; then
docker_exec /goss/generate_goss.sh "$os" "$arch"
# docker exec $container_name bash -c "cp /goss/${os}/goss-generated-$arch.yaml /goss/${os}/goss-expected.yaml"
docker_exec diff -wu "/goss/${os}/goss-expected.yaml" "/goss/${os}/goss-generated-$arch.yaml"
# docker exec $container_name bash -c "cp /goss/${os}/goss-aa-generated-$arch.yaml /goss/${os}/goss-aa-expected.yaml"
docker_exec diff -wu "/goss/${os}/goss-aa-expected.yaml" "/goss/${os}/goss-aa-generated-$arch.yaml"
docker_exec /goss/generate_goss.sh "$os" "$arch" -q
# docker exec $container_name bash -c "cp /goss/${os}/goss-generated-$arch.yaml /goss/${os}/goss-expected-q.yaml"
docker_exec diff -wu "/goss/${os}/goss-expected-q.yaml" "/goss/${os}/goss-generated-$arch.yaml"
fi
#docker rm -vf goss_int_test_$os
goss-0.4.9/logs.go 0000664 0000000 0000000 00000001666 14675050513 0014011 0 ustar 00root root 0000000 0000000 package goss
import (
"fmt"
"io"
"log"
"os"
"strings"
"time"
"github.com/goss-org/goss/util"
"github.com/hashicorp/logutils"
)
func setLogLevel(c *util.Config) error {
filter := &logutils.LevelFilter{
Levels: []logutils.LogLevel{"TRACE", "DEBUG", "INFO", "WARN", "ERROR"},
MinLevel: logutils.LogLevel("INFO"),
Writer: os.Stderr,
}
log.SetFlags(0) // Turn off standard timestamp flags
log.SetOutput(×tampedWriter{filter})
for _, lvl := range filter.Levels {
cLvl := strings.ToUpper(c.LogLevel)
if string(lvl) == cLvl {
filter.MinLevel = lvl
log.Printf("[DEBUG] Setting log level to %v", cLvl)
return nil
}
}
return fmt.Errorf("Unsupported log level: %s", c.LogLevel)
}
type timestampedWriter struct {
wrappedWriter io.Writer
}
func (t *timestampedWriter) Write(b []byte) (int, error) {
timestamp := time.Now().UTC().Format(time.RFC3339)
return fmt.Fprintf(t.wrappedWriter, "%s %s", timestamp, b)
}
goss-0.4.9/matcher_test.go 0000664 0000000 0000000 00000003644 14675050513 0015525 0 ustar 00root root 0000000 0000000 //go:build linux
package goss
import (
"bytes"
"flag"
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"testing"
"github.com/goss-org/goss/util"
"github.com/stretchr/testify/assert"
)
var (
// This will generate the "golden files" prior to running the tests.
// helpful when the output is changed and a user doesn't want to update every single expectation file by hand
update = flag.Bool("update", false, "update the golden files of this test")
)
func TestMain(m *testing.M) {
flag.Parse()
os.Exit(m.Run())
}
func TestMatchers(t *testing.T) {
files, err := filepath.Glob(filepath.Join("testdata", "out_matching_*"))
if err != nil {
t.Fatal(err)
}
for _, outFile := range files {
outFile := outFile
parts := strings.Split(outFile, ".")
specName := fmt.Sprintf("%s.yaml", strings.TrimPrefix(parts[0], "testdata/out_"))
specFile := filepath.Join("testdata", specName)
outFormat := parts[2]
wantCode, err := strconv.Atoi(parts[1])
if err != nil {
t.Fatal(err)
}
tn := outFile
t.Run(tn, func(t *testing.T) {
output := &bytes.Buffer{}
cfg, err := util.NewConfig(
util.WithOutputFormat(outFormat),
util.WithResultWriter(output),
util.WithSpecFile(specFile),
util.WithFormatOptions("sort", "pretty"),
)
if err != nil {
t.Fatal(err)
}
exitCode, err := Validate(cfg)
if err != nil {
t.Fatal(err)
}
actualOut := output.String()
actualOut = sanitizeOutput(actualOut)
if *update {
os.WriteFile(outFile, []byte(actualOut), 0644)
}
wantOutB, err := os.ReadFile(outFile)
if err != nil {
t.Fatal(err)
}
wantOut := string(wantOutB)
if actualOut != wantOut {
assert.Equal(t, wantOut, actualOut)
}
if exitCode != wantCode {
assert.Equal(t, wantCode, exitCode)
}
})
}
}
func sanitizeOutput(s string) string {
// Remove duration time
re := regexp.MustCompile(`\d\.\d\d\ds`)
return re.ReplaceAllString(s, "")
}
goss-0.4.9/matchers/ 0000775 0000000 0000000 00000000000 14675050513 0014313 5 ustar 00root root 0000000 0000000 goss-0.4.9/matchers/and.go 0000664 0000000 0000000 00000002051 14675050513 0015402 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
)
type AndMatcher struct {
fakeOmegaMatcher
Matchers []GossMatcher
// state
firstFailedMatcher GossMatcher
}
func And(ms ...GossMatcher) GossMatcher {
return &AndMatcher{Matchers: ms}
}
func (m *AndMatcher) Match(actual interface{}) (success bool, err error) {
m.firstFailedMatcher = nil
for _, matcher := range m.Matchers {
success, err := matcher.Match(actual)
if !success || err != nil {
m.firstFailedMatcher = matcher
return false, err
}
}
return true, nil
}
func (m *AndMatcher) FailureResult(actual interface{}) MatcherResult {
return m.firstFailedMatcher.FailureResult(actual)
}
func (m *AndMatcher) NegatedFailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "not to satisfy all of these matchers",
Expected: m.Matchers,
}
}
func (m *AndMatcher) MarshalJSON() ([]byte, error) {
if len(m.Matchers) == 1 {
return json.Marshal(m.Matchers[0])
}
j := make(map[string]interface{})
j["and"] = m.Matchers
return json.Marshal(j)
}
goss-0.4.9/matchers/be_numerically_matcher.go 0000664 0000000 0000000 00000002761 14675050513 0021345 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
"fmt"
"github.com/onsi/gomega/matchers"
)
type BeNumericallyMatcher struct {
fakeOmegaMatcher
Comparator string
CompareTo []interface{}
}
func BeNumerically(comparator string, compareTo ...interface{}) GossMatcher {
return &BeNumericallyMatcher{
Comparator: comparator,
CompareTo: compareTo,
}
}
func (m *BeNumericallyMatcher) Match(actual interface{}) (success bool, err error) {
comparator, err := strToSymbol(m.Comparator)
if err != nil {
return false, err
}
matcher := &matchers.BeNumericallyMatcher{
Comparator: comparator,
CompareTo: m.CompareTo,
}
return matcher.Match(actual)
}
func (m *BeNumericallyMatcher) FailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: fmt.Sprintf("to be numerically %s", m.Comparator),
Expected: m.CompareTo[0],
}
}
func (m *BeNumericallyMatcher) NegatedFailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: fmt.Sprintf("not to be numerically %s", m.Comparator),
Expected: m.CompareTo[0],
}
}
func (m *BeNumericallyMatcher) MarshalJSON() ([]byte, error) {
j := make(map[string]interface{})
j[m.Comparator] = m.CompareTo[0]
return json.Marshal(j)
}
func strToSymbol(s string) (string, error) {
comparator, ok := map[string]string{
"gt": ">",
"ge": ">=",
"lt": "<",
"le": "<=",
"eq": "==",
}[s]
if !ok {
return "", fmt.Errorf("Unknown comparator: %s", s)
}
return comparator, nil
}
goss-0.4.9/matchers/consist_of.go 0000664 0000000 0000000 00000002267 14675050513 0017017 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
"github.com/onsi/gomega/matchers"
"github.com/samber/lo"
)
type ConsistOfMatcher struct {
matchers.ConsistOfMatcher
}
func ConsistOf(elements ...interface{}) GossMatcher {
return &ConsistOfMatcher{
matchers.ConsistOfMatcher{
Elements: elements,
},
}
}
func (m *ConsistOfMatcher) FailureResult(actual interface{}) MatcherResult {
missingElements := getUnexported(m, "missingElements")
extraElements := getUnexported(m, "extraElements")
missingEl, ok := missingElements.([]interface{})
var foundElements any
if ok {
foundElements, _ = lo.Difference(m.Elements, missingEl)
}
return MatcherResult{
Actual: actual,
Message: "to consist of",
Expected: m.Elements,
MissingElements: missingElements,
ExtraElements: extraElements,
FoundElements: foundElements,
}
}
func (m *ConsistOfMatcher) NegatedFailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "not to consist of",
Expected: m.Elements,
}
}
func (m *ConsistOfMatcher) MarshalJSON() ([]byte, error) {
j := make(map[string]interface{})
j["consist-of"] = m.Elements
return json.Marshal(j)
}
goss-0.4.9/matchers/contain_element_matcher.go 0000664 0000000 0000000 00000001547 14675050513 0021520 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
"github.com/onsi/gomega/matchers"
)
type ContainElementMatcher struct {
matchers.ContainElementMatcher
}
func ContainElement(element interface{}) GossMatcher {
return &ContainElementMatcher{
matchers.ContainElementMatcher{
Element: element,
},
}
}
func (m *ContainElementMatcher) FailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "to contain element matching",
Expected: m.Element,
}
}
func (m *ContainElementMatcher) NegatedFailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "not to contain element matching",
Expected: m.Element,
}
}
func (m *ContainElementMatcher) MarshalJSON() ([]byte, error) {
j := make(map[string]interface{})
j["contain-element"] = m.Element
return json.Marshal(j)
}
goss-0.4.9/matchers/contain_elements_matcher.go 0000664 0000000 0000000 00000002265 14675050513 0021701 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
"github.com/onsi/gomega/matchers"
"github.com/samber/lo"
)
type ContainElementsMatcher struct {
matchers.ContainElementsMatcher
}
func ContainElements(elements ...interface{}) GossMatcher {
return &ContainElementsMatcher{
matchers.ContainElementsMatcher{
Elements: elements,
},
}
}
func (m *ContainElementsMatcher) FailureResult(actual interface{}) MatcherResult {
missingElements := getUnexported(m, "missingElements")
missingEl, ok := missingElements.([]interface{})
var foundElements any
if ok {
foundElements, _ = lo.Difference(m.Elements, missingEl)
}
return MatcherResult{
Actual: actual,
Message: "to contain elements matching",
Expected: m.Elements,
MissingElements: missingElements,
FoundElements: foundElements,
}
}
func (m *ContainElementsMatcher) NegatedFailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "not to contain elements matching",
Expected: m.Elements,
}
}
func (m *ContainElementsMatcher) MarshalJSON() ([]byte, error) {
j := make(map[string]interface{})
j["contain-elements"] = m.Elements
return json.Marshal(j)
}
goss-0.4.9/matchers/contain_substring_matcher.go 0000664 0000000 0000000 00000001606 14675050513 0022103 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
"github.com/onsi/gomega/matchers"
)
type ContainSubstringMatcher struct {
matchers.ContainSubstringMatcher
}
func ContainSubstring(substr string, args ...interface{}) GossMatcher {
return &ContainSubstringMatcher{
matchers.ContainSubstringMatcher{
Substr: substr,
Args: args,
},
}
}
func (m *ContainSubstringMatcher) FailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "to contain substring",
Expected: m.Substr,
}
}
func (m *ContainSubstringMatcher) NegatedFailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "not to contain substring",
Expected: m.Substr,
}
}
func (m *ContainSubstringMatcher) MarshalJSON() ([]byte, error) {
j := make(map[string]interface{})
j["contain-substring"] = m.Substr
return json.Marshal(j)
}
goss-0.4.9/matchers/equal_matcher.go 0000664 0000000 0000000 00000001300 14675050513 0017446 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
"github.com/onsi/gomega/matchers"
)
type EqualMatcher struct {
matchers.EqualMatcher
}
func Equal(element interface{}) GossMatcher {
return &EqualMatcher{
matchers.EqualMatcher{
Expected: element,
},
}
}
func (m *EqualMatcher) FailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "to equal",
Expected: m.Expected,
}
}
func (m *EqualMatcher) NegatedFailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "not to equal",
Expected: m.Expected,
}
}
func (m *EqualMatcher) MarshalJSON() ([]byte, error) {
return json.Marshal(m.Expected)
}
goss-0.4.9/matchers/have_key_matcher.go 0000664 0000000 0000000 00000001402 14675050513 0020135 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
"github.com/onsi/gomega/matchers"
)
type HaveKeyMatcher struct {
matchers.HaveKeyMatcher
}
func HaveKey(key interface{}) GossMatcher {
return &HaveKeyMatcher{
matchers.HaveKeyMatcher{
Key: key,
},
}
}
func (m *HaveKeyMatcher) FailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "to have key matching",
Expected: m.Key,
}
}
func (m *HaveKeyMatcher) NegatedFailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "not to have key matching",
Expected: m.Key,
}
}
func (m *HaveKeyMatcher) MarshalJSON() ([]byte, error) {
j := make(map[string]interface{})
j["have-key"] = m.Key
return json.Marshal(j)
}
goss-0.4.9/matchers/have_len_matcher.go 0000664 0000000 0000000 00000001372 14675050513 0020131 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
"github.com/onsi/gomega/matchers"
)
type HaveLenMatcher struct {
matchers.HaveLenMatcher
}
func HaveLen(count int) GossMatcher {
return &HaveLenMatcher{
matchers.HaveLenMatcher{
Count: count,
},
}
}
func (m *HaveLenMatcher) FailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "to have length",
Expected: m.Count,
}
}
func (m *HaveLenMatcher) NegatedFailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "not to have length",
Expected: m.Count,
}
}
func (m *HaveLenMatcher) MarshalJSON() ([]byte, error) {
j := make(map[string]interface{})
j["have-len"] = m.Count
return json.Marshal(j)
}
goss-0.4.9/matchers/have_patterns.go 0000664 0000000 0000000 00000013041 14675050513 0017504 0 ustar 00root root 0000000 0000000 package matchers
import (
"bufio"
"encoding/json"
"fmt"
"io"
"regexp"
"strings"
"github.com/onsi/gomega/format"
)
const (
maxScanTokenSize = 1024 * 1024
)
type HavePatternsMatcher struct {
fakeOmegaMatcher
Elements interface{}
missingElements []string
foundElements []string
}
func HavePatterns(elements interface{}) GossMatcher {
return &HavePatternsMatcher{
Elements: elements,
}
}
func (m *HavePatternsMatcher) Match(actual interface{}) (success bool, err error) {
t, ok := m.Elements.([]interface{})
if !ok {
return false, fmt.Errorf("HavePatterns matcher expects an array of matchers. Got:\n%s", format.Object(m.Elements, 1))
}
elements := make([]string, len(t))
for i, v := range t {
switch v := v.(type) {
case string:
elements[i] = v
default:
return false, fmt.Errorf("HavePatterns matcher expects patterns to be a string. got: \n%s", format.Object(v, 1))
}
}
notfound, err := sliceToPatterns(elements)
if err != nil {
return false, err
}
// short circuit
if len(notfound) == 0 {
return true, nil
}
var fh io.Reader
switch av := actual.(type) {
case io.Reader:
fh = av
case string:
fh = strings.NewReader(av)
case []string:
fh = strings.NewReader(strings.Join(av, "\n"))
default:
err = fmt.Errorf("Incorrect type %T", actual)
}
if err != nil {
return false, err
}
defer func() {
if rc, ok := fh.(io.ReadCloser); ok {
rc.Close()
}
}()
scanner := bufio.NewScanner(fh)
scanner.Buffer(nil, maxScanTokenSize)
var found []patternMatcher
for scanner.Scan() {
line := scanner.Text()
i := 0
for _, pat := range notfound {
if pat.Match(line) {
// Found it, but wasn't supposed to, don't mark it as found, but remove it from search
if !pat.Inverse() {
found = append(found, pat)
}
continue
}
notfound[i] = pat
i++
}
notfound = notfound[:i]
if len(notfound) == 0 {
break
}
}
if err := scanner.Err(); err != nil {
return false, err
}
for _, pat := range notfound {
// Didn't find it, but we didn't want to.. so we mark it as found
// Empty pattern should match even if input to scanner is empty
if pat.Inverse() || pat.Pattern() == "" {
found = append(found, pat)
}
}
foundSlice := patternsToSlice(found)
m.foundElements = foundSlice
if len(elements) != len(found) {
m.missingElements = subtractSlice(elements, foundSlice)
return false, nil
}
return true, nil
}
func (m *HavePatternsMatcher) FailureResult(actual interface{}) MatcherResult {
var a interface{}
switch actual.(type) {
case string, []string:
a = actual
default:
a = fmt.Sprintf("object: %T", actual)
}
return MatcherResult{
Actual: a,
Message: "to have patterns",
Expected: m.Elements,
MissingElements: m.missingElements,
FoundElements: m.foundElements,
}
}
func (m *HavePatternsMatcher) NegatedFailureResult(actual interface{}) MatcherResult {
a, ok := actual.(string)
if !ok {
a = fmt.Sprintf("object: %T", actual)
}
return MatcherResult{
Actual: a,
Message: "not to have patterns",
Expected: m.Elements,
}
}
type patternMatcher interface {
Match(string) bool
Pattern() string
Inverse() bool
}
type stringPattern struct {
pattern string
cleanPattern string
inverse bool
}
func newStringPattern(str string) *stringPattern {
var inverse bool
if strings.HasPrefix(str, "!") {
inverse = true
}
cleanPattern := strings.TrimLeft(str, "\\/!")
return &stringPattern{
pattern: str,
cleanPattern: cleanPattern,
inverse: inverse,
}
}
func (s *stringPattern) Match(str string) bool {
return strings.Contains(str, s.cleanPattern)
}
func (s *stringPattern) Pattern() string { return s.pattern }
func (s *stringPattern) Inverse() bool { return s.inverse }
type regexPattern struct {
pattern string
re *regexp.Regexp
inverse bool
}
func newRegexPattern(str string) (*regexPattern, error) {
var inverse bool
cleanStr := str
if strings.HasPrefix(str, "!") {
inverse = true
cleanStr = cleanStr[1:]
}
trimLeft := []rune{'\\', '/'}
for _, r := range trimLeft {
if rune(cleanStr[0]) == r {
cleanStr = cleanStr[1:]
break
}
}
trimRight := []rune{'/'}
for _, r := range trimRight {
if rune(cleanStr[len(cleanStr)-1]) == r {
cleanStr = cleanStr[:len(cleanStr)-1]
break
}
}
re, err := regexp.Compile(cleanStr)
return ®exPattern{
pattern: str,
re: re,
inverse: inverse,
}, err
}
func (re *regexPattern) Match(str string) bool {
return re.re.MatchString(str)
}
func (re *regexPattern) Pattern() string { return re.pattern }
func (re *regexPattern) Inverse() bool { return re.inverse }
func sliceToPatterns(slice []string) ([]patternMatcher, error) {
var patterns []patternMatcher
for _, s := range slice {
if (strings.HasPrefix(s, "/") || strings.HasPrefix(s, "!/")) && strings.HasSuffix(s, "/") {
pat, err := newRegexPattern(s)
if err != nil {
return nil, err
}
patterns = append(patterns, pat)
} else {
patterns = append(patterns, newStringPattern(s))
}
}
return patterns, nil
}
func patternsToSlice(patterns []patternMatcher) []string {
var slice []string
for _, p := range patterns {
slice = append(slice, p.Pattern())
}
return slice
}
func subtractSlice(x, y []string) []string {
m := make(map[string]bool)
for _, y := range y {
m[y] = true
}
var ret []string
for _, x := range x {
if m[x] {
continue
}
ret = append(ret, x)
}
return ret
}
func (matcher *HavePatternsMatcher) MarshalJSON() ([]byte, error) {
j := make(map[string]interface{})
j["have-patterns"] = matcher.Elements
return json.Marshal(j)
}
goss-0.4.9/matchers/have_prefix_matcher.go 0000664 0000000 0000000 00000001504 14675050513 0020645 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
"github.com/onsi/gomega/matchers"
)
type HavePrefixMatcher struct {
matchers.HavePrefixMatcher
}
func HavePrefix(prefix string, args ...interface{}) GossMatcher {
return &HavePrefixMatcher{
matchers.HavePrefixMatcher{
Prefix: prefix,
Args: args,
},
}
}
func (m *HavePrefixMatcher) FailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "to have prefix",
Expected: m.Prefix,
}
}
func (m *HavePrefixMatcher) NegatedFailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "not to have prefix",
Expected: m.Prefix,
}
}
func (m *HavePrefixMatcher) MarshalJSON() ([]byte, error) {
j := make(map[string]interface{})
j["have-prefix"] = m.Prefix
return json.Marshal(j)
}
goss-0.4.9/matchers/have_suffix_matcher.go 0000664 0000000 0000000 00000001504 14675050513 0020654 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
"github.com/onsi/gomega/matchers"
)
type HaveSuffixMatcher struct {
matchers.HaveSuffixMatcher
}
func HaveSuffix(prefix string, args ...interface{}) GossMatcher {
return &HaveSuffixMatcher{
matchers.HaveSuffixMatcher{
Suffix: prefix,
Args: args,
},
}
}
func (m *HaveSuffixMatcher) FailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "to have suffix",
Expected: m.Suffix,
}
}
func (m *HaveSuffixMatcher) NegatedFailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "not to have suffix",
Expected: m.Suffix,
}
}
func (m *HaveSuffixMatcher) MarshalJSON() ([]byte, error) {
j := make(map[string]interface{})
j["have-suffix"] = m.Suffix
return json.Marshal(j)
}
goss-0.4.9/matchers/match_regexp_matcher.go 0000664 0000000 0000000 00000001547 14675050513 0021022 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
"github.com/onsi/gomega/matchers"
)
type MatchRegexpMatcher struct {
matchers.MatchRegexpMatcher
}
func MatchRegexp(regexp string, args ...interface{}) GossMatcher {
return &MatchRegexpMatcher{
matchers.MatchRegexpMatcher{
Regexp: regexp,
Args: args,
},
}
}
func (m *MatchRegexpMatcher) FailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "to match regular expression",
Expected: m.Regexp,
}
}
func (m *MatchRegexpMatcher) NegatedFailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "not to match regular expression",
Expected: m.Regexp,
}
}
func (m *MatchRegexpMatcher) MarshalJSON() ([]byte, error) {
j := make(map[string]interface{})
j["match-regexp"] = m.Regexp
return json.Marshal(j)
}
goss-0.4.9/matchers/matchers.go 0000664 0000000 0000000 00000002756 14675050513 0016462 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
"reflect"
"unsafe"
"github.com/onsi/gomega/types"
)
type GossMatcher interface {
// This is needed due to oMegaMatcher test in some of the GomegaMatcher logic
types.GomegaMatcher
//Match(actual interface{}) (success bool, err error)
FailureResult(actual interface{}) MatcherResult
NegatedFailureResult(actual interface{}) MatcherResult
// This doesn't seem to make a difference, maybe not needed
json.Marshaler
}
type MatcherResult struct {
Actual interface{} `json:"actual"`
Message string `json:"message"`
Expected interface{} `json:"expected"`
MissingElements interface{} `json:"missing-elements"`
FoundElements interface{} `json:"found-elements"`
ExtraElements interface{} `json:"extra-elements"`
TransformerChain []Transformer `json:"transform-chain"`
UntransformedValue interface{} `json:"untransformed-value"`
}
func getUnexported(i interface{}, field string) interface{} {
rs := reflect.ValueOf(i).Elem()
rf := rs.FieldByName(field)
rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem()
return rf.Interface()
}
type fakeOmegaMatcher struct{}
// FailureMessage is a stub to honor omegaMatcher interface
func (m *fakeOmegaMatcher) FailureMessage(_ interface{}) (message string) {
return ""
}
// NegatedFailureMessage is a stub to honor omegaMatcher interface
func (m *fakeOmegaMatcher) NegatedFailureMessage(_ interface{}) (message string) {
return ""
}
goss-0.4.9/matchers/not.go 0000664 0000000 0000000 00000001352 14675050513 0015443 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
)
type NotMatcher struct {
fakeOmegaMatcher
Matcher GossMatcher
}
func Not(matcher GossMatcher) GossMatcher {
return &NotMatcher{Matcher: matcher}
}
func (m *NotMatcher) Match(actual interface{}) (bool, error) {
success, err := m.Matcher.Match(actual)
if err != nil {
return false, err
}
return !success, nil
}
func (m *NotMatcher) FailureResult(actual interface{}) MatcherResult {
return m.Matcher.NegatedFailureResult(actual)
}
func (m *NotMatcher) NegatedFailureResult(actual interface{}) MatcherResult {
return m.Matcher.FailureResult(actual)
}
func (m *NotMatcher) MarshalJSON() ([]byte, error) {
j := make(map[string]interface{})
j["not"] = m.Matcher
return json.Marshal(j)
}
goss-0.4.9/matchers/or.go 0000664 0000000 0000000 00000002151 14675050513 0015261 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
)
type OrMatcher struct {
fakeOmegaMatcher
Matchers []GossMatcher
// state
firstSuccessfulMatcher GossMatcher
}
func Or(ms ...GossMatcher) GossMatcher {
return &OrMatcher{Matchers: ms}
}
func (m *OrMatcher) Match(actual interface{}) (success bool, err error) {
m.firstSuccessfulMatcher = nil
for _, matcher := range m.Matchers {
success, err := matcher.Match(actual)
if err != nil {
return false, err
}
if success {
m.firstSuccessfulMatcher = matcher
return true, nil
}
}
return false, nil
}
func (m *OrMatcher) FailureResult(actual interface{}) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "to satisfy at least one of these matchers",
Expected: m.Matchers,
}
}
func (m *OrMatcher) NegatedFailureResult(actual interface{}) MatcherResult {
firstSuccessfulMatcher := getUnexported(m, "firstSuccessfulMatcher")
return firstSuccessfulMatcher.(GossMatcher).NegatedFailureResult(actual)
}
func (m *OrMatcher) MarshalJSON() ([]byte, error) {
j := make(map[string]interface{})
j["or"] = m.Matchers
return json.Marshal(j)
}
goss-0.4.9/matchers/semver_constraint.go 0000664 0000000 0000000 00000005023 14675050513 0020407 0 ustar 00root root 0000000 0000000 package matchers
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"github.com/blang/semver/v4"
"github.com/onsi/gomega/format"
)
type BeSemverConstraintMatcher struct {
fakeOmegaMatcher
Constraint any
}
func BeSemverConstraint(constraint any) GossMatcher {
return &BeSemverConstraintMatcher{
Constraint: constraint,
}
}
func (m *BeSemverConstraintMatcher) Match(actual any) (success bool, err error) {
constraint, ok := toConstraint(m.Constraint)
if !ok {
return false, fmt.Errorf("Expected a valid semver constraint. Got:\n%s", format.Object(m.Constraint, 1))
}
actualSlice, ok := toVersions(actual)
if !ok {
return false, fmt.Errorf("Expected a single or list of semver valid version(s). Got:\n%s", format.Object(actual, 1))
}
for _, v := range actualSlice {
if !constraint(*v) {
return false, nil
}
}
return true, nil
}
func (m *BeSemverConstraintMatcher) FailureResult(actual any) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "to satisfy semver constraint",
Expected: m.Constraint,
}
}
func (m *BeSemverConstraintMatcher) NegatedFailureResult(actual any) MatcherResult {
return MatcherResult{
Actual: actual,
Message: "not to satisfy semver constraint",
Expected: m.Constraint,
}
}
func toConstraint(in any) (semver.Range, bool) {
str, ok := in.(string)
if !ok {
return nil, false
}
out, err := semver.ParseRange(str)
return out, err == nil
}
func toVersion(in any) (*semver.Version, bool) {
str, ok := in.(string)
if !ok {
return nil, false
}
v, err := semver.Parse(str)
if err != nil {
return nil, false
}
return &v, true
}
func toVersions(in any) ([]*semver.Version, bool) {
if v, ok := toVersion(in); ok {
return []*semver.Version{v}, ok
}
if reflect.ValueOf(in).Kind() != reflect.Slice {
return nil, false
}
out := make([]*semver.Version, 0)
if slice, ok := in.([]any); ok {
for _, ele := range slice {
if v, ok := toVersion(ele); ok {
out = append(out, v)
} else {
return nil, false
}
}
} else if slice, ok := in.([]string); ok {
for _, ele := range slice {
if v, ok := toVersion(ele); ok {
out = append(out, v)
} else {
return nil, false
}
}
}
return out, len(out) > 0
}
func (m *BeSemverConstraintMatcher) MarshalJSON() ([]byte, error) {
j := make(map[string]any)
j["semver-constraint"] = m.Constraint
buffer := &bytes.Buffer{}
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(false)
err := encoder.Encode(j)
if err != nil {
return nil, nil
}
b := buffer.Bytes()
return b, nil
}
goss-0.4.9/matchers/semver_constraint_test.go 0000664 0000000 0000000 00000016500 14675050513 0021450 0 ustar 00root root 0000000 0000000 package matchers
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/blang/semver/v4"
)
func TestBeSemverConstraint(t *testing.T) {
type args struct {
Constraint any
}
tests := []struct {
name string
args args
want GossMatcher
}{
{
name: "sanity",
args: args{Constraint: "> 1.0.0"},
want: &BeSemverConstraintMatcher{Constraint: "> 1.0.0"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := BeSemverConstraint(tt.args.Constraint)
assert.Equal(t, tt.want, got)
})
}
}
func TestBeSemverConstraintMatcher_FailureMessage(t *testing.T) {
type fields struct {
Constraint any
}
type args struct {
actual any
}
tests := []struct {
name string
fields fields
args args
wantResult MatcherResult
}{
{
name: "string",
fields: fields{Constraint: "> 1.1.0"},
args: args{actual: "1.0.0"},
wantResult: MatcherResult{
Actual: "1.0.0",
Message: "to satisfy semver constraint",
Expected: "> 1.1.0",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
matcher := &BeSemverConstraintMatcher{
Constraint: tt.fields.Constraint,
}
gotResult := matcher.FailureResult(tt.args.actual)
assert.Equal(t, tt.wantResult, gotResult)
})
}
}
func TestBeSemverConstraintMatcher_Match(t *testing.T) {
type fields struct {
Constraint any
}
type args struct {
actual any
}
type want struct {
success bool
err bool
errMessage string
}
tests := []struct {
name string
fields fields
args args
want want
}{
{
name: "pre_release_fail",
fields: fields{Constraint: ">= 4.0.0"},
args: args{actual: []string{"4.0.0-rc1"}},
want: want{
success: false,
err: false,
},
},
{
name: "pre_release_valid",
fields: fields{Constraint: "< 4.0.0"},
args: args{actual: []string{"4.0.0-rc1"}},
want: want{
success: true,
err: false,
},
},
{
name: "invalid_version_starting_with_0",
fields: fields{Constraint: "> 4.0.0"},
args: args{actual: []string{"4.4.019-1"}},
want: want{
success: false,
err: true,
errMessage: "Expected a single or list of semver valid version(s). Got:\n <[]string | len:1, cap:1>: [\"4.4.019-1\"]",
},
},
{
name: "build_fail",
fields: fields{Constraint: "> 4.0.0"},
args: args{actual: []string{"4.4.019+build+build2"}},
want: want{
success: false,
err: true,
errMessage: "Expected a single or list of semver valid version(s). Got:\n <[]string | len:1, cap:1>: [\n \"4.4.019+build+build2\",\n ]",
},
},
{
name: "build_valid",
fields: fields{Constraint: "> 4.0.0"},
args: args{actual: []string{"4.1.0+build"}},
want: want{
success: true,
err: false,
},
},
{
name: "invalid_actual",
fields: fields{Constraint: nil},
args: args{actual: []string{"4.1.0"}},
want: want{
success: false,
err: true,
errMessage: "Expected a valid semver constraint. Got:\n : nil",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
matcher := &BeSemverConstraintMatcher{
Constraint: tt.fields.Constraint,
}
gotSuccess, err := matcher.Match(tt.args.actual)
assert.Equal(t, tt.want.success, gotSuccess, "has success")
assert.Equal(t, tt.want.err, err != nil, "has error")
if tt.want.err {
assert.EqualError(t, err, tt.want.errMessage, "error message")
}
})
}
}
func TestBeSemverConstraintMatcher_NegatedFailureMessage(t *testing.T) {
type fields struct {
Constraint any
}
type args struct {
actual any
}
tests := []struct {
name string
fields fields
args args
wantResult MatcherResult
}{
{
name: "string",
fields: fields{Constraint: "> 1.1.0"},
args: args{actual: "1.0.0"},
wantResult: MatcherResult{
Actual: "1.0.0",
Message: "not to satisfy semver constraint",
Expected: "> 1.1.0",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
matcher := &BeSemverConstraintMatcher{
Constraint: tt.fields.Constraint,
}
gotResult := matcher.NegatedFailureResult(tt.args.actual)
assert.Equal(t, tt.wantResult, gotResult)
})
}
}
func Test_toConstraint(t *testing.T) {
type args struct {
in any
}
type want struct {
ok bool
}
tests := []struct {
name string
args args
want want
}{
{
name: "simple",
args: args{in: "> 1.0.0"},
want: want{ok: true},
},
{
name: "complex",
args: args{in: "> 1.0.0 < 2.0.0 || > 4.0.0"},
want: want{ok: true},
},
{
name: "nil",
args: args{in: nil},
want: want{ok: false},
},
{
name: "empty",
args: args{in: ""},
want: want{ok: false},
},
{
name: "invalid",
args: args{in: "invalid"},
want: want{ok: false},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotConstraint, gotOk := toConstraint(tt.args.in)
assert.Equal(t, tt.want.ok, gotOk, "success")
if tt.want.ok {
assert.NotNil(t, gotConstraint, "constraint shouldn't be nil")
assert.IsType(t, semver.Range(nil), gotConstraint, "constraint type")
}
})
}
}
func Test_toVersion(t *testing.T) {
type args struct {
in any
}
type want struct {
ok bool
}
tests := []struct {
name string
args args
want want
}{
{
name: "simple",
args: args{in: "1.0.0"},
want: want{ok: true},
},
{
name: "pre_release",
args: args{in: "1.2.3-rc1"},
want: want{ok: true},
},
{
name: "build",
args: args{in: "1.2.3+build1"},
want: want{ok: true},
},
{
name: "pre_release_build",
args: args{in: "1.2.3+build1"},
want: want{ok: true},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotVersion, gotOk := toVersion(tt.args.in)
assert.Equal(t, tt.want.ok, gotOk)
if tt.want.ok {
assert.NotNil(t, gotVersion, "version shouldn't be nil")
if gotVersion != nil {
assert.Equal(t, fmt.Sprint(tt.args.in), gotVersion.String(), "version")
}
}
})
}
}
func Test_toVersions(t *testing.T) {
type args struct {
in any
}
type want struct {
ok bool
}
tests := []struct {
name string
args args
want want
}{
{
name: "single",
args: args{in: "1.0.0"},
want: want{ok: true},
},
{
name: "slice_strings",
args: args{in: []string{"1.0.0"}},
want: want{ok: true},
},
{
name: "slice_interfaces",
args: args{in: []any{"1.0.0"}},
want: want{ok: true},
},
{
name: "invalid_object",
args: args{in: want{}},
want: want{ok: false},
},
{
name: "invalid_object_in_slice",
args: args{in: []any{want{}}},
want: want{ok: false},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotVersions, gotOk := toVersions(tt.args.in)
assert.Equal(t, tt.want.ok, gotOk)
if tt.want.ok {
assert.NotNil(t, gotVersions, "versions shouldn't be nil")
assert.NotEmpty(t, gotVersions, "versions shouldn't be empty")
for i, version := range gotVersions {
if versions, ok := tt.args.in.([]string); ok {
assert.Equal(t, fmt.Sprint(versions[i]), version.String())
} else if versions, ok := tt.args.in.([]any); ok {
assert.Equal(t, fmt.Sprint(versions[i]), version.String())
} else {
assert.Equal(t, fmt.Sprint(tt.args.in), version.String())
}
}
}
})
}
}
goss-0.4.9/matchers/type_conversion.go 0000664 0000000 0000000 00000005625 14675050513 0020100 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"github.com/onsi/gomega/format"
"github.com/tidwall/gjson"
)
type Transformer interface {
Transform(interface{}) (interface{}, error)
}
type ToNumeric struct{}
func (t ToNumeric) Transform(e interface{}) (interface{}, error) {
switch v := e.(type) {
case float64, int:
return v, nil
case string:
return strconv.ParseFloat(strings.TrimSpace(v), 64)
case []string:
i, err := ToString{}.Transform(v)
if err != nil {
return 0, err
}
s := i.(string)
return strconv.ParseFloat(strings.TrimSpace(s), 64)
default:
return 0, fmt.Errorf("Expected numeric, Got:%s", format.Object(e, 1))
}
}
func (t ToNumeric) MarshalJSON() ([]byte, error) {
j := map[string]interface{}{
"to-numeric": map[string]string{},
}
return json.Marshal(j)
}
type ToString struct{}
func (t ToString) Transform(e interface{}) (interface{}, error) {
switch v := e.(type) {
case []interface{}:
vs := make([]string, len(v))
for i, v := range v {
vs[i] = fmt.Sprintf("%v", v)
}
return strings.Join(vs, "\n"), nil
case []string:
return strings.Join(v, "\n"), nil
default:
return fmt.Sprintf("%v", v), nil
}
}
func (t ToString) MarshalJSON() ([]byte, error) {
j := map[string]interface{}{
"to-string": map[string]string{},
}
return json.Marshal(j)
}
type ToArray struct{}
func (t ToArray) Transform(i interface{}) (interface{}, error) {
switch v := i.(type) {
case string:
return strings.Split(v, "\n"), nil
default:
return i, nil
}
}
func (matcher ToArray) MarshalJSON() ([]byte, error) {
j := map[string]interface{}{
"to-array": map[string]string{},
}
return json.Marshal(j)
}
//type ReaderToStrings struct{}
//
//func (t ReaderToStrings) Transform(i interface{}) (interface{}, error) {
// r, ok := i.(io.Reader)
// if !ok {
// return nil, fmt.Errorf("Expected io.reader, Got:%s", format.Object(i, 1))
// }
// var lines []string
// i, err := ReaderToString{}.Transform(r)
// if err != nil {
// return lines, err
// }
// s := i.(string)
// return strings.Split(s, "\n"), nil
//}
type ReaderToString struct{}
func (t ReaderToString) Transform(i interface{}) (interface{}, error) {
r, ok := i.(io.Reader)
if !ok {
return nil, fmt.Errorf("Expected io.reader, Got:%s", format.Object(i, 1))
}
b, err := io.ReadAll(r)
if err != nil {
return "", err
}
return string(b), nil
}
type Gjson struct {
Path string
}
func (g Gjson) Transform(i interface{}) (interface{}, error) {
s, ok := i.(string)
if !ok {
return nil, fmt.Errorf("Expected string, Got:%s", format.Object(i, 1))
}
if !gjson.Valid(s) {
return nil, fmt.Errorf("Invalid json")
}
r := gjson.Get(s, g.Path)
if !r.Exists() {
return nil, fmt.Errorf("Path not found: %s", g.Path)
}
return r.Value(), nil
}
func (g Gjson) MarshalJSON() ([]byte, error) {
j := map[string]interface{}{
"gjson": map[string]string{
"Path": g.Path,
},
}
return json.Marshal(j)
}
goss-0.4.9/matchers/with_safe_transform.go 0000664 0000000 0000000 00000004073 14675050513 0020712 0 ustar 00root root 0000000 0000000 package matchers
import (
"encoding/json"
"fmt"
"reflect"
)
type WithSafeTransformMatcher struct {
fakeOmegaMatcher
// input
Transform Transformer // must be a function of one parameter that returns one value
Matcher GossMatcher
// state
transformedValue interface{}
wasTransformed bool
}
func WithSafeTransform(transform Transformer, matcher GossMatcher) GossMatcher {
return &WithSafeTransformMatcher{
Transform: transform,
Matcher: matcher,
}
}
func (m *WithSafeTransformMatcher) Match(actual interface{}) (bool, error) {
var err error
//log.Printf("%#v: input: %v", m.Transform, actual)
m.transformedValue, err = m.Transform.Transform(actual)
if !reflect.DeepEqual(actual, m.transformedValue) {
m.wasTransformed = true
}
if err != nil {
return false, fmt.Errorf("%#v: %s", m.Transform, err)
}
//log.Printf("%#v: output: %v", m.Transform, m.transformedValue)
return m.Matcher.Match(m.transformedValue)
}
func (m *WithSafeTransformMatcher) FailureResult(actual interface{}) MatcherResult {
tchain, matcher, tvalue := m.getTransformerChainAndMatcher()
result := matcher.FailureResult(tvalue)
result.TransformerChain = tchain
result.UntransformedValue = actual
return result
}
func (m *WithSafeTransformMatcher) NegatedFailureResult(actual interface{}) MatcherResult {
tchain, matcher, tvalue := m.getTransformerChainAndMatcher()
result := matcher.NegatedFailureResult(tvalue)
result.TransformerChain = tchain
result.UntransformedValue = actual
return result
}
func (m *WithSafeTransformMatcher) getTransformerChainAndMatcher() (tchain []Transformer, matcher GossMatcher, tvalue interface{}) {
matcher = m
tvalue = m.transformedValue
L:
for {
switch v := matcher.(type) {
case *WithSafeTransformMatcher:
matcher = v.Matcher
tvalue = v.transformedValue
if v.wasTransformed {
tchain = append(tchain, v.Transform)
}
default:
break L
}
}
return tchain, matcher, tvalue
}
func (m *WithSafeTransformMatcher) MarshalJSON() ([]byte, error) {
_, matcher, _ := m.getTransformerChainAndMatcher()
return json.Marshal(matcher)
}
goss-0.4.9/mkdocs.yml 0000664 0000000 0000000 00000004457 14675050513 0014522 0 ustar 00root root 0000000 0000000 site_name: Goss
site_description: Goss is a YAML based serverspec alternative tool for validating a server’s configuration.
site_author: Goss team
site_url: https://goss.readthedocs.io/
repo_url: https://github.com/goss-org/goss
repo_name: goss-org/goss
edit_uri: edit/master/docs/
theme:
name: material
palette:
- media: "(prefers-color-scheme: light)"
scheme: default
primary: black
accent: amber
toggle:
icon: material/weather-sunny
name: Switch to dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
primary: black
accent: indigo
toggle:
icon: material/weather-night
name: Switch to light mode
features:
- content.action.edit
- content.code.copy
- navigation.footer
- navigation.instant
- navigation.instant.progress
- navigation.top
- navigation.tracking
- search.highlight
- search.share
- search.suggest
- toc.follow
extra_css:
- style.css
plugins:
- search
- awesome-pages
- macros:
render_by_default: false
- exclude:
glob:
- requirements.txt
markdown_extensions:
- abbr
- admonition
- attr_list
- def_list
- md_in_html
- mdx_breakless_lists
- tables
- pymdownx.details
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- pymdownx.inlinehilite
- pymdownx.magiclink:
repo_url_shortener: true
social_url_shortener: true
repo_url_shorthand: true
social_url_shorthand: true
user: goss-org
repo: goss
- pymdownx.snippets:
base_path:
- .
- docs/snippets
check_paths: true
- pymdownx.superfences
copyright: Copyright © 2015 - 2024 Ahmed Elsabbahy
extra:
social:
- icon: fontawesome/brands/github
link: https://github.com/goss-org/goss
- icon: simple/travisci
link: https://travis-ci.org/goss-org/goss
- icon: fontawesome/brands/medium
link: https://medium.com/@aelsabbahy
watch:
- README.md
- LICENSE
- .github/CONTRIBUTING.md
- extras/dcgoss/README.md
- extras/dgoss/README.md
- extras/kgoss/README.md
goss-0.4.9/novendor.sh 0000775 0000000 0000000 00000000665 14675050513 0014705 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
set -euo pipefail
# Bash replacement for glide novendor command
# Returns all directories which include go files
DIRS=$(ls -ld */ . | awk {'print $9'} | grep -v vendor)
for DIR in ${DIRS}; do
GOFILES=$(git ls-files ${DIR} | grep ".*\.go$") || true
if [[ ${DIR} == "." ]]; then
echo "."
continue
fi
if [[ ${GOFILES} != "" ]]; then
echo "./"${DIR}"..."
fi
done
exit 0
goss-0.4.9/outputs/ 0000775 0000000 0000000 00000000000 14675050513 0014230 5 ustar 00root root 0000000 0000000 goss-0.4.9/outputs/documentation.go 0000664 0000000 0000000 00000003625 14675050513 0017436 0 ustar 00root root 0000000 0000000 package outputs
import (
"fmt"
"io"
"time"
"github.com/goss-org/goss/resource"
"github.com/goss-org/goss/util"
)
type Documentation struct{}
func (r Documentation) ValidOptions() []*formatOption {
return []*formatOption{
{name: foSort},
}
}
func (r Documentation) Output(w io.Writer, results <-chan []resource.TestResult,
outConfig util.OutputConfig) (exitCode int) {
includeRaw := !util.IsValueInList(foExcludeRaw, outConfig.FormatOptions)
sort := util.IsValueInList(foSort, outConfig.FormatOptions)
results = getResults(results, sort)
var startTime time.Time
var endTime time.Time
testCount := 0
var failedOrSkipped [][]resource.TestResult
var skipped, failed int
for resultGroup := range results {
failedOrSkippedGroup := []resource.TestResult{}
first := resultGroup[0]
header := header(first)
if header != "" {
fmt.Fprint(w, header)
}
for _, testResult := range resultGroup {
if startTime.IsZero() || testResult.StartTime.Before(startTime) {
startTime = testResult.StartTime
}
if endTime.IsZero() || testResult.EndTime.After(endTime) {
endTime = testResult.EndTime
}
switch testResult.Result {
case resource.SUCCESS:
fmt.Fprintln(w, humanizeResult(testResult, false, includeRaw))
case resource.SKIP:
fmt.Fprintln(w, humanizeResult(testResult, false, includeRaw))
failedOrSkippedGroup = append(failedOrSkippedGroup, testResult)
skipped++
case resource.FAIL:
fmt.Fprintln(w, humanizeResult(testResult, false, includeRaw))
failedOrSkippedGroup = append(failedOrSkippedGroup, testResult)
failed++
}
testCount++
}
if len(failedOrSkippedGroup) > 0 {
failedOrSkipped = append(failedOrSkipped, failedOrSkippedGroup)
}
}
fmt.Fprint(w, "\n\n")
fmt.Fprint(w, failedOrSkippedSummary(failedOrSkipped, includeRaw))
fmt.Fprint(w, summary(startTime, endTime, testCount, failed, skipped))
if failed > 0 {
return 1
}
return 0
}
goss-0.4.9/outputs/json.go 0000664 0000000 0000000 00000004763 14675050513 0015542 0 ustar 00root root 0000000 0000000 package outputs
import (
"encoding/json"
"fmt"
"io"
"log"
"time"
"github.com/fatih/color"
"github.com/goss-org/goss/resource"
"github.com/goss-org/goss/util"
)
type Json struct{}
func (r Json) ValidOptions() []*formatOption {
return []*formatOption{
{name: foPretty},
{name: foSort},
}
}
func (r Json) Output(w io.Writer, results <-chan []resource.TestResult,
outConfig util.OutputConfig) (exitCode int) {
var pretty bool = util.IsValueInList(foPretty, outConfig.FormatOptions)
includeRaw := !util.IsValueInList(foExcludeRaw, outConfig.FormatOptions)
sort := util.IsValueInList(foSort, outConfig.FormatOptions)
results = getResults(results, sort)
var startTime time.Time
var endTime time.Time
color.NoColor = true
testCount := 0
failed := 0
skipped := 0
var resultsOut []map[string]any
for resultGroup := range results {
for _, testResult := range resultGroup {
if startTime.IsZero() || testResult.StartTime.Before(startTime) {
startTime = testResult.StartTime
}
if endTime.IsZero() || testResult.EndTime.After(endTime) {
endTime = testResult.EndTime
}
if testResult.Result == resource.FAIL {
failed++
logTrace("TRACE", "FAIL", testResult, true)
} else {
logTrace("TRACE", "SUCCESS", testResult, true)
}
if testResult.Skipped {
skipped++
}
m := struct2map(testResult)
m["successful"] = testResult.Result != resource.FAIL
m["summary-line"] = humanizeResult(testResult, false, includeRaw)
m["summary-line-compact"] = humanizeResult(testResult, true, includeRaw)
m["duration"] = testResult.Duration.Nanoseconds()
resultsOut = append(resultsOut, m)
testCount++
}
}
summary := make(map[string]any)
duration := endTime.Sub(startTime)
summary["test-count"] = testCount
summary["failed-count"] = failed
summary["skipped-count"] = skipped
summary["total-duration"] = duration
summary["summary-line"] = fmt.Sprintf("Count: %d, Failed: %d, Skipped: %d, Duration: %.3fs", testCount, failed, skipped, duration.Seconds())
out := make(map[string]any)
out["results"] = resultsOut
out["summary"] = summary
var j []byte
if pretty {
j, _ = json.MarshalIndent(out, "", " ")
} else {
j, _ = json.Marshal(out)
}
resstr := string(j)
fmt.Fprintln(w, resstr)
if failed > 0 {
log.Printf("[DEBUG] FAIL SUMMARY: %s", resstr)
return 1
}
log.Printf("[DEBUG] OK SUMMARY: %s", resstr)
return 0
}
func struct2map(i any) map[string]any {
out := make(map[string]any)
j, _ := json.Marshal(i)
json.Unmarshal(j, &out)
return out
}
goss-0.4.9/outputs/junit.go 0000664 0000000 0000000 00000005042 14675050513 0015711 0 ustar 00root root 0000000 0000000 package outputs
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"strconv"
"time"
"github.com/fatih/color"
"github.com/goss-org/goss/resource"
"github.com/goss-org/goss/util"
)
type JUnit struct{}
func (r JUnit) ValidOptions() []*formatOption {
return []*formatOption{
{name: foSort},
}
}
func (r JUnit) Output(w io.Writer, results <-chan []resource.TestResult,
outConfig util.OutputConfig) (exitCode int) {
includeRaw := !util.IsValueInList(foExcludeRaw, outConfig.FormatOptions)
sort := util.IsValueInList(foSort, outConfig.FormatOptions)
results = getResults(results, sort)
color.NoColor = true
var testCount, failed, skipped int
// ISO8601 timeformat
timestamp := time.Now().Format(time.RFC3339)
var summary map[int]string = make(map[int]string)
var startTime time.Time
var endTime time.Time
for resultGroup := range results {
for _, testResult := range resultGroup {
if startTime.IsZero() || testResult.StartTime.Before(startTime) {
startTime = testResult.StartTime
}
if endTime.IsZero() || testResult.EndTime.After(endTime) {
endTime = testResult.EndTime
}
duration := strconv.FormatFloat(testResult.Duration.Seconds(), 'f', 3, 64)
summary[testCount] = "\n"
if testResult.Result == resource.FAIL {
summary[testCount] += "" +
escapeString(humanizeResult(testResult, true, includeRaw)) +
" \n"
summary[testCount] += "" +
escapeString(humanizeResult(testResult, true, includeRaw)) +
" \n \n"
failed++
} else {
if testResult.Result == resource.SKIP {
summary[testCount] += " "
skipped++
}
summary[testCount] += "" +
escapeString(humanizeResult(testResult, true, includeRaw)) +
" \n\n"
}
testCount++
}
}
duration := endTime.Sub(startTime)
fmt.Fprintln(w, "")
fmt.Fprintf(w, "\n",
testCount, failed, skipped, duration.Seconds(), timestamp)
for i := 0; i < testCount; i++ {
fmt.Fprintf(w, "%s", summary[i])
}
fmt.Fprintln(w, " ")
if failed > 0 {
return 1
}
return 0
}
func escapeString(str string) string {
buffer := new(bytes.Buffer)
xml.EscapeText(buffer, []byte(str))
return buffer.String()
}
goss-0.4.9/outputs/nagios.go 0000664 0000000 0000000 00000004122 14675050513 0016036 0 ustar 00root root 0000000 0000000 package outputs
import (
"fmt"
"io"
"strconv"
"time"
"github.com/goss-org/goss/resource"
"github.com/goss-org/goss/util"
)
type Nagios struct{}
func (r Nagios) ValidOptions() []*formatOption {
return []*formatOption{
{name: foPerfData},
{name: foVerbose},
}
}
func (r Nagios) Output(w io.Writer, results <-chan []resource.TestResult,
outConfig util.OutputConfig) (exitCode int) {
var testCount, failed, skipped int
var perfdata, verbose bool
perfdata = util.IsValueInList(foPerfData, outConfig.FormatOptions)
verbose = util.IsValueInList(foVerbose, outConfig.FormatOptions)
includeRaw := !util.IsValueInList(foExcludeRaw, outConfig.FormatOptions)
var startTime time.Time
var endTime time.Time
var summary map[int]string = make(map[int]string)
for resultGroup := range results {
for _, testResult := range resultGroup {
if startTime.IsZero() || testResult.StartTime.Before(startTime) {
startTime = testResult.StartTime
}
if endTime.IsZero() || testResult.EndTime.After(endTime) {
endTime = testResult.EndTime
}
switch testResult.Result {
case resource.FAIL:
if util.IsValueInList(foVerbose, outConfig.FormatOptions) {
summary[failed] = "Fail " + strconv.Itoa(failed+1) + " - " + humanizeResult(testResult, true, includeRaw) + "\n"
}
failed++
case resource.SKIP:
skipped++
}
testCount++
}
}
duration := endTime.Sub(startTime)
if failed > 0 {
fmt.Fprintf(w, "GOSS CRITICAL - Count: %d, Failed: %d, Skipped: %d, Duration: %.3fs", testCount, failed, skipped, duration.Seconds())
if perfdata {
fmt.Fprintf(w, "|total=%d failed=%d skipped=%d duration=%.3fs", testCount, failed, skipped, duration.Seconds())
}
fmt.Fprint(w, "\n")
if verbose {
for i := 0; i < failed; i++ {
fmt.Fprintf(w, "%s", summary[i])
}
}
return 2
}
fmt.Fprintf(w, "GOSS OK - Count: %d, Failed: %d, Skipped: %d, Duration: %.3fs", testCount, failed, skipped, duration.Seconds())
if perfdata {
fmt.Fprintf(w, "|total=%d failed=%d skipped=%d duration=%.3fs", testCount, failed, skipped, duration.Seconds())
}
fmt.Fprint(w, "\n")
return 0
}
goss-0.4.9/outputs/outputs.go 0000664 0000000 0000000 00000016476 14675050513 0016320 0 ustar 00root root 0000000 0000000 package outputs
import (
"bytes"
"encoding/json"
"fmt"
"io"
"reflect"
"regexp"
"sort"
"strings"
"sync"
"time"
"unicode"
"github.com/fatih/color"
"github.com/goss-org/goss/resource"
"github.com/goss-org/goss/util"
"github.com/pmezard/go-difflib/difflib"
)
type formatOption struct {
name string
}
type Outputer interface {
Output(io.Writer, <-chan []resource.TestResult, util.OutputConfig) int
ValidOptions() []*formatOption
}
var (
outputersMu sync.Mutex
outputers = map[string]Outputer{
"documentation": &Documentation{},
"json": &Json{},
"junit": &JUnit{},
"nagios": &Nagios{},
"prometheus": &Prometheus{},
"rspecish": &Rspecish{},
"structured": &Structured{},
"tap": &Tap{},
"silent": &Silent{},
}
foPerfData = "perfdata"
foVerbose = "verbose"
foPretty = "pretty"
foExcludeRaw = "exclude_raw"
foSort = "sort"
)
var green = color.New(color.FgGreen).SprintfFunc()
var red = color.New(color.FgRed).SprintfFunc()
var yellow = color.New(color.FgYellow).SprintfFunc()
var multiple_space = regexp.MustCompile(`\s+`)
func humanizeResult(r resource.TestResult, compact bool, includeRaw bool) string {
sep := "\n"
if compact {
sep = " "
}
switch r.Result {
case resource.SUCCESS:
return green("%s: %s: %s: %s: %s", r.ResourceType, r.ResourceId, r.Property, r.MatcherResult.Message, prettyPrint(r.MatcherResult.Expected, false))
case resource.FAIL:
matcherResult := prettyPrintTestResult(r, compact, includeRaw)
return red("%s: %s: %s:%s%s", r.ResourceType, r.ResourceId, r.Property, sep, matcherResult)
case resource.SKIP:
return yellow("%s: %s: %s: skipped", r.ResourceType, r.ResourceId, r.Property)
default:
panic(fmt.Sprintf("Unexpected Result Code: %v\n", r.Result))
}
}
func prettyPrintTestResult(t resource.TestResult, compact bool, includeRaw bool) string {
sep := "\n"
if compact {
sep = " "
}
m := t.MatcherResult
var ss []string
//var s string
if t.Err != nil {
e := fmt.Sprint(t.Err)
if compact {
e = multiple_space.ReplaceAllString(e, " ")
} else {
e = indentLines(e)
}
ss = append(ss, "Error")
ss = append(ss, e)
} else {
ss = append(ss, "Expected")
ss = append(ss, prettyPrint(m.Actual, !compact))
ss = append(ss, m.Message)
ss = append(ss, prettyPrint(m.Expected, !compact))
ss = maybeAddDiff(ss, m.Expected, m.Actual, compact)
}
if reflect.ValueOf(m.MissingElements).IsValid() && !reflect.ValueOf(m.MissingElements).IsNil() {
ss = append(ss, "the missing elements were")
ss = append(ss, prettyPrint(m.MissingElements, !compact))
}
if reflect.ValueOf(m.ExtraElements).IsValid() && !reflect.ValueOf(m.ExtraElements).IsNil() {
ss = append(ss, "the extra elements were")
ss = append(ss, prettyPrint(m.ExtraElements, !compact))
}
if len(m.TransformerChain) != 0 {
ss = append(ss, "the transform chain was")
ss = append(ss, prettyPrint(m.TransformerChain, !compact))
if includeRaw {
ss = append(ss, "the raw value was")
ss = append(ss, prettyPrint(m.UntransformedValue, !compact))
}
}
return strings.Join(ss, sep)
}
func maybeAddDiff(ss []string, expected, actual any, compact bool) []string {
if compact {
return ss
}
want, ok := expected.(string)
if !ok {
return ss
}
got, ok := actual.(string)
if !ok {
return ss
}
if want == got {
return ss
}
ss = append(ss, "diff")
diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
A: difflib.SplitLines(want),
B: difflib.SplitLines(got),
FromFile: "test",
FromDate: "",
ToFile: "actual",
ToDate: "",
Context: 1,
})
ss = append(ss, indentLines(diff))
return ss
}
func prettyPrint(i interface{}, indent bool) string {
buffer := &bytes.Buffer{}
encoder := json.NewEncoder(buffer)
encoder.SetEscapeHTML(false)
var b []byte
err := encoder.Encode(i)
if err == nil {
b = buffer.Bytes()
} else {
// FIXME: Is this the right thing to do?
b = []byte(err.Error())
}
b = bytes.TrimRightFunc(b, unicode.IsSpace)
if indent {
return indentLines(string(b))
} else {
return string(b)
}
}
// indents a block of text with an indent string
func indentLines(text string) string {
indent := " "
result := ""
for _, j := range strings.Split(strings.TrimRight(text, "\n"), "\n") {
result += indent + j + "\n"
}
return result[:len(result)-1]
}
func RegisterOutputer(name string, outputer Outputer) {
outputersMu.Lock()
defer outputersMu.Unlock()
if outputer == nil {
panic("goss: Register outputer is nil")
}
if _, dup := outputers[name]; dup {
panic("goss: Register called twice for ouputer " + name)
}
outputers[name] = outputer
}
// Outputers returns a sorted list of the names of the registered outputers.
func Outputers() []string {
outputersMu.Lock()
defer outputersMu.Unlock()
var list []string
for name := range outputers {
list = append(list, name)
}
sort.Strings(list)
return list
}
// FormatOptions returns a sorted list of all the valid options that outputers accept
func FormatOptions() []string {
outputersMu.Lock()
defer outputersMu.Unlock()
found := map[string]*formatOption{}
for _, o := range outputers {
for _, opt := range o.ValidOptions() {
found[opt.name] = opt
}
}
var list []string
for name := range found {
list = append(list, name)
}
sort.Strings(list)
return list
}
// IsValidFormat determines if f is a valid format name based on Outputers()
func IsValidFormat(f string) bool {
for _, o := range Outputers() {
if o == f {
return true
}
}
return false
}
func GetOutputer(name string) (Outputer, error) {
if _, ok := outputers[name]; !ok {
return nil, fmt.Errorf("bad output format: " + name)
}
return outputers[name], nil
}
func header(t resource.TestResult) string {
var out string
if t.Title != "" {
out += fmt.Sprintf("Title: %s\n", t.Title)
}
if t.Meta != nil {
var keys []string
for k := range t.Meta {
keys = append(keys, k)
}
sort.Strings(keys)
out += "Meta:\n"
for _, k := range keys {
out += fmt.Sprintf(" %v: %v\n", k, t.Meta[k])
}
}
return out
}
func summary(startTime, endTime time.Time, count, failed, skipped int) string {
var s string
s += fmt.Sprintf("Total Duration: %.3fs\n", endTime.Sub(startTime).Seconds())
f := green
if failed > 0 {
f = red
}
s += f("Count: %d, Failed: %d, Skipped: %d\n", count, failed, skipped)
return s
}
func failedOrSkippedSummary(failedOrSkipped [][]resource.TestResult, includeRaw bool) string {
var s string
if len(failedOrSkipped) > 0 {
s += "Failures/Skipped:\n\n"
sort.Slice(failedOrSkipped, func(i, j int) bool {
return failedOrSkipped[i][0].SortKey() < failedOrSkipped[j][0].SortKey()
})
for _, failedGroup := range failedOrSkipped {
first := failedGroup[0]
header := header(first)
if header != "" {
s += fmt.Sprint(header)
}
for _, testResult := range failedGroup {
s += fmt.Sprintln(humanizeResult(testResult, false, includeRaw))
}
s += "\n"
}
}
return s
}
func getResults(tr <-chan []resource.TestResult, doSort bool) <-chan []resource.TestResult {
if !doSort {
return tr
}
str := make([][]resource.TestResult, 0)
for i := range tr {
str = append(str, i)
}
sort.Slice(str, func(i, j int) bool {
return str[i][0].SortKey() < str[j][0].SortKey()
})
c := make(chan []resource.TestResult)
go func(c chan []resource.TestResult) {
defer close(c)
for _, i := range str {
c <- i
}
}(c)
return c
}
goss-0.4.9/outputs/outputs_test.go 0000664 0000000 0000000 00000002636 14675050513 0017350 0 ustar 00root root 0000000 0000000 package outputs
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsValidFormat(t *testing.T) {
if IsValidFormat("ne") {
t.Fatal("'ne' should not be a valid output format")
}
if !IsValidFormat("json") {
t.Fatal("'json' should be a valid output format")
}
}
func TestOutputers(t *testing.T) {
list := Outputers()
assert.NotEmpty(t, list)
}
func TestGetOutputer(t *testing.T) {
t.Run("valid", func(t *testing.T) {
got, err := GetOutputer("rspecish")
assert.NoError(t, err)
assert.NotNil(t, got)
})
t.Run("not-valid", func(t *testing.T) {
got, err := GetOutputer("gibberish")
assert.Error(t, err)
assert.Nil(t, got)
})
}
func TestOutputFormatOptions(t *testing.T) {
list := FormatOptions()
assert.NotEmpty(t, list)
assert.Contains(t, list, foPerfData)
assert.Contains(t, list, foPretty)
assert.Contains(t, list, foVerbose)
assert.Len(t, list, 4)
}
func TestOptionsRegistration(t *testing.T) {
registeredOutputs := Outputers()
assert.Contains(t, registeredOutputs, "documentation")
assert.Contains(t, registeredOutputs, "json")
assert.Contains(t, registeredOutputs, "junit")
assert.Contains(t, registeredOutputs, "nagios")
assert.Contains(t, registeredOutputs, "prometheus")
assert.Contains(t, registeredOutputs, "rspecish")
assert.Contains(t, registeredOutputs, "silent")
assert.Contains(t, registeredOutputs, "structured")
assert.Contains(t, registeredOutputs, "tap")
}
goss-0.4.9/outputs/prometheus.go 0000664 0000000 0000000 00000007465 14675050513 0016766 0 ustar 00root root 0000000 0000000 package outputs
import (
"io"
"strings"
"time"
"github.com/goss-org/goss/resource"
"github.com/goss-org/goss/util"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/common/expfmt"
)
const (
labelType = "type"
labelOutcome = "outcome"
labelResourceId = "resource_id"
)
var (
registry *prometheus.Registry
testOutcomes *prometheus.CounterVec
testDurations *prometheus.CounterVec
runOutcomes *prometheus.CounterVec
runDuration *prometheus.CounterVec
)
// Prometheus renders metrics in prometheus.io text-format https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format
type Prometheus struct{}
// ValidOptions is a list of valid format options for prometheus
func (r Prometheus) ValidOptions() []*formatOption {
return []*formatOption{
{name: foVerbose},
}
}
// Output converts the results into the prometheus text-format.
func (r Prometheus) Output(w io.Writer, results <-chan []resource.TestResult,
outConfig util.OutputConfig) (exitCode int) {
verbose := util.IsValueInList(foVerbose, outConfig.FormatOptions)
if registry == nil {
setupMetrics(verbose)
}
overallOutcome := resource.OutcomeUnknown
var startTime time.Time
for resultGroup := range results {
for i, tr := range resultGroup {
if startTime.IsZero() || tr.StartTime.Before(startTime) {
startTime = tr.StartTime
}
resType := strings.ToLower(tr.ResourceType)
outcome := tr.ToOutcome()
if verbose {
resId := tr.ResourceId
testOutcomes.WithLabelValues(resType, outcome, resId).Inc()
testDurations.WithLabelValues(resType, outcome, resId).Add(float64(tr.Duration.Milliseconds()))
} else {
testOutcomes.WithLabelValues(resType, outcome).Inc()
testDurations.WithLabelValues(resType, outcome).Add(float64(tr.Duration.Milliseconds()))
}
if i == 0 || canChangeOverallOutcome(overallOutcome, outcome) {
overallOutcome = outcome
}
}
}
runOutcomes.WithLabelValues(overallOutcome).Inc()
runDuration.WithLabelValues(overallOutcome).Add(float64(time.Since(startTime).Milliseconds()))
metricsFamilies, err := registry.Gather()
if err != nil {
return -1
}
encoder := expfmt.NewEncoder(w, expfmt.NewFormat(expfmt.TypeTextPlain))
for _, mf := range metricsFamilies {
err := encoder.Encode(mf)
if err != nil {
return -1
}
}
return 0
}
func setupMetrics(verbose bool) {
registry = prometheus.NewRegistry()
factory := promauto.With(registry)
var testLabels []string
if verbose {
testLabels = []string{labelType, labelOutcome, labelResourceId}
} else {
testLabels = []string{labelType, labelOutcome}
}
testOutcomes = factory.NewCounterVec(prometheus.CounterOpts{
Namespace: "goss",
Subsystem: "tests",
Name: "outcomes_total",
Help: "The number of test-outcomes from this run.",
}, testLabels)
testDurations = factory.NewCounterVec(prometheus.CounterOpts{
Namespace: "goss",
Subsystem: "tests",
Name: "outcomes_duration_milliseconds",
Help: "The duration of tests from this run. Note; tests run concurrently.",
}, testLabels)
runOutcomes = factory.NewCounterVec(prometheus.CounterOpts{
Namespace: "goss",
Subsystem: "tests",
Name: "run_outcomes_total",
Help: "The outcomes of this run as a whole.",
}, []string{labelOutcome})
runDuration = factory.NewCounterVec(prometheus.CounterOpts{
Namespace: "goss",
Subsystem: "tests",
Name: "run_duration_milliseconds",
Help: "The end-to-end duration of this run.",
}, []string{labelOutcome})
}
func canChangeOverallOutcome(current, result string) bool {
switch current {
case resource.OutcomeSkip:
return true
case resource.OutcomeFail:
return false
case resource.OutcomePass:
return result != resource.OutcomeSkip
default:
return result == resource.OutcomeFail
}
}
goss-0.4.9/outputs/prometheus_test.go 0000664 0000000 0000000 00000044640 14675050513 0020021 0 ustar 00root root 0000000 0000000 package outputs
import (
"bytes"
"fmt"
"sync"
"testing"
"time"
"github.com/goss-org/goss/resource"
"github.com/goss-org/goss/util"
"github.com/stretchr/testify/assert"
)
func TestPrometheusOutput(t *testing.T) {
testCases := map[string]struct {
results []resource.TestResult
formatOptions []string
expectedMetrics []string
}{
"all-success-single-type": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SUCCESS,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SUCCESS,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="pass",type="command"} 20`,
`goss_tests_outcomes_total{outcome="pass",type="command"} 2`,
`goss_tests_run_duration_milliseconds{outcome="pass"}`,
`goss_tests_run_outcomes_total{outcome="pass"} 1`,
},
},
"all-skip-single-type": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SKIP,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SKIP,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="skip",type="command"} 20`,
`goss_tests_outcomes_total{outcome="skip",type="command"} 2`,
`goss_tests_run_duration_milliseconds{outcome="skip"}`,
`goss_tests_run_outcomes_total{outcome="skip"} 1`,
},
},
"all-fail-single-type": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.FAIL,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.FAIL,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="fail",type="command"} 20`,
`goss_tests_outcomes_total{outcome="fail",type="command"} 2`,
`goss_tests_run_duration_milliseconds{outcome="fail"}`,
`goss_tests_run_outcomes_total{outcome="fail"} 1`,
},
},
"all-unknown-single-type": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.UNKNOWN,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.UNKNOWN,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="unknown",type="command"} 20`,
`goss_tests_outcomes_total{outcome="unknown",type="command"} 2`,
`goss_tests_run_duration_milliseconds{outcome="unknown"}`,
`goss_tests_run_outcomes_total{outcome="unknown"} 1`,
},
},
"all-success-multiple-types": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SUCCESS,
},
{
ResourceType: "File",
Duration: 10 * time.Millisecond,
Result: resource.SUCCESS,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="pass",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="pass",type="file"} 10`,
`goss_tests_outcomes_total{outcome="pass",type="command"} 1`,
`goss_tests_outcomes_total{outcome="pass",type="file"} 1`,
`goss_tests_run_duration_milliseconds{outcome="pass"}`,
`goss_tests_run_outcomes_total{outcome="pass"} 1`,
},
},
"various-results-single-type": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SUCCESS,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SKIP,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.FAIL,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.UNKNOWN,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="pass",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="skip",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="fail",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="unknown",type="command"} 10`,
`goss_tests_outcomes_total{outcome="pass",type="command"} 1`,
`goss_tests_outcomes_total{outcome="skip",type="command"} 1`,
`goss_tests_outcomes_total{outcome="fail",type="command"} 1`,
`goss_tests_outcomes_total{outcome="unknown",type="command"} 1`,
`goss_tests_run_duration_milliseconds{outcome="fail"}`,
`goss_tests_run_outcomes_total{outcome="fail"} 1`,
},
},
"various-results-multiple-types": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SUCCESS,
},
{
ResourceType: "File",
Duration: 10 * time.Millisecond,
Result: resource.SKIP,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.FAIL,
},
{
ResourceType: "File",
Duration: 10 * time.Millisecond,
Result: resource.UNKNOWN,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="pass",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="skip",type="file"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="fail",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="unknown",type="file"} 10`,
`goss_tests_outcomes_total{outcome="pass",type="command"} 1`,
`goss_tests_outcomes_total{outcome="skip",type="file"} 1`,
`goss_tests_outcomes_total{outcome="fail",type="command"} 1`,
`goss_tests_outcomes_total{outcome="unknown",type="file"} 1`,
`goss_tests_run_duration_milliseconds{outcome="fail"}`,
`goss_tests_run_outcomes_total{outcome="fail"} 1`,
},
},
"unknown-skip": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.UNKNOWN,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SKIP,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="skip",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="unknown",type="command"} 10`,
`goss_tests_outcomes_total{outcome="skip",type="command"} 1`,
`goss_tests_outcomes_total{outcome="unknown",type="command"} 1`,
`goss_tests_run_duration_milliseconds{outcome="unknown"}`,
`goss_tests_run_outcomes_total{outcome="unknown"} 1`,
},
},
"unknown-fail": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.UNKNOWN,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.FAIL,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="fail",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="unknown",type="command"} 10`,
`goss_tests_outcomes_total{outcome="fail",type="command"} 1`,
`goss_tests_outcomes_total{outcome="unknown",type="command"} 1`,
`goss_tests_run_duration_milliseconds{outcome="fail"}`,
`goss_tests_run_outcomes_total{outcome="fail"} 1`,
},
},
"unknown-success": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.UNKNOWN,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SUCCESS,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="pass",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="unknown",type="command"} 10`,
`goss_tests_outcomes_total{outcome="pass",type="command"} 1`,
`goss_tests_outcomes_total{outcome="unknown",type="command"} 1`,
`goss_tests_run_duration_milliseconds{outcome="unknown"}`,
`goss_tests_run_outcomes_total{outcome="unknown"} 1`,
},
},
"skip-unknown": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SKIP,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.UNKNOWN,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="skip",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="unknown",type="command"} 10`,
`goss_tests_outcomes_total{outcome="skip",type="command"} 1`,
`goss_tests_outcomes_total{outcome="unknown",type="command"} 1`,
`goss_tests_run_duration_milliseconds{outcome="unknown"}`,
`goss_tests_run_outcomes_total{outcome="unknown"} 1`,
},
},
"skip-fail": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SKIP,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.FAIL,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="skip",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="fail",type="command"} 10`,
`goss_tests_outcomes_total{outcome="skip",type="command"} 1`,
`goss_tests_outcomes_total{outcome="fail",type="command"} 1`,
`goss_tests_run_duration_milliseconds{outcome="fail"}`,
`goss_tests_run_outcomes_total{outcome="fail"} 1`,
},
},
"skip-success": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SKIP,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SUCCESS,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="pass",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="skip",type="command"} 10`,
`goss_tests_outcomes_total{outcome="pass",type="command"} 1`,
`goss_tests_outcomes_total{outcome="skip",type="command"} 1`,
`goss_tests_run_duration_milliseconds{outcome="pass"}`,
`goss_tests_run_outcomes_total{outcome="pass"} 1`,
},
},
"fail-unknown": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.FAIL,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.UNKNOWN,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="fail",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="unknown",type="command"} 10`,
`goss_tests_outcomes_total{outcome="fail",type="command"} 1`,
`goss_tests_outcomes_total{outcome="unknown",type="command"} 1`,
`goss_tests_run_duration_milliseconds{outcome="fail"}`,
`goss_tests_run_outcomes_total{outcome="fail"} 1`,
},
},
"fail-skip": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.FAIL,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SKIP,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="fail",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="skip",type="command"} 10`,
`goss_tests_outcomes_total{outcome="fail",type="command"} 1`,
`goss_tests_outcomes_total{outcome="skip",type="command"} 1`,
`goss_tests_run_duration_milliseconds{outcome="fail"}`,
`goss_tests_run_outcomes_total{outcome="fail"} 1`,
},
},
"fail-success": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.FAIL,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SUCCESS,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="fail",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="pass",type="command"} 10`,
`goss_tests_outcomes_total{outcome="fail",type="command"} 1`,
`goss_tests_outcomes_total{outcome="pass",type="command"} 1`,
`goss_tests_run_duration_milliseconds{outcome="fail"}`,
`goss_tests_run_outcomes_total{outcome="fail"} 1`,
},
},
"success-unknown": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SUCCESS,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.UNKNOWN,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="pass",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="unknown",type="command"} 10`,
`goss_tests_outcomes_total{outcome="pass",type="command"} 1`,
`goss_tests_outcomes_total{outcome="unknown",type="command"} 1`,
`goss_tests_run_duration_milliseconds{outcome="unknown"}`,
`goss_tests_run_outcomes_total{outcome="unknown"} 1`,
},
},
"success-skip": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SUCCESS,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SKIP,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="pass",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="skip",type="command"} 10`,
`goss_tests_outcomes_total{outcome="pass",type="command"} 1`,
`goss_tests_outcomes_total{outcome="skip",type="command"} 1`,
`goss_tests_run_duration_milliseconds{outcome="pass"}`,
`goss_tests_run_outcomes_total{outcome="pass"} 1`,
},
},
"success-fail": {
results: []resource.TestResult{
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.SUCCESS,
},
{
ResourceType: "Command",
Duration: 10 * time.Millisecond,
Result: resource.FAIL,
},
},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="pass",type="command"} 10`,
`goss_tests_outcomes_duration_milliseconds{outcome="fail",type="command"} 10`,
`goss_tests_outcomes_total{outcome="pass",type="command"} 1`,
`goss_tests_outcomes_total{outcome="fail",type="command"} 1`,
`goss_tests_run_duration_milliseconds{outcome="fail"}`,
`goss_tests_run_outcomes_total{outcome="fail"} 1`,
},
},
"no-results": {
results: []resource.TestResult{},
expectedMetrics: []string{
`goss_tests_run_duration_milliseconds{outcome="unknown"}`,
`goss_tests_run_outcomes_total{outcome="unknown"} 1`,
},
},
"verbose": {
results: []resource.TestResult{
{
ResourceType: "Command",
ResourceId: "some command here",
Duration: 10 * time.Millisecond,
Result: resource.SUCCESS,
},
{
ResourceType: "Command",
ResourceId: "something else here",
Duration: 10 * time.Millisecond,
Result: resource.SUCCESS,
},
{
ResourceType: "File",
ResourceId: "/path/to/file",
Duration: 10 * time.Millisecond,
Result: resource.FAIL,
},
},
formatOptions: []string{foVerbose},
expectedMetrics: []string{
`goss_tests_outcomes_duration_milliseconds{outcome="pass",resource_id="some command here",type="command"} 10`,
`goss_tests_outcomes_total{outcome="pass",resource_id="some command here",type="command"} 1`,
`goss_tests_outcomes_duration_milliseconds{outcome="pass",resource_id="something else here",type="command"} 10`,
`goss_tests_outcomes_total{outcome="pass",resource_id="something else here",type="command"} 1`,
`goss_tests_outcomes_duration_milliseconds{outcome="fail",resource_id="/path/to/file",type="file"} 10`,
`goss_tests_outcomes_total{outcome="fail",resource_id="/path/to/file",type="file"} 1`,
`goss_tests_run_duration_milliseconds{outcome="fail"}`,
`goss_tests_run_outcomes_total{outcome="fail"} 1`,
},
},
}
for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
buf := &bytes.Buffer{}
outputer := &Prometheus{}
config := util.OutputConfig{
FormatOptions: testCase.formatOptions,
}
defer resetMetrics()
exitCode := outputer.Output(buf, makeResults(testCase.results...), config)
assert.Equal(t, 0, exitCode)
output := buf.String()
t.Logf(output)
for _, metric := range testCase.expectedMetrics {
assert.Contains(t, output, metric)
}
})
}
}
func makeResults(results ...resource.TestResult) <-chan []resource.TestResult {
out := make(chan []resource.TestResult)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
out <- append([]resource.TestResult{}, results...)
}()
go func() {
wg.Wait()
close(out)
}()
return out
}
func resetMetrics() {
registry = nil
}
func TestCanChangeOverallOutcome(t *testing.T) {
testCases := map[string]map[string]bool{
resource.OutcomePass: {
resource.OutcomePass: true,
resource.OutcomeSkip: false,
resource.OutcomeFail: true,
resource.OutcomeUnknown: true,
},
resource.OutcomeSkip: {
resource.OutcomePass: true,
resource.OutcomeSkip: true,
resource.OutcomeFail: true,
resource.OutcomeUnknown: true,
},
resource.OutcomeFail: {
resource.OutcomePass: false,
resource.OutcomeSkip: false,
resource.OutcomeFail: false,
resource.OutcomeUnknown: false,
},
resource.OutcomeUnknown: {
resource.OutcomePass: false,
resource.OutcomeSkip: false,
resource.OutcomeFail: true,
resource.OutcomeUnknown: false,
},
}
for current, expectations := range testCases {
for result, canChange := range expectations {
t.Run(fmt.Sprintf("%s/%s", current, result), func(t *testing.T) {
assert.Equalf(t, canChange, canChangeOverallOutcome(current, result), "canChangeOverallOutcome(%v, %v)", current, result)
})
}
}
}
goss-0.4.9/outputs/rspecish.go 0000664 0000000 0000000 00000004247 14675050513 0016406 0 ustar 00root root 0000000 0000000 package outputs
import (
"fmt"
"io"
"log"
"strings"
"time"
"github.com/goss-org/goss/resource"
"github.com/goss-org/goss/util"
)
type Rspecish struct{}
func (r Rspecish) ValidOptions() []*formatOption {
return []*formatOption{}
}
func (r Rspecish) Output(w io.Writer, results <-chan []resource.TestResult,
outConfig util.OutputConfig) (exitCode int) {
sort := util.IsValueInList(foSort, outConfig.FormatOptions)
results = getResults(results, sort)
var startTime time.Time
var endTime time.Time
testCount := 0
var failedOrSkipped [][]resource.TestResult
var skipped, failed int
for resultGroup := range results {
failedOrSkippedGroup := []resource.TestResult{}
for _, testResult := range resultGroup {
// Calculates the start and end times based on the start of the first test
// and end of the last test, this allows the time/duration to be stable
// FIXME: move this to shared code
if startTime.IsZero() || testResult.StartTime.Before(startTime) {
startTime = testResult.StartTime
}
if endTime.IsZero() || testResult.EndTime.After(endTime) {
endTime = testResult.EndTime
}
switch testResult.Result {
case resource.SUCCESS:
logTrace("TRACE", "SUCCESS", testResult, false)
fmt.Fprint(w, green("."))
case resource.SKIP:
logTrace("TRACE", "SKIP", testResult, false)
fmt.Fprint(w, yellow("S"))
failedOrSkippedGroup = append(failedOrSkippedGroup, testResult)
skipped++
case resource.FAIL:
logTrace("TRACE", "FAIL", testResult, false)
fmt.Fprint(w, red("F"))
failedOrSkippedGroup = append(failedOrSkippedGroup, testResult)
failed++
}
testCount++
}
if len(failedOrSkippedGroup) > 0 {
failedOrSkipped = append(failedOrSkipped, failedOrSkippedGroup)
}
}
fmt.Fprint(w, "\n\n")
includeRaw := !util.IsValueInList(foExcludeRaw, outConfig.FormatOptions)
fmt.Fprint(w, failedOrSkippedSummary(failedOrSkipped, includeRaw))
outstr := summary(startTime, endTime, testCount, failed, skipped)
fmt.Fprint(w, outstr)
resstr := strings.ReplaceAll(outstr, "\n", " ")
if failed > 0 {
log.Printf("[DEBUG] FAIL SUMMARY: %s", resstr)
return 1
}
log.Printf("[DEBUG] OK SUMMARY: %s", resstr)
return 0
}
goss-0.4.9/outputs/silent.go 0000664 0000000 0000000 00000001041 14675050513 0016051 0 ustar 00root root 0000000 0000000 package outputs
import (
"io"
"github.com/goss-org/goss/resource"
"github.com/goss-org/goss/util"
)
type Silent struct{}
func (r Silent) ValidOptions() []*formatOption {
return []*formatOption{}
}
func (r Silent) Output(w io.Writer, results <-chan []resource.TestResult,
outConfig util.OutputConfig) (exitCode int) {
var failed int
for resultGroup := range results {
for _, testResult := range resultGroup {
switch testResult.Result {
case resource.FAIL:
failed++
}
}
}
if failed > 0 {
return 1
}
return 0
}
goss-0.4.9/outputs/structured.go 0000664 0000000 0000000 00000005532 14675050513 0016770 0 ustar 00root root 0000000 0000000 package outputs
import (
"encoding/json"
"fmt"
"io"
"time"
"github.com/goss-org/goss/resource"
"github.com/goss-org/goss/util"
)
// Structured is a output formatter that logs into a StructuredOutput structure
type Structured struct{}
func (r Structured) ValidOptions() []*formatOption {
return []*formatOption{
{name: foPretty},
{name: foSort},
}
}
// StructuredTestResult is an individual test result with additional human friendly summary
type StructuredTestResult struct {
resource.TestResult
SummaryLine string `json:"summary-line"`
SummaryLineCompact string `json:"summary-line-compact"`
}
// StructureTestSummary holds summary information about a test run
type StructureTestSummary struct {
TestCount int `json:"test-count"`
Failed int `json:"failed-count"`
TotalDuration time.Duration `json:"total-duration"`
}
// StructuredOutput is the full output structure for the structured output format
type StructuredOutput struct {
Results []StructuredTestResult `json:"results"`
Summary StructureTestSummary `json:"summary"`
SummaryLine string `json:"summary-line"`
}
// String represents human friendly representation of the test summary
func (s *StructureTestSummary) String() string {
return fmt.Sprintf("Count: %d, Failed: %d, Duration: %.3fs", s.TestCount, s.Failed, s.TotalDuration.Seconds())
}
// Output processes output from tests into StructuredOutput written to w as a string
func (r Structured) Output(w io.Writer, results <-chan []resource.TestResult, outConfig util.OutputConfig) (exitCode int) {
includeRaw := !util.IsValueInList(foExcludeRaw, outConfig.FormatOptions)
sort := util.IsValueInList(foSort, outConfig.FormatOptions)
results = getResults(results, sort)
result := &StructuredOutput{
Results: []StructuredTestResult{},
Summary: StructureTestSummary{},
}
var startTime time.Time
var endTime time.Time
for resultGroup := range results {
for _, testResult := range resultGroup {
if startTime.IsZero() || testResult.StartTime.Before(startTime) {
startTime = testResult.StartTime
}
if endTime.IsZero() || testResult.EndTime.After(endTime) {
endTime = testResult.EndTime
}
r := StructuredTestResult{
TestResult: testResult,
SummaryLine: humanizeResult(testResult, false, includeRaw),
SummaryLineCompact: humanizeResult(testResult, true, includeRaw),
}
if testResult.Result == resource.FAIL {
result.Summary.Failed++
}
result.Summary.TestCount++
result.Results = append(result.Results, r)
}
}
result.Summary.TotalDuration = endTime.Sub(startTime)
result.SummaryLine = result.Summary.String()
var j []byte
if util.IsValueInList(foPretty, outConfig.FormatOptions) {
j, _ = json.MarshalIndent(result, "", " ")
} else {
j, _ = json.Marshal(result)
}
fmt.Fprintln(w, string(j))
return 0
}
goss-0.4.9/outputs/tap.go 0000664 0000000 0000000 00000002652 14675050513 0015350 0 ustar 00root root 0000000 0000000 package outputs
import (
"fmt"
"io"
"strconv"
"github.com/goss-org/goss/resource"
"github.com/goss-org/goss/util"
)
type Tap struct{}
func (r Tap) ValidOptions() []*formatOption {
return []*formatOption{
{name: foSort},
}
}
func (r Tap) Output(w io.Writer, results <-chan []resource.TestResult,
outConfig util.OutputConfig) (exitCode int) {
includeRaw := !util.IsValueInList(foExcludeRaw, outConfig.FormatOptions)
sort := util.IsValueInList(foSort, outConfig.FormatOptions)
results = getResults(results, sort)
testCount := 0
failed := 0
var summary map[int]string = make(map[int]string)
for resultGroup := range results {
for _, testResult := range resultGroup {
switch testResult.Result {
case resource.SUCCESS:
summary[testCount] = "ok " + strconv.Itoa(testCount+1) + " - " + humanizeResult(testResult, true, includeRaw) + "\n"
case resource.FAIL:
summary[testCount] = "not ok " + strconv.Itoa(testCount+1) + " - " + humanizeResult(testResult, true, includeRaw) + "\n"
failed++
case resource.SKIP:
summary[testCount] = "ok " + strconv.Itoa(testCount+1) + " - # SKIP " + humanizeResult(testResult, true, includeRaw) + "\n"
default:
panic(fmt.Sprintf("Unexpected Result Code: %v\n", testResult.Result))
}
testCount++
}
}
fmt.Fprintf(w, "1..%d\n", testCount)
for i := 0; i < testCount; i++ {
fmt.Fprintf(w, "%s", summary[i])
}
if failed > 0 {
return 1
}
return 0
}
goss-0.4.9/outputs/traces.go 0000664 0000000 0000000 00000001364 14675050513 0016044 0 ustar 00root root 0000000 0000000 package outputs
import (
"log"
"github.com/goss-org/goss/resource"
)
func logTrace(level string, msg string, testResult resource.TestResult, withIntResult bool) {
if withIntResult {
log.Printf("[%s] %s: %s => %s (%s %+v %+v) [%.02f] [%d]",
level,
msg,
testResult.ResourceType,
testResult.ResourceId,
testResult.Property,
testResult.MatcherResult.Expected,
testResult.MatcherResult.Actual,
testResult.Duration.Seconds(),
testResult.Result,
)
} else {
log.Printf("[%s] %s: %s => %s (%s %+v %+v) [%.02f]",
level,
msg,
testResult.ResourceType,
testResult.ResourceId,
testResult.Property,
testResult.MatcherResult.Expected,
testResult.MatcherResult.Actual,
testResult.Duration.Seconds(),
)
}
}
goss-0.4.9/release-build.sh 0000775 0000000 0000000 00000001741 14675050513 0015564 0 ustar 00root root 0000000 0000000 #!/usr/bin/env bash
set -euo pipefail
platform_spec="${1:?"Must supply name of release binary to build e.g. goss-linux-amd64"}"
version_stamp="${TRAVIS_TAG:-"0.0.0"}"
# Split platform_spec into platform/arch segments
IFS='- ' read -r -a segments <<< "${platform_spec}"
os="${segments[0]}"
arch="${segments[1]}"
if [[ "${segments[0]}" == "alpha" ]]; then
os="${segments[1]}"
arch="${segments[2]}"
fi
output_dir="release/"
output_fname="goss-${platform_spec}"
if [[ "${os}" == "windows" ]]; then
output_fname="${output_fname}.exe"
fi
output="${output_dir}/${output_fname}"
GOOS="${os}" GOARCH="${arch}" CGO_ENABLED=0 go build \
-ldflags "-X github.com/goss-org/goss/util.Version=${version_stamp} -s -w" \
-o "${output}" \
github.com/goss-org/goss/cmd/goss
chmod +x "${output}"
function __sha256sum {
if [[ "$OSTYPE" == "darwin"* ]]; then
shasum -a 256 "$1"
else
sha256sum "$1"
fi
}
(cd "$output_dir" && __sha256sum "${output_fname}" > "${output_fname}.sha256")
goss-0.4.9/resource/ 0000775 0000000 0000000 00000000000 14675050513 0014334 5 ustar 00root root 0000000 0000000 goss-0.4.9/resource/addr.go 0000664 0000000 0000000 00000004210 14675050513 0015572 0 ustar 00root root 0000000 0000000 package resource
import (
"context"
"fmt"
"time"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
type Addr struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
id string `json:"-" yaml:"-"`
Address string `json:"address,omitempty" yaml:"address,omitempty"`
LocalAddress string `json:"local-address,omitempty" yaml:"local-address,omitempty"`
Reachable matcher `json:"reachable" yaml:"reachable"`
Timeout int `json:"timeout" yaml:"timeout"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
}
type idKey struct{}
const (
AddrResourceKey = "addr"
AddResourceName = "Addr"
)
func init() {
registerResource(AddrResourceKey, &Addr{})
}
func (a *Addr) ID() string {
if a.Address != "" && a.Address != a.id {
return fmt.Sprintf("%s: %s", a.id, a.Address)
}
return a.id
}
func (a *Addr) SetID(id string) { a.id = id }
func (a *Addr) SetSkip() { a.Skip = true }
func (a *Addr) TypeKey() string { return AddrResourceKey }
func (a *Addr) TypeName() string { return AddResourceName }
// FIXME: Can this be refactored?
func (a *Addr) GetTitle() string { return a.Title }
func (a *Addr) GetMeta() meta { return a.Meta }
func (a *Addr) GetAddress() string {
if a.Address != "" {
return a.Address
}
return a.id
}
func (a *Addr) Validate(sys *system.System) []TestResult {
ctx := context.WithValue(context.Background(), idKey{}, a.ID())
skip := a.Skip
if a.Timeout == 0 {
a.Timeout = 500
}
sysAddr := sys.NewAddr(ctx, a.GetAddress(), sys, util.Config{Timeout: time.Duration(a.Timeout) * time.Millisecond, LocalAddress: a.LocalAddress})
var results []TestResult
results = append(results, ValidateValue(a, "reachable", a.Reachable, sysAddr.Reachable, skip))
return results
}
func NewAddr(sysAddr system.Addr, config util.Config) (*Addr, error) {
address := sysAddr.Address()
reachable, err := sysAddr.Reachable()
a := &Addr{
id: address,
Reachable: reachable,
Timeout: config.TimeOutMilliSeconds(),
LocalAddress: config.LocalAddress,
}
return a, err
}
goss-0.4.9/resource/command.go 0000664 0000000 0000000 00000006164 14675050513 0016310 0 ustar 00root root 0000000 0000000 package resource
import (
"bufio"
"context"
"fmt"
"io"
"strings"
"time"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
type Command struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
id string `json:"-" yaml:"-"`
Exec string `json:"exec,omitempty" yaml:"exec,omitempty"`
ExitStatus matcher `json:"exit-status" yaml:"exit-status"`
Stdout matcher `json:"stdout" yaml:"stdout"`
Stderr matcher `json:"stderr" yaml:"stderr"`
Timeout int `json:"timeout" yaml:"timeout"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
}
const (
CommandResourceKey = "command"
CommandResourceName = "Command"
)
func init() {
registerResource(CommandResourceKey, &Command{})
}
func (c *Command) ID() string { return c.id }
func (c *Command) SetID(id string) { c.id = id }
func (c *Command) SetSkip() { c.Skip = true }
func (c *Command) TypeKey() string { return CommandResourceKey }
func (c *Command) TypeName() string { return CommandResourceName }
func (c *Command) GetTitle() string { return c.Title }
func (c *Command) GetMeta() meta { return c.Meta }
func (c *Command) GetExec() string {
if c.Exec != "" {
return c.Exec
}
return c.id
}
func (c *Command) Validate(sys *system.System) []TestResult {
ctx := context.WithValue(context.Background(), idKey{}, c.ID())
skip := c.Skip
if c.Timeout == 0 {
c.Timeout = 10000
}
var results []TestResult
sysCommand := sys.NewCommand(ctx, c.GetExec(), sys, util.Config{Timeout: time.Duration(c.Timeout) * time.Millisecond})
cExitStatus := deprecateAtoI(c.ExitStatus, fmt.Sprintf("%s: command.exit-status", c.ID()))
results = append(results, ValidateValue(c, "exit-status", cExitStatus, sysCommand.ExitStatus, skip))
if isSet(c.Stdout) {
results = append(results, ValidateValue(c, "stdout", c.Stdout, sysCommand.Stdout, skip))
}
if isSet(c.Stderr) {
results = append(results, ValidateValue(c, "stderr", c.Stderr, sysCommand.Stderr, skip))
}
return results
}
func NewCommand(sysCommand system.Command, config util.Config) (*Command, error) {
command := sysCommand.Command()
exitStatus, err := sysCommand.ExitStatus()
c := &Command{
id: command,
ExitStatus: exitStatus,
Stdout: "",
Stderr: "",
Timeout: config.TimeOutMilliSeconds(),
}
if !contains(config.IgnoreList, "stdout") {
stdout, _ := sysCommand.Stdout()
outSlice := readerToSlice(stdout)
if len(outSlice) != 0 {
c.Stdout = outSlice
}
}
if !contains(config.IgnoreList, "stderr") {
stderr, _ := sysCommand.Stderr()
errSlice := readerToSlice(stderr)
if len(errSlice) != 0 {
c.Stderr = errSlice
}
}
return c, err
}
func escapePattern(s string) string {
if strings.HasPrefix(s, "!") || strings.HasPrefix(s, "/") {
return "\\" + s
}
return s
}
func readerToSlice(reader io.Reader) []string {
scanner := bufio.NewScanner(reader)
slice := []string{}
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
line = escapePattern(line)
if line != "" {
slice = append(slice, line)
}
}
return slice
}
goss-0.4.9/resource/dns.go 0000664 0000000 0000000 00000005223 14675050513 0015451 0 ustar 00root root 0000000 0000000 package resource
import (
"context"
"fmt"
"strings"
"time"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
type DNS struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
id string `json:"-" yaml:"-"`
Resolve string `json:"resolve,omitempty" yaml:"resolve,omitempty"`
Resolveable matcher `json:"resolveable,omitempty" yaml:"resolveable,omitempty"`
Resolvable matcher `json:"resolvable" yaml:"resolvable"`
Addrs matcher `json:"addrs,omitempty" yaml:"addrs,omitempty"`
Timeout int `json:"timeout" yaml:"timeout"`
Server string `json:"server,omitempty" yaml:"server,omitempty"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
}
const (
DNSResourceKey = "dns"
DNSResourceName = "DNS"
)
func init() {
registerResource(DNSResourceKey, &DNS{})
}
func (d *DNS) ID() string {
if d.Resolve != "" && d.Resolve != d.id {
return fmt.Sprintf("%s: %s", d.id, d.Resolve)
}
return d.id
}
func (d *DNS) SetID(id string) { d.id = id }
func (d *DNS) SetSkip() { d.Skip = true }
func (d *DNS) TypeKey() string { return DNSResourceKey }
func (d *DNS) TypeName() string { return DNSResourceName }
func (d *DNS) GetTitle() string { return d.Title }
func (d *DNS) GetMeta() meta { return d.Meta }
func (d *DNS) GetResolve() string {
if d.Resolve != "" {
return d.Resolve
}
return d.id
}
func (d *DNS) Validate(sys *system.System) []TestResult {
ctx := context.WithValue(context.Background(), idKey{}, d.ID())
skip := d.Skip
if d.Timeout == 0 {
d.Timeout = 500
}
sysDNS := sys.NewDNS(ctx, d.GetResolve(), sys, util.Config{Timeout: time.Duration(d.Timeout) * time.Millisecond, Server: d.Server})
var results []TestResult
// Backwards compatibility hack for now
if d.Resolvable == nil {
d.Resolvable = d.Resolveable
}
results = append(results, ValidateValue(d, "resolvable", d.Resolvable, sysDNS.Resolvable, skip))
if shouldSkip(results) {
skip = true
}
if d.Addrs != nil {
results = append(results, ValidateValue(d, "addrs", d.Addrs, sysDNS.Addrs, skip))
}
return results
}
func NewDNS(sysDNS system.DNS, config util.Config) (*DNS, error) {
var host string
if sysDNS.Qtype() != "" {
host = strings.Join([]string{sysDNS.Qtype(), sysDNS.Host()}, ":")
} else {
host = sysDNS.Host()
}
resolvable, err := sysDNS.Resolvable()
server := sysDNS.Server()
d := &DNS{
id: host,
Resolvable: resolvable,
Timeout: config.TimeOutMilliSeconds(),
Server: server,
}
if !contains(config.IgnoreList, "addrs") {
addrs, _ := sysDNS.Addrs()
d.Addrs = addrs
}
return d, err
}
goss-0.4.9/resource/file.go 0000664 0000000 0000000 00000011307 14675050513 0015604 0 ustar 00root root 0000000 0000000 package resource
import (
"context"
"fmt"
"os"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
type File struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
id string `json:"-" yaml:"-"`
Path string `json:"path,omitempty" yaml:"path,omitempty"`
Exists matcher `json:"exists" yaml:"exists"`
Mode matcher `json:"mode,omitempty" yaml:"mode,omitempty"`
Size matcher `json:"size,omitempty" yaml:"size,omitempty"`
Owner matcher `json:"owner,omitempty" yaml:"owner,omitempty"`
Uid matcher `json:"uid,omitempty" yaml:"uid,omitempty"`
Group matcher `json:"group,omitempty" yaml:"group,omitempty"`
Gid matcher `json:"gid,omitempty" yaml:"gid,omitempty"`
LinkedTo matcher `json:"linked-to,omitempty" yaml:"linked-to,omitempty"`
Filetype matcher `json:"filetype,omitempty" yaml:"filetype,omitempty"`
Contains matcher `json:"contains,omitempty" yaml:"contains,omitempty"`
Contents matcher `json:"contents" yaml:"contents"`
Md5 matcher `json:"md5,omitempty" yaml:"md5,omitempty"`
Sha256 matcher `json:"sha256,omitempty" yaml:"sha256,omitempty"`
Sha512 matcher `json:"sha512,omitempty" yaml:"sha512,omitempty"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
}
const (
FileResourceKey = "file"
FileResourceName = "File"
)
func init() {
registerResource(FileResourceKey, &File{})
}
func (f *File) ID() string {
if f.Path != "" && f.Path != f.id {
return fmt.Sprintf("%s: %s", f.id, f.Path)
}
return f.id
}
func (f *File) SetID(id string) { f.id = id }
func (f *File) SetSkip() { f.Skip = true }
func (f *File) TypeKey() string { return FileResourceKey }
func (f *File) TypeName() string { return FileResourceName }
func (f *File) GetTitle() string { return f.Title }
func (f *File) GetMeta() meta { return f.Meta }
func (f *File) GetPath() string {
if f.Path != "" {
return f.Path
}
return f.id
}
func (f *File) Validate(sys *system.System) []TestResult {
ctx := context.WithValue(context.Background(), idKey{}, f.ID())
skip := f.Skip
sysFile := sys.NewFile(ctx, f.GetPath(), sys, util.Config{})
var results []TestResult
results = append(results, ValidateValue(f, "exists", f.Exists, sysFile.Exists, skip))
if shouldSkip(results) {
skip = true
}
if f.Mode != nil {
results = append(results, ValidateValue(f, "mode", f.Mode, sysFile.Mode, skip))
}
if f.Owner != nil {
results = append(results, ValidateValue(f, "owner", f.Owner, sysFile.Owner, skip))
}
if f.Uid != nil {
results = append(results, ValidateValue(f, "uid", f.Uid, sysFile.Uid, skip))
}
if f.Group != nil {
results = append(results, ValidateValue(f, "group", f.Group, sysFile.Group, skip))
}
if f.Gid != nil {
results = append(results, ValidateValue(f, "gid", f.Gid, sysFile.Gid, skip))
}
if f.LinkedTo != nil {
results = append(results, ValidateValue(f, "linkedto", f.LinkedTo, sysFile.LinkedTo, skip))
}
if f.Filetype != nil {
results = append(results, ValidateValue(f, "filetype", f.Filetype, sysFile.Filetype, skip))
}
if isSet(f.Contains) {
fmt.Fprintf(os.Stderr, "DEPRECATION WARNING: file.contains has been renamed to file.contents\n")
results = append(results, ValidateValue(f, "contains", f.Contains, sysFile.Contents, skip))
}
if isSet(f.Contents) {
results = append(results, ValidateValue(f, "contents", f.Contents, sysFile.Contents, skip))
}
if f.Size != nil {
results = append(results, ValidateValue(f, "size", f.Size, sysFile.Size, skip))
}
if f.Md5 != nil {
results = append(results, ValidateValue(f, "md5", f.Md5, sysFile.Md5, skip))
}
if f.Sha256 != nil {
results = append(results, ValidateValue(f, "sha256", f.Sha256, sysFile.Sha256, skip))
}
if f.Sha512 != nil {
results = append(results, ValidateValue(f, "sha512", f.Sha512, sysFile.Sha512, skip))
}
return results
}
func NewFile(sysFile system.File, config util.Config) (*File, error) {
path := sysFile.Path()
exists, err := sysFile.Exists()
if err != nil {
return nil, err
}
f := &File{
id: path,
Exists: exists,
Contents: []string{},
}
if !contains(config.IgnoreList, "mode") {
if mode, err := sysFile.Mode(); err == nil {
f.Mode = mode
}
}
if !contains(config.IgnoreList, "owner") {
if owner, err := sysFile.Owner(); err == nil {
f.Owner = owner
}
}
if !contains(config.IgnoreList, "group") {
if group, err := sysFile.Group(); err == nil {
f.Group = group
}
}
if !contains(config.IgnoreList, "linked-to") {
if linkedTo, err := sysFile.LinkedTo(); err == nil {
f.LinkedTo = linkedTo
}
}
if !contains(config.IgnoreList, "filetype") {
if filetype, err := sysFile.Filetype(); err == nil {
f.Filetype = filetype
}
}
return f, nil
}
goss-0.4.9/resource/gomega.go 0000664 0000000 0000000 00000013332 14675050513 0016124 0 ustar 00root root 0000000 0000000 package resource
import (
"fmt"
"github.com/goss-org/goss/matchers"
"github.com/samber/lo"
)
func matcherToGomegaMatcher(matcher any) (matchers.GossMatcher, error) {
// Default matchers
switch x := matcher.(type) {
case string:
return matchers.WithSafeTransform(matchers.ToString{}, matchers.Equal(x)), nil
case float64, int:
return matchers.WithSafeTransform(matchers.ToNumeric{}, matchers.BeNumerically("eq", x)), nil
case bool:
return matchers.Equal(x), nil
case []any:
subMatchers, err := sliceToGomega(x, "")
if err != nil {
return nil, err
}
var interfaceSlice []any
for _, d := range subMatchers {
interfaceSlice = append(interfaceSlice, d)
}
return matchers.ContainElements(interfaceSlice...), nil
}
if matcher == nil {
return nil, fmt.Errorf("Syntax Error: Missing required attribute")
}
matcherMap, ok := matcher.(map[string]any)
if !ok {
return nil, invalidArgSyntaxError("matcher", "map", matcher)
//panic(fmt.Sprintf("Syntax Error: Unexpected matcher type: %T\n\n", matcher))
}
keys := lo.Keys(matcherMap)
if len(keys) > 1 {
return nil, fmt.Errorf("Syntax Error: Invalid matcher configuration. At a given nesting level, only one matcher is allowed. Found multiple matchers: %q", keys)
}
matchType := keys[0]
value := matcherMap[matchType]
switch matchType {
case "equal":
return matchers.Equal(value), nil
case "have-prefix":
v, isStr := value.(string)
if !isStr {
return nil, invalidArgSyntaxError("have-prefix", "string", value)
}
return matchers.WithSafeTransform(matchers.ToString{}, matchers.HavePrefix(v)), nil
case "have-suffix":
v, isStr := value.(string)
if !isStr {
return nil, invalidArgSyntaxError("have-suffix", "string", value)
}
return matchers.WithSafeTransform(matchers.ToString{}, matchers.HaveSuffix(v)), nil
case "match-regexp":
v, isStr := value.(string)
if !isStr {
return nil, invalidArgSyntaxError("match-regexp", "string", value)
}
return matchers.WithSafeTransform(matchers.ToString{}, matchers.MatchRegexp(v)), nil
case "contain-substring":
v, isStr := value.(string)
if !isStr {
return nil, invalidArgSyntaxError("contain-substring", "string", value)
}
return matchers.WithSafeTransform(matchers.ToString{}, matchers.ContainSubstring(v)), nil
case "have-len":
var v int
switch val := value.(type) {
case float64:
v = int(val)
case int:
v = val
default:
return nil, invalidArgSyntaxError("have-len", "numeric", value)
}
return matchers.HaveLen(v), nil
case "have-patterns":
_, isArr := value.([]any)
if !isArr {
return nil, invalidArgSyntaxError("have-patterns", "array", value)
}
return matchers.WithSafeTransform(matchers.ToString{}, matchers.HavePatterns(value)), nil
case "have-key":
subMatcher, err := matcherToGomegaMatcher(value)
if err != nil {
return nil, err
}
return matchers.HaveKey(subMatcher), nil
case "contain-element":
switch value.(type) {
case map[string]any, string, float64, int:
default:
return nil, invalidArgSyntaxError("contain-element", "matcher, string or numeric", value)
}
subMatcher, err := matcherToGomegaMatcher(value)
if err != nil {
return nil, err
}
return matchers.WithSafeTransform(matchers.ToArray{}, matchers.ContainElement(subMatcher)), nil
case "contain-elements":
subMatchers, err := sliceToGomega(value, "contains-elements")
if err != nil {
return nil, err
}
var interfaceSlice []any
for _, d := range subMatchers {
interfaceSlice = append(interfaceSlice, d)
}
return matchers.WithSafeTransform(matchers.ToArray{}, matchers.ContainElements(interfaceSlice...)), nil
case "not":
subMatcher, err := matcherToGomegaMatcher(value)
if err != nil {
return nil, err
}
return matchers.Not(subMatcher), nil
case "consist-of":
subMatchers, err := sliceToGomega(value, "consist-of")
if err != nil {
return nil, err
}
var interfaceSlice []any
for _, d := range subMatchers {
interfaceSlice = append(interfaceSlice, d)
}
return matchers.ConsistOf(interfaceSlice...), nil
case "and":
subMatchers, err := sliceToGomega(value, "and")
if err != nil {
return nil, err
}
return matchers.And(subMatchers...), nil
case "or":
subMatchers, err := sliceToGomega(value, "or")
if err != nil {
return nil, err
}
return matchers.Or(subMatchers...), nil
case "gt", "ge", "lt", "le":
return matchers.WithSafeTransform(matchers.ToNumeric{}, matchers.BeNumerically(matchType, value)), nil
case "semver-constraint":
v, isStr := value.(string)
if !isStr {
return nil, invalidArgSyntaxError("semver-constraint", "string", value)
}
return matchers.BeSemverConstraint(v), nil
case "gjson":
var subMatchers []matchers.GossMatcher
valueI, ok := value.(map[string]any)
if !ok {
return nil, invalidArgSyntaxError("gjson", "map", value)
}
for key, val := range valueI {
subMatcher, err := matcherToGomegaMatcher(val)
if err != nil {
return nil, err
}
subMatchers = append(subMatchers, matchers.WithSafeTransform(matchers.Gjson{Path: key}, subMatcher))
}
return matchers.And(subMatchers...), nil
default:
return nil, fmt.Errorf("Syntax Error: Unknown matcher: %s", matchType)
}
}
func sliceToGomega(value any, name string) ([]matchers.GossMatcher, error) {
valueI, ok := value.([]any)
if !ok {
return nil, invalidArgSyntaxError(name, "array", value)
}
var subMatchers []matchers.GossMatcher
for _, v := range valueI {
subMatcher, err := matcherToGomegaMatcher(v)
if err != nil {
return nil, err
}
subMatchers = append(subMatchers, subMatcher)
}
return subMatchers, nil
}
func invalidArgSyntaxError(name, expected string, value any) error {
return fmt.Errorf("Syntax Error: Invalid '%s' argument. Expected %s value, but received: %T: %q", name, expected, value, value)
}
goss-0.4.9/resource/gomega_test.go 0000664 0000000 0000000 00000010227 14675050513 0017163 0 ustar 00root root 0000000 0000000 package resource
import (
"encoding/json"
"testing"
"github.com/goss-org/goss/matchers"
"github.com/stretchr/testify/assert"
)
var gomegaTests = []struct {
in string
want any
useNegateTester bool
}{
// Default for simple types
{
in: `"foo"`,
want: matchers.WithSafeTransform(matchers.ToString{}, matchers.Equal("foo")),
},
{
in: `1`,
want: matchers.WithSafeTransform(matchers.ToNumeric{}, matchers.BeNumerically("eq", float64(1))),
},
{
in: `true`,
want: matchers.Equal(true),
},
// Default for Array
{
in: `["foo", "bar"]`,
want: matchers.ContainElements(
matchers.WithSafeTransform(matchers.ToString{}, matchers.Equal("foo")),
matchers.WithSafeTransform(matchers.ToString{}, matchers.Equal("bar"))),
useNegateTester: true,
},
// Numeric
// Golang json escapes '>', '<' symbols, so we use 'gt', 'le' instead
{
in: `{"gt": 1}`,
want: matchers.WithSafeTransform(matchers.ToNumeric{}, matchers.BeNumerically("gt", float64(1))),
},
{
in: `{"ge": 1}`,
want: matchers.WithSafeTransform(matchers.ToNumeric{}, matchers.BeNumerically("ge", float64(1))),
},
{
in: `{"lt": 1}`,
want: matchers.WithSafeTransform(matchers.ToNumeric{}, matchers.BeNumerically("lt", float64(1))),
},
{
in: `{"le": 1}`,
want: matchers.WithSafeTransform(matchers.ToNumeric{}, matchers.BeNumerically("le", float64(1))),
},
// String
{
in: `{"have-prefix": "foo"}`,
want: matchers.WithSafeTransform(matchers.ToString{}, matchers.HavePrefix("foo")),
},
{
in: `{"have-suffix": "foo"}`,
want: matchers.WithSafeTransform(matchers.ToString{}, matchers.HaveSuffix("foo")),
},
// Regex support is based on golangs regex engine https://golang.org/pkg/regexp/syntax/
{
in: `{"match-regexp": "foo"}`,
want: matchers.WithSafeTransform(matchers.ToString{}, matchers.MatchRegexp("foo")),
},
// Collection
{
in: `{"consist-of": ["foo"]}`,
want: matchers.ConsistOf(matchers.WithSafeTransform(matchers.ToString{}, matchers.Equal("foo"))),
},
{
in: `{"contain-element": "foo"}`,
want: matchers.WithSafeTransform(matchers.ToArray{},
matchers.ContainElement(
matchers.WithSafeTransform(matchers.ToString{},
matchers.Equal("foo")))),
},
{
in: `{"have-len": 3}`,
want: matchers.HaveLen(3),
},
{
in: `{"have-key": "foo"}`,
want: matchers.HaveKey(matchers.WithSafeTransform(matchers.ToString{}, matchers.Equal("foo"))),
},
// Negation
{
in: `{"not": "foo"}`,
want: matchers.Not(matchers.WithSafeTransform(matchers.ToString{}, matchers.Equal("foo"))),
},
// Complex logic
{
in: `{"and": ["foo", "foo"]}`,
want: matchers.And(
matchers.WithSafeTransform(matchers.ToString{},
matchers.Equal("foo")),
matchers.WithSafeTransform(matchers.ToString{},
matchers.Equal("foo")),
),
useNegateTester: true,
},
{
in: `{"and": [{"have-prefix": "foo"}, "foo"]}`,
want: matchers.And(
matchers.WithSafeTransform(matchers.ToString{},
matchers.HavePrefix("foo")),
matchers.WithSafeTransform(matchers.ToString{},
matchers.Equal("foo")),
),
useNegateTester: true,
},
{
in: `{"not": {"have-prefix": "foo"}}`,
want: matchers.Not(
matchers.WithSafeTransform(matchers.ToString{},
matchers.HavePrefix("foo"))),
},
{
in: `{"or": ["foo", "foo"]}`,
want: matchers.Or(
matchers.WithSafeTransform(matchers.ToString{},
matchers.Equal("foo")),
matchers.WithSafeTransform(matchers.ToString{},
matchers.Equal("foo"))),
},
{
in: `{"not": {"and": [{"have-prefix": "foo"}]}}`,
want: matchers.Not(
matchers.And(
matchers.WithSafeTransform(matchers.ToString{},
matchers.HavePrefix("foo")))),
},
// Semver Constraint
{
in: `{"semver-constraint": "> 1.0.0"}`,
want: matchers.BeSemverConstraint("> 1.0.0"),
},
}
func TestMatcherToGomegaMatcher(t *testing.T) {
for _, c := range gomegaTests {
var dat any
if err := json.Unmarshal([]byte(c.in), &dat); err != nil {
t.Fatal(err)
}
got, err := matcherToGomegaMatcher(dat)
if err != nil {
t.Fatal(err)
}
gomegaTestEqual(t, got, c.want, c.useNegateTester, c.in)
}
}
func gomegaTestEqual(t *testing.T, got, want any, useNegateTester bool, in string) {
assert.Equal(t, got, want)
}
goss-0.4.9/resource/gossfile.go 0000664 0000000 0000000 00000002502 14675050513 0016475 0 ustar 00root root 0000000 0000000 package resource
import (
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
type Gossfile struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
Path string `json:"-" yaml:"-"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
File string `json:"file,omitempty" yaml:"file,omitempty"`
}
const (
GossFileResourceKey = "gossfile"
GossFileResourceName = "Gossfile"
)
func init() {
registerResource(GossFileResourceKey, &Gossfile{})
}
func (g *Gossfile) ID() string { return g.Path }
func (g *Gossfile) SetID(id string) { g.Path = id }
func (g *Gossfile) SetSkip() {}
func (g *Gossfile) TypeKey() string { return GossFileResourceKey }
func (g *Gossfile) TypeName() string { return GossFileResourceName }
func (g *Gossfile) GetTitle() string { return g.Title }
func (g *Gossfile) GetMeta() meta { return g.Meta }
func (g *Gossfile) GetSkip() bool { return g.Skip }
func (g *Gossfile) GetGossfile() string {
if g.File != "" {
return g.File
}
return g.Path
}
func (g *Gossfile) Validate(sys *system.System) []TestResult {
return []TestResult{}
}
func NewGossfile(sysGossfile system.Gossfile, config util.Config) (*Gossfile, error) {
path := sysGossfile.Path()
return &Gossfile{
Path: path,
}, nil
}
goss-0.4.9/resource/group.go 0000664 0000000 0000000 00000004157 14675050513 0016026 0 ustar 00root root 0000000 0000000 package resource
import (
"context"
"fmt"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
type Group struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
id string `json:"-" yaml:"-"`
Groupname string `json:"groupname,omitempty" yaml:"groupname,omitempty"`
Exists matcher `json:"exists" yaml:"exists"`
GID matcher `json:"gid,omitempty" yaml:"gid,omitempty"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
}
const (
GroupResourceKey = "group"
GroupResourceName = "Group"
)
func init() {
registerResource(GroupResourceKey, &Group{})
}
func (g *Group) ID() string {
if g.Groupname != "" && g.Groupname != g.id {
return fmt.Sprintf("%s: %s", g.id, g.Groupname)
}
return g.id
}
func (g *Group) SetID(id string) { g.id = id }
func (g *Group) SetSkip() { g.Skip = true }
func (g *Group) TypeKey() string { return GroupResourceKey }
func (g *Group) TypeName() string { return GroupResourceName }
func (g *Group) GetTitle() string { return g.Title }
func (g *Group) GetMeta() meta { return g.Meta }
func (g *Group) GetGroupname() string {
if g.Groupname != "" {
return g.Groupname
}
return g.id
}
func (g *Group) Validate(sys *system.System) []TestResult {
ctx := context.WithValue(context.Background(), idKey{}, g.ID())
skip := g.Skip
sysgroup := sys.NewGroup(ctx, g.GetGroupname(), sys, util.Config{})
var results []TestResult
results = append(results, ValidateValue(g, "exists", g.Exists, sysgroup.Exists, skip))
if shouldSkip(results) {
skip = true
}
if g.GID != nil {
gGID := deprecateAtoI(g.GID, fmt.Sprintf("%s: group.gid", g.ID()))
results = append(results, ValidateValue(g, "gid", gGID, sysgroup.GID, skip))
}
return results
}
func NewGroup(sysGroup system.Group, config util.Config) (*Group, error) {
groupname := sysGroup.Groupname()
exists, _ := sysGroup.Exists()
g := &Group{
id: groupname,
Exists: exists,
}
if !contains(config.IgnoreList, "stderr") {
if gid, err := sysGroup.GID(); err == nil {
g.GID = gid
}
}
return g, nil
}
goss-0.4.9/resource/http.go 0000664 0000000 0000000 00000007740 14675050513 0015652 0 ustar 00root root 0000000 0000000 package resource
import (
"context"
"fmt"
"time"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
type HTTP struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
id string `json:"-" yaml:"-"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
Method string `json:"method,omitempty" yaml:"method,omitempty"`
Status matcher `json:"status" yaml:"status"`
AllowInsecure bool `json:"allow-insecure" yaml:"allow-insecure"`
NoFollowRedirects bool `json:"no-follow-redirects" yaml:"no-follow-redirects"`
Timeout int `json:"timeout,omitempty" yaml:"timeout,omitempty"`
RequestHeader []string `json:"request-headers,omitempty" yaml:"request-headers,omitempty"`
RequestBody string `json:"request-body,omitempty" yaml:"request-body,omitempty"`
Headers matcher `json:"headers,omitempty" yaml:"headers,omitempty"`
Body matcher `json:"body,omitempty" yaml:"body,omitempty"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty"`
CAFile string `json:"ca-file,omitempty" yaml:"ca-file,omitempty"`
CertFile string `json:"cert-file,omitempty" yaml:"cert-file,omitempty"`
KeyFile string `json:"key-file,omitempty" yaml:"key-file,omitempty"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
Proxy string `json:"proxy,omitempty" yaml:"proxy,omitempty"`
}
const (
HTTPResourceKey = "http"
HTTPResourceName = "HTTP"
)
func init() {
registerResource(HTTPResourceKey, &HTTP{})
}
func (h *HTTP) ID() string {
if h.URL != "" && h.URL != h.id {
return fmt.Sprintf("%s: %s", h.id, h.URL)
}
return h.id
}
func (h *HTTP) SetID(id string) { h.id = id }
func (u *HTTP) SetSkip() { u.Skip = true }
func (u *HTTP) TypeKey() string { return HTTPResourceKey }
func (u *HTTP) TypeName() string { return HTTPResourceName }
// FIXME: Can this be refactored?
func (r *HTTP) GetTitle() string { return r.Title }
func (r *HTTP) GetMeta() meta { return r.Meta }
func (r *HTTP) getURL() string {
if r.URL != "" {
return r.URL
}
return r.id
}
func (u *HTTP) Validate(sys *system.System) []TestResult {
ctx := context.WithValue(context.Background(), idKey{}, u.ID())
skip := u.Skip
if u.Timeout == 0 {
u.Timeout = 5000
}
sysHTTP := sys.NewHTTP(ctx, u.getURL(), sys, util.Config{
AllowInsecure: u.AllowInsecure,
CAFile: u.CAFile,
CertFile: u.CertFile,
KeyFile: u.KeyFile,
NoFollowRedirects: u.NoFollowRedirects,
Timeout: time.Duration(u.Timeout) * time.Millisecond, Username: u.Username, Password: u.Password, Proxy: u.Proxy,
RequestHeader: u.RequestHeader, RequestBody: u.RequestBody, Method: u.Method})
sysHTTP.SetAllowInsecure(u.AllowInsecure)
sysHTTP.SetNoFollowRedirects(u.NoFollowRedirects)
var results []TestResult
results = append(results, ValidateValue(u, "status", u.Status, sysHTTP.Status, skip))
if shouldSkip(results) {
skip = true
}
if isSet(u.Headers) {
results = append(results, ValidateValue(u, "Headers", u.Headers, sysHTTP.Headers, skip))
}
if isSet(u.Body) {
results = append(results, ValidateValue(u, "Body", u.Body, sysHTTP.Body, skip))
}
return results
}
func NewHTTP(sysHTTP system.HTTP, config util.Config) (*HTTP, error) {
http := sysHTTP.HTTP()
status, err := sysHTTP.Status()
u := &HTTP{
id: http,
Status: status,
RequestHeader: []string{},
Headers: nil,
Body: []string{},
AllowInsecure: config.AllowInsecure,
NoFollowRedirects: config.NoFollowRedirects,
Timeout: config.TimeOutMilliSeconds(),
Username: config.Username,
Password: config.Password,
Proxy: config.Proxy,
}
return u, err
}
goss-0.4.9/resource/interface.go 0000664 0000000 0000000 00000004640 14675050513 0016627 0 ustar 00root root 0000000 0000000 package resource
import (
"context"
"fmt"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
type Interface struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
id string `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Exists matcher `json:"exists" yaml:"exists"`
Addrs matcher `json:"addrs,omitempty" yaml:"addrs,omitempty"`
MTU matcher `json:"mtu,omitempty" yaml:"mtu,omitempty"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
}
const (
InterfaceResourceKey = "interface"
InterfaceResourceName = "Interface"
)
func init() {
registerResource(InterfaceResourceKey, &Interface{})
}
func (i *Interface) ID() string {
if i.Name != "" && i.Name != i.id {
return fmt.Sprintf("%s: %s", i.id, i.Name)
}
return i.id
}
func (i *Interface) SetID(id string) { i.id = id }
func (i *Interface) SetSkip() { i.Skip = true }
func (i *Interface) TypeKey() string { return InterfaceResourceKey }
func (i *Interface) TypeName() string { return InterfaceResourceName }
// FIXME: Can this be refactored?
func (i *Interface) GetTitle() string { return i.Title }
func (i *Interface) GetMeta() meta { return i.Meta }
func (i *Interface) GetName() string {
if i.Name != "" {
return i.Name
}
return i.id
}
func (i *Interface) Validate(sys *system.System) []TestResult {
ctx := context.WithValue(context.Background(), idKey{}, i.ID())
skip := i.Skip
sysInterface := sys.NewInterface(ctx, i.GetName(), sys, util.Config{})
var results []TestResult
results = append(results, ValidateValue(i, "exists", i.Exists, sysInterface.Exists, skip))
if shouldSkip(results) {
skip = true
}
if i.Addrs != nil {
results = append(results, ValidateValue(i, "addrs", i.Addrs, sysInterface.Addrs, skip))
}
if i.MTU != nil {
results = append(results, ValidateValue(i, "mtu", i.MTU, sysInterface.MTU, skip))
}
return results
}
func NewInterface(sysInterface system.Interface, config util.Config) (*Interface, error) {
name := sysInterface.Name()
exists, _ := sysInterface.Exists()
i := &Interface{
id: name,
Exists: exists,
}
if !contains(config.IgnoreList, "addrs") {
if addrs, err := sysInterface.Addrs(); err == nil {
i.Addrs = addrs
}
}
if !contains(config.IgnoreList, "mtu") {
if mtu, err := sysInterface.MTU(); err == nil {
i.MTU = mtu
}
}
return i, nil
}
goss-0.4.9/resource/kernel_param.go 0000664 0000000 0000000 00000003572 14675050513 0017332 0 ustar 00root root 0000000 0000000 package resource
import (
"context"
"fmt"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
type KernelParam struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
id string `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Key string `json:"-" yaml:"-"`
Value matcher `json:"value" yaml:"value"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
}
const (
KernelParamResourceKey = "kernel-param"
KernelParamResourceName = "KernelParam"
)
func init() {
registerResource(KernelParamResourceKey, &KernelParam{})
}
func (k *KernelParam) ID() string {
if k.Name != "" && k.Name != k.id {
return fmt.Sprintf("%s: %s", k.id, k.Name)
}
return k.id
}
func (a *KernelParam) SetID(id string) { a.id = id }
func (a *KernelParam) SetSkip() { a.Skip = true }
func (a *KernelParam) TypeKey() string { return KernelParamResourceKey }
func (a *KernelParam) TypeName() string { return KernelParamResourceName }
// FIXME: Can this be refactored?
func (k *KernelParam) GetTitle() string { return k.Title }
func (k *KernelParam) GetMeta() meta { return k.Meta }
func (k *KernelParam) GetName() string {
if k.Name != "" {
return k.Name
}
return k.id
}
func (k *KernelParam) Validate(sys *system.System) []TestResult {
ctx := context.WithValue(context.Background(), idKey{}, k.ID())
skip := k.Skip
sysKernelParam := sys.NewKernelParam(ctx, k.GetName(), sys, util.Config{})
var results []TestResult
results = append(results, ValidateValue(k, "value", k.Value, sysKernelParam.Value, skip))
return results
}
func NewKernelParam(sysKernelParam system.KernelParam, config util.Config) (*KernelParam, error) {
key := sysKernelParam.Key()
value, err := sysKernelParam.Value()
a := &KernelParam{
id: key,
Value: value,
}
return a, err
}
goss-0.4.9/resource/matching.go 0000664 0000000 0000000 00000005767 14675050513 0016474 0 ustar 00root root 0000000 0000000 package resource
import (
"encoding/json"
"fmt"
"io"
"reflect"
"strings"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
type Matching struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
Content any `json:"content,omitempty" yaml:"content,omitempty"`
AsReader bool `json:"as-reader,omitempty" yaml:"as-reader,omitempty"`
id string `json:"-" yaml:"-"`
Matches matcher `json:"matches" yaml:"matches"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
}
const (
MatchingResourceKey = "mount"
MatchingResourceName = "Mount"
)
type MatchingMap map[string]*Matching
func (a *Matching) ID() string { return a.id }
func (a *Matching) SetID(id string) { a.id = id }
func (a *Matching) SetSkip() {}
func (a *Matching) TypeKey() string { return MatchingResourceKey }
func (a *Matching) TypeName() string { return MatchingResourceName }
// FIXME: Can this be refactored?
func (r *Matching) GetTitle() string { return r.Title }
func (r *Matching) GetMeta() meta { return r.Meta }
func (a *Matching) Validate(sys *system.System) []TestResult {
skip := false
if a.Skip {
skip = true
}
var stub interface{}
if a.AsReader {
s := fmt.Sprintf("%v", a.Content)
// ValidateValue expects a function
stub = func() (io.Reader, error) {
return strings.NewReader(s), nil
}
} else {
// ValidateValue expects a function
stub = func() (any, error) {
return a.Content, nil
}
}
var results []TestResult
results = append(results, ValidateValue(a, "matches", a.Matches, stub, skip))
return results
}
func (ret *MatchingMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i any) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := Matching{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Matching
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *MatchingMap) UnmarshalYAML(unmarshal func(v any) error) error {
// Validate configuration
zero := Matching{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Matching
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
goss-0.4.9/resource/mount.go 0000664 0000000 0000000 00000006674 14675050513 0016042 0 ustar 00root root 0000000 0000000 package resource
import (
"context"
"fmt"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
"time"
)
type Mount struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
id string `json:"-" yaml:"-"`
MountPoint string `json:"mountpoint,omitempty" yaml:"mountpoint,omitempty"`
Exists matcher `json:"exists" yaml:"exists"`
Opts matcher `json:"opts,omitempty" yaml:"opts,omitempty"`
VfsOpts matcher `json:"vfs-opts,omitempty" yaml:"vfs-opts,omitempty"`
Source matcher `json:"source,omitempty" yaml:"source,omitempty"`
Filesystem matcher `json:"filesystem,omitempty" yaml:"filesystem,omitempty"`
Timeout int `json:"timeout" yaml:"timeout"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
Usage matcher `json:"usage,omitempty" yaml:"usage,omitempty"`
}
const (
MountResourceKey = "mount"
MountResourceName = "Mount"
)
func init() {
registerResource(MountResourceKey, &Mount{})
}
func (m *Mount) ID() string {
if m.MountPoint != "" && m.MountPoint != m.id {
return fmt.Sprintf("%s: %s", m.id, m.MountPoint)
}
return m.id
}
func (m *Mount) SetID(id string) { m.id = id }
func (m *Mount) SetSkip() { m.Skip = true }
func (m *Mount) TypeKey() string { return MountResourceKey }
func (m *Mount) TypeName() string { return MountResourceName }
// FIXME: Can this be refactored?
func (m *Mount) GetTitle() string { return m.Title }
func (m *Mount) GetMeta() meta { return m.Meta }
func (m *Mount) GetMountPoint() string {
if m.MountPoint != "" {
return m.MountPoint
}
return m.id
}
func (m *Mount) Validate(sys *system.System) []TestResult {
ctx := context.WithValue(context.Background(), idKey{}, m.ID())
skip := m.Skip
if m.Timeout == 0 {
m.Timeout = 1000
}
sysMount := sys.NewMount(ctx, m.GetMountPoint(), sys, util.Config{Timeout: time.Duration(m.Timeout) * time.Millisecond})
var results []TestResult
results = append(results, ValidateValue(m, "exists", m.Exists, sysMount.Exists, skip))
if shouldSkip(results) {
skip = true
}
if m.Opts != nil {
results = append(results, ValidateValue(m, "opts", m.Opts, sysMount.Opts, skip))
}
if m.VfsOpts != nil {
results = append(results, ValidateValue(m, "vfs-opts", m.VfsOpts, sysMount.VfsOpts, skip))
}
if m.Source != nil {
results = append(results, ValidateValue(m, "source", m.Source, sysMount.Source, skip))
}
if m.Filesystem != nil {
results = append(results, ValidateValue(m, "filesystem", m.Filesystem, sysMount.Filesystem, skip))
}
if m.Usage != nil {
results = append(results, ValidateValue(m, "usage", m.Usage, sysMount.Usage, skip))
}
return results
}
func NewMount(sysMount system.Mount, config util.Config) (*Mount, error) {
mountPoint := sysMount.MountPoint()
exists, _ := sysMount.Exists()
m := &Mount{
id: mountPoint,
Exists: exists,
Timeout: config.TimeOutMilliSeconds(),
}
if !contains(config.IgnoreList, "opts") {
if opts, err := sysMount.Opts(); err == nil {
m.Opts = opts
}
}
if !contains(config.IgnoreList, "vfs-opts") {
if vfsOpts, err := sysMount.VfsOpts(); err == nil {
m.VfsOpts = vfsOpts
}
}
if !contains(config.IgnoreList, "source") {
if source, err := sysMount.Source(); err == nil {
m.Source = source
}
}
if !contains(config.IgnoreList, "filesystem") {
if filesystem, err := sysMount.Filesystem(); err == nil {
m.Filesystem = filesystem
}
}
return m, nil
}
goss-0.4.9/resource/package.go 0000664 0000000 0000000 00000004160 14675050513 0016257 0 ustar 00root root 0000000 0000000 package resource
import (
"context"
"fmt"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
type Package struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
id string `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Installed matcher `json:"installed" yaml:"installed"`
Versions matcher `json:"versions,omitempty" yaml:"versions,omitempty"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
}
const (
PackageResourceKey = "package"
PackageResourceName = "Package"
)
func init() {
registerResource(PackageResourceKey, &Package{})
}
func (p *Package) ID() string {
if p.Name != "" && p.Name != p.id {
return fmt.Sprintf("%s: %s", p.id, p.Name)
}
return p.id
}
func (p *Package) SetID(id string) { p.id = id }
func (p *Package) SetSkip() { p.Skip = true }
func (p *Package) TypeKey() string { return PackageResourceKey }
func (p *Package) TypeName() string { return PackageResourceName }
func (p *Package) GetTitle() string { return p.Title }
func (p *Package) GetMeta() meta { return p.Meta }
func (p *Package) GetName() string {
if p.Name != "" {
return p.Name
}
return p.id
}
func (p *Package) Validate(sys *system.System) []TestResult {
ctx := context.WithValue(context.Background(), idKey{}, p.ID())
skip := p.Skip
sysPkg := sys.NewPackage(ctx, p.GetName(), sys, util.Config{})
var results []TestResult
results = append(results, ValidateValue(p, "installed", p.Installed, sysPkg.Installed, skip))
if shouldSkip(results) {
skip = true
}
if p.Versions != nil {
results = append(results, ValidateValue(p, "version", p.Versions, sysPkg.Versions, skip))
}
return results
}
func NewPackage(sysPackage system.Package, config util.Config) (*Package, error) {
name := sysPackage.Name()
installed, _ := sysPackage.Installed()
p := &Package{
id: name,
Installed: installed,
}
if !contains(config.IgnoreList, "versions") {
if versions, err := sysPackage.Versions(); err == nil {
p.Versions = versions
}
}
return p, nil
}
goss-0.4.9/resource/port.go 0000664 0000000 0000000 00000003741 14675050513 0015654 0 ustar 00root root 0000000 0000000 package resource
import (
"context"
"fmt"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
type Port struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
id string `json:"-" yaml:"-"`
Port string `json:"port,omitempty" yaml:"port,omitempty"`
Listening matcher `json:"listening" yaml:"listening"`
IP matcher `json:"ip,omitempty" yaml:"ip,omitempty"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
}
const (
PortResourceKey = "port"
PortResourceName = "Port"
)
func init() {
registerResource(PortResourceKey, &Port{})
}
func (p *Port) ID() string {
if p.Port != "" && p.Port != p.id {
return fmt.Sprintf("%s: %s", p.id, p.Port)
}
return p.id
}
func (p *Port) SetID(id string) { p.id = id }
func (p *Port) SetSkip() { p.Skip = true }
func (p *Port) TypeKey() string { return PortResourceKey }
func (p *Port) TypeName() string { return PortResourceName }
func (p *Port) GetTitle() string { return p.Title }
func (p *Port) GetMeta() meta { return p.Meta }
func (p *Port) GetPort() string {
if p.Port != "" {
return p.Port
}
return p.id
}
func (p *Port) Validate(sys *system.System) []TestResult {
ctx := context.WithValue(context.Background(), idKey{}, p.ID())
skip := p.Skip
sysPort := sys.NewPort(ctx, p.GetPort(), sys, util.Config{})
var results []TestResult
results = append(results, ValidateValue(p, "listening", p.Listening, sysPort.Listening, skip))
if shouldSkip(results) {
skip = true
}
if p.IP != nil {
results = append(results, ValidateValue(p, "ip", p.IP, sysPort.IP, skip))
}
return results
}
func NewPort(sysPort system.Port, config util.Config) (*Port, error) {
port := sysPort.Port()
listening, _ := sysPort.Listening()
p := &Port{
id: port,
Listening: listening,
}
if !contains(config.IgnoreList, "ip") {
if ip, err := sysPort.IP(); err == nil {
p.IP = ip
}
}
return p, nil
}
goss-0.4.9/resource/process.go 0000664 0000000 0000000 00000003426 14675050513 0016346 0 ustar 00root root 0000000 0000000 package resource
import (
"context"
"fmt"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
type Process struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
id string `json:"-" yaml:"-"`
Comm string `json:"comm,omitempty" yaml:"comm,omitempty"`
Running matcher `json:"running" yaml:"running"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
}
const (
ProcessResourceKey = "process"
ProcessResourceName = "Process"
)
func init() {
registerResource(ProcessResourceKey, &Process{})
}
func (p *Process) ID() string {
if p.Comm != "" && p.Comm != p.id {
return fmt.Sprintf("%s: %s", p.id, p.Comm)
}
return p.id
}
func (p *Process) SetID(id string) { p.id = id }
func (p *Process) SetSkip() { p.Skip = true }
func (p *Process) TypeKey() string { return ProcessResourceKey }
func (p *Process) TypeName() string { return ProcessResourceName }
func (p *Process) GetTitle() string { return p.Title }
func (p *Process) GetMeta() meta { return p.Meta }
func (p *Process) GetComm() string {
if p.Comm != "" {
return p.Comm
}
return p.id
}
func (p *Process) Validate(sys *system.System) []TestResult {
ctx := context.WithValue(context.Background(), idKey{}, p.ID())
skip := p.Skip
sysProcess := sys.NewProcess(ctx, p.GetComm(), sys, util.Config{})
var results []TestResult
results = append(results, ValidateValue(p, "running", p.Running, sysProcess.Running, skip))
return results
}
func NewProcess(sysProcess system.Process, config util.Config) (*Process, error) {
executable := sysProcess.Executable()
running, err := sysProcess.Running()
if err != nil {
return nil, err
}
return &Process{
id: executable,
Running: running,
}, nil
}
goss-0.4.9/resource/resource.go 0000664 0000000 0000000 00000002574 14675050513 0016522 0 ustar 00root root 0000000 0000000 package resource
import (
"fmt"
"os"
"path/filepath"
"strconv"
"sync"
"github.com/goss-org/goss/system"
)
type Resource interface {
Validate(sys *system.System) []TestResult
SetID(string)
SetSkip()
TypeKey() string
TypeName() string
}
var (
resourcesMu sync.Mutex
resources = map[string]Resource{}
)
func registerResource(key string, resource Resource) {
resourcesMu.Lock()
resources[key] = resource
resourcesMu.Unlock()
}
func Resources() map[string]Resource {
return resources
}
type ResourceRead interface {
ID() string
GetTitle() string
GetMeta() meta
}
type matcher any
type meta map[string]any
func contains(a []string, s string) bool {
for _, e := range a {
if m, _ := filepath.Match(e, s); m {
return true
}
}
return false
}
func deprecateAtoI(depr any, desc string) any {
s, ok := depr.(string)
if !ok {
return depr
}
fmt.Fprintf(os.Stderr, "DEPRECATION WARNING: %s should be an integer not a string\n", desc)
i, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return float64(i)
}
func shouldSkip(results []TestResult) bool {
if len(results) < 1 {
return false
}
if results[0].Err != nil || results[0].Result != SUCCESS || results[0].MatcherResult.Actual == false {
return true
}
return false
}
func isSet(i interface{}) bool {
switch v := i.(type) {
case []interface{}:
return len(v) > 0
default:
return i != nil
}
}
goss-0.4.9/resource/resource_list.go 0000664 0000000 0000000 00000106512 14675050513 0017552 0 ustar 00root root 0000000 0000000 // This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
package resource
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
type AddrMap map[string]*Addr
func (r AddrMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*Addr, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewAddr(ctx, sr, sys, config)
res, err := NewAddr(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}
func (r AddrMap) AppendSysResourceIfExists(sr string, sys *system.System) (*Addr, system.Addr, bool, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewAddr(ctx, sr, sys, util.Config{})
res, err := NewAddr(sysres, util.Config{})
if err != nil {
return nil, nil, false, err
}
if e, _ := sysres.Exists(); !e {
return res, sysres, false, nil
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true, nil
}
func (ret *AddrMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := Addr{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Addr
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *AddrMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := Addr{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Addr
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
type CommandMap map[string]*Command
func (r CommandMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*Command, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewCommand(ctx, sr, sys, config)
res, err := NewCommand(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}
func (r CommandMap) AppendSysResourceIfExists(sr string, sys *system.System) (*Command, system.Command, bool, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewCommand(ctx, sr, sys, util.Config{})
res, err := NewCommand(sysres, util.Config{})
if err != nil {
return nil, nil, false, err
}
if e, _ := sysres.Exists(); !e {
return res, sysres, false, nil
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true, nil
}
func (ret *CommandMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := Command{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Command
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *CommandMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := Command{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Command
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
type DNSMap map[string]*DNS
func (r DNSMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*DNS, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewDNS(ctx, sr, sys, config)
res, err := NewDNS(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}
func (r DNSMap) AppendSysResourceIfExists(sr string, sys *system.System) (*DNS, system.DNS, bool, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewDNS(ctx, sr, sys, util.Config{})
res, err := NewDNS(sysres, util.Config{})
if err != nil {
return nil, nil, false, err
}
if e, _ := sysres.Exists(); !e {
return res, sysres, false, nil
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true, nil
}
func (ret *DNSMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := DNS{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*DNS
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *DNSMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := DNS{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*DNS
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
type FileMap map[string]*File
func (r FileMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*File, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewFile(ctx, sr, sys, config)
res, err := NewFile(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}
func (r FileMap) AppendSysResourceIfExists(sr string, sys *system.System) (*File, system.File, bool, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewFile(ctx, sr, sys, util.Config{})
res, err := NewFile(sysres, util.Config{})
if err != nil {
return nil, nil, false, err
}
if e, _ := sysres.Exists(); !e {
return res, sysres, false, nil
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true, nil
}
func (ret *FileMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := File{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*File
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *FileMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := File{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*File
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
type GossfileMap map[string]*Gossfile
func (r GossfileMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*Gossfile, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewGossfile(ctx, sr, sys, config)
res, err := NewGossfile(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}
func (r GossfileMap) AppendSysResourceIfExists(sr string, sys *system.System) (*Gossfile, system.Gossfile, bool, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewGossfile(ctx, sr, sys, util.Config{})
res, err := NewGossfile(sysres, util.Config{})
if err != nil {
return nil, nil, false, err
}
if e, _ := sysres.Exists(); !e {
return res, sysres, false, nil
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true, nil
}
func (ret *GossfileMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := Gossfile{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Gossfile
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *GossfileMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := Gossfile{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Gossfile
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
type GroupMap map[string]*Group
func (r GroupMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*Group, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewGroup(ctx, sr, sys, config)
res, err := NewGroup(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}
func (r GroupMap) AppendSysResourceIfExists(sr string, sys *system.System) (*Group, system.Group, bool, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewGroup(ctx, sr, sys, util.Config{})
res, err := NewGroup(sysres, util.Config{})
if err != nil {
return nil, nil, false, err
}
if e, _ := sysres.Exists(); !e {
return res, sysres, false, nil
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true, nil
}
func (ret *GroupMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := Group{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Group
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *GroupMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := Group{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Group
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
type PackageMap map[string]*Package
func (r PackageMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*Package, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewPackage(ctx, sr, sys, config)
res, err := NewPackage(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}
func (r PackageMap) AppendSysResourceIfExists(sr string, sys *system.System) (*Package, system.Package, bool, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewPackage(ctx, sr, sys, util.Config{})
res, err := NewPackage(sysres, util.Config{})
if err != nil {
return nil, nil, false, err
}
if e, _ := sysres.Exists(); !e {
return res, sysres, false, nil
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true, nil
}
func (ret *PackageMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := Package{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Package
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *PackageMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := Package{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Package
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
type PortMap map[string]*Port
func (r PortMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*Port, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewPort(ctx, sr, sys, config)
res, err := NewPort(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}
func (r PortMap) AppendSysResourceIfExists(sr string, sys *system.System) (*Port, system.Port, bool, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewPort(ctx, sr, sys, util.Config{})
res, err := NewPort(sysres, util.Config{})
if err != nil {
return nil, nil, false, err
}
if e, _ := sysres.Exists(); !e {
return res, sysres, false, nil
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true, nil
}
func (ret *PortMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := Port{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Port
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *PortMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := Port{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Port
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
type ProcessMap map[string]*Process
func (r ProcessMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*Process, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewProcess(ctx, sr, sys, config)
res, err := NewProcess(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}
func (r ProcessMap) AppendSysResourceIfExists(sr string, sys *system.System) (*Process, system.Process, bool, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewProcess(ctx, sr, sys, util.Config{})
res, err := NewProcess(sysres, util.Config{})
if err != nil {
return nil, nil, false, err
}
if e, _ := sysres.Exists(); !e {
return res, sysres, false, nil
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true, nil
}
func (ret *ProcessMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := Process{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Process
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *ProcessMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := Process{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Process
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
type ServiceMap map[string]*Service
func (r ServiceMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*Service, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewService(ctx, sr, sys, config)
res, err := NewService(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}
func (r ServiceMap) AppendSysResourceIfExists(sr string, sys *system.System) (*Service, system.Service, bool, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewService(ctx, sr, sys, util.Config{})
res, err := NewService(sysres, util.Config{})
if err != nil {
return nil, nil, false, err
}
if e, _ := sysres.Exists(); !e {
return res, sysres, false, nil
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true, nil
}
func (ret *ServiceMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := Service{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Service
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *ServiceMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := Service{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Service
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
type UserMap map[string]*User
func (r UserMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*User, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewUser(ctx, sr, sys, config)
res, err := NewUser(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}
func (r UserMap) AppendSysResourceIfExists(sr string, sys *system.System) (*User, system.User, bool, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewUser(ctx, sr, sys, util.Config{})
res, err := NewUser(sysres, util.Config{})
if err != nil {
return nil, nil, false, err
}
if e, _ := sysres.Exists(); !e {
return res, sysres, false, nil
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true, nil
}
func (ret *UserMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := User{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*User
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *UserMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := User{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*User
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
type KernelParamMap map[string]*KernelParam
func (r KernelParamMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*KernelParam, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewKernelParam(ctx, sr, sys, config)
res, err := NewKernelParam(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}
func (r KernelParamMap) AppendSysResourceIfExists(sr string, sys *system.System) (*KernelParam, system.KernelParam, bool, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewKernelParam(ctx, sr, sys, util.Config{})
res, err := NewKernelParam(sysres, util.Config{})
if err != nil {
return nil, nil, false, err
}
if e, _ := sysres.Exists(); !e {
return res, sysres, false, nil
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true, nil
}
func (ret *KernelParamMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := KernelParam{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*KernelParam
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *KernelParamMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := KernelParam{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*KernelParam
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
type MountMap map[string]*Mount
func (r MountMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*Mount, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewMount(ctx, sr, sys, config)
res, err := NewMount(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}
func (r MountMap) AppendSysResourceIfExists(sr string, sys *system.System) (*Mount, system.Mount, bool, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewMount(ctx, sr, sys, util.Config{})
res, err := NewMount(sysres, util.Config{})
if err != nil {
return nil, nil, false, err
}
if e, _ := sysres.Exists(); !e {
return res, sysres, false, nil
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true, nil
}
func (ret *MountMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := Mount{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Mount
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *MountMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := Mount{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Mount
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
type InterfaceMap map[string]*Interface
func (r InterfaceMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*Interface, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewInterface(ctx, sr, sys, config)
res, err := NewInterface(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}
func (r InterfaceMap) AppendSysResourceIfExists(sr string, sys *system.System) (*Interface, system.Interface, bool, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewInterface(ctx, sr, sys, util.Config{})
res, err := NewInterface(sysres, util.Config{})
if err != nil {
return nil, nil, false, err
}
if e, _ := sysres.Exists(); !e {
return res, sysres, false, nil
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true, nil
}
func (ret *InterfaceMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := Interface{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Interface
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *InterfaceMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := Interface{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*Interface
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
type HTTPMap map[string]*HTTP
func (r HTTPMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*HTTP, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewHTTP(ctx, sr, sys, config)
res, err := NewHTTP(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}
func (r HTTPMap) AppendSysResourceIfExists(sr string, sys *system.System) (*HTTP, system.HTTP, bool, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewHTTP(ctx, sr, sys, util.Config{})
res, err := NewHTTP(sysres, util.Config{})
if err != nil {
return nil, nil, false, err
}
if e, _ := sysres.Exists(); !e {
return res, sysres, false, nil
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true, nil
}
func (ret *HTTPMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := HTTP{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*HTTP
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *HTTPMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := HTTP{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*HTTP
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
goss-0.4.9/resource/resource_list_genny.go 0000664 0000000 0000000 00000006161 14675050513 0020751 0 ustar 00root root 0000000 0000000 //go:build genny
// +build genny
package resource
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"
"github.com/cheekybits/genny/generic"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
//go:generate genny -in=$GOFILE -out=resource_list.go gen "ResourceType=Addr,Command,DNS,File,Gossfile,Group,Package,Port,Process,Service,User,KernelParam,Mount,Interface,HTTP"
//go:generate sed -i -e "/^\\/\\/ +build genny/d" resource_list.go
//go:generate sed -i -e "/^\\/\\/go:.*/d" resource_list.go
//go:generate sed -i -e "s/aelsabbahy/goss-org/" resource_list.go
//go:generate goimports -w resource_list.go resource_list.go
type ResourceType generic.Type
type ResourceTypeMap map[string]*ResourceType
func (r ResourceTypeMap) AppendSysResource(sr string, sys *system.System, config util.Config) (*ResourceType, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewResourceType(ctx, sr, sys, config)
res, err := NewResourceType(sysres, config)
if err != nil {
return nil, err
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, nil
}
func (r ResourceTypeMap) AppendSysResourceIfExists(sr string, sys *system.System) (*ResourceType, system.ResourceType, bool, error) {
ctx := context.WithValue(context.Background(), idKey{}, sr)
sysres := sys.NewResourceType(ctx, sr, sys, util.Config{})
res, err := NewResourceType(sysres, util.Config{})
if err != nil {
return nil, nil, false, err
}
if e, _ := sysres.Exists(); !e {
return res, sysres, false, nil
}
if old_res, ok := r[res.ID()]; ok {
res.Title = old_res.Title
res.Meta = old_res.Meta
}
r[res.ID()] = res
return res, sysres, true, nil
}
func (ret *ResourceTypeMap) UnmarshalJSON(data []byte) error {
// Curried json.Unmarshal
unmarshal := func(i interface{}) error {
if err := json.Unmarshal(data, i); err != nil {
return err
}
return nil
}
// Validate configuration
zero := ResourceType{}
whitelist, err := util.WhitelistAttrs(zero, util.JSON)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*ResourceType
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
func (ret *ResourceTypeMap) UnmarshalYAML(unmarshal func(v interface{}) error) error {
// Validate configuration
zero := ResourceType{}
whitelist, err := util.WhitelistAttrs(zero, util.YAML)
if err != nil {
return err
}
if err := util.ValidateSections(unmarshal, zero, whitelist); err != nil {
return err
}
var tmp map[string]*ResourceType
if err := unmarshal(&tmp); err != nil {
return err
}
typ := reflect.TypeOf(zero)
typs := strings.Split(typ.String(), ".")[1]
for id, res := range tmp {
if res == nil {
return fmt.Errorf("Could not parse resource %s:%s", typs, id)
}
res.SetID(id)
}
*ret = tmp
return nil
}
goss-0.4.9/resource/service.go 0000664 0000000 0000000 00000004412 14675050513 0016324 0 ustar 00root root 0000000 0000000 package resource
import (
"context"
"fmt"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
type Service struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
id string `json:"-" yaml:"-"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Enabled matcher `json:"enabled" yaml:"enabled"`
Running matcher `json:"running" yaml:"running"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
RunLevels matcher `json:"runlevels,omitempty" yaml:"runlevels,omitempty"`
}
const (
ServiceResourceKey = "service"
ServiceResourceName = "Service"
)
func init() {
registerResource(ServiceResourceKey, &Service{})
}
func (s *Service) ID() string {
if s.Name != "" && s.Name != s.id {
return fmt.Sprintf("%s: %s", s.id, s.Name)
}
return s.id
}
func (s *Service) SetID(id string) { s.id = id }
func (s *Service) SetSkip() { s.Skip = true }
func (s *Service) TypeKey() string { return ServiceResourceKey }
func (s *Service) TypeName() string { return ServiceResourceName }
func (s *Service) GetTitle() string { return s.Title }
func (s *Service) GetMeta() meta { return s.Meta }
func (s *Service) GetName() string {
if s.Name != "" {
return s.Name
}
return s.id
}
func (s *Service) Validate(sys *system.System) []TestResult {
ctx := context.WithValue(context.Background(), idKey{}, s.ID())
skip := s.Skip
sysservice := sys.NewService(ctx, s.GetName(), sys, util.Config{})
var results []TestResult
if s.Enabled != nil {
results = append(results, ValidateValue(s, "enabled", s.Enabled, sysservice.Enabled, skip))
}
if s.Running != nil {
results = append(results, ValidateValue(s, "running", s.Running, sysservice.Running, skip))
}
if s.RunLevels != nil {
results = append(results, ValidateValue(s, "runlevels", s.RunLevels, sysservice.RunLevels, skip))
}
return results
}
func NewService(sysService system.Service, config util.Config) (*Service, error) {
service := sysService.Service()
enabled, err := sysService.Enabled()
if err != nil {
return nil, err
}
running, err := sysService.Running()
if err != nil {
return nil, err
}
return &Service{
id: service,
Enabled: enabled,
Running: running,
}, nil
}
goss-0.4.9/resource/user.go 0000664 0000000 0000000 00000006407 14675050513 0015650 0 ustar 00root root 0000000 0000000 package resource
import (
"context"
"fmt"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
type User struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Meta meta `json:"meta,omitempty" yaml:"meta,omitempty"`
id string `json:"-" yaml:"-"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`
Exists matcher `json:"exists" yaml:"exists"`
UID matcher `json:"uid,omitempty" yaml:"uid,omitempty"`
GID matcher `json:"gid,omitempty" yaml:"gid,omitempty"`
Groups matcher `json:"groups,omitempty" yaml:"groups,omitempty"`
Home matcher `json:"home,omitempty" yaml:"home,omitempty"`
Shell matcher `json:"shell,omitempty" yaml:"shell,omitempty"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
}
const (
UserResourceKey = "user"
UserResourceName = "User"
)
func init() {
registerResource(UserResourceKey, &User{})
}
func (u *User) ID() string {
if u.Username != "" && u.Username != u.id {
return fmt.Sprintf("%s: %s", u.id, u.Username)
}
return u.id
}
func (u *User) SetID(id string) { u.id = id }
func (u *User) SetSkip() { u.Skip = true }
func (u *User) TypeKey() string { return UserResourceKey }
func (u *User) TypeName() string { return UserResourceName }
func (u *User) GetTitle() string { return u.Title }
func (u *User) GetMeta() meta { return u.Meta }
func (u *User) GetUsername() string {
if u.Username != "" {
return u.Username
}
return u.id
}
func (u *User) Validate(sys *system.System) []TestResult {
ctx := context.WithValue(context.Background(), idKey{}, u.ID())
skip := u.Skip
sysuser := sys.NewUser(ctx, u.GetUsername(), sys, util.Config{})
var results []TestResult
results = append(results, ValidateValue(u, "exists", u.Exists, sysuser.Exists, skip))
if shouldSkip(results) {
skip = true
}
if u.UID != nil {
uUID := deprecateAtoI(u.UID, fmt.Sprintf("%s: user.uid", u.Username))
results = append(results, ValidateValue(u, "uid", uUID, sysuser.UID, skip))
}
if u.GID != nil {
uGID := deprecateAtoI(u.GID, fmt.Sprintf("%s: user.gid", u.Username))
results = append(results, ValidateValue(u, "gid", uGID, sysuser.GID, skip))
}
if u.Home != nil {
results = append(results, ValidateValue(u, "home", u.Home, sysuser.Home, skip))
}
if u.Groups != nil {
results = append(results, ValidateValue(u, "groups", u.Groups, sysuser.Groups, skip))
}
if u.Shell != nil {
results = append(results, ValidateValue(u, "shell", u.Shell, sysuser.Shell, skip))
}
return results
}
func NewUser(sysUser system.User, config util.Config) (*User, error) {
username := sysUser.Username()
exists, _ := sysUser.Exists()
u := &User{
id: username,
Exists: exists,
}
if !contains(config.IgnoreList, "uid") {
if uid, err := sysUser.UID(); err == nil {
u.UID = uid
}
}
if !contains(config.IgnoreList, "gid") {
if gid, err := sysUser.GID(); err == nil {
u.GID = gid
}
}
if !contains(config.IgnoreList, "groups") {
if groups, err := sysUser.Groups(); err == nil {
u.Groups = groups
}
}
if !contains(config.IgnoreList, "home") {
if home, err := sysUser.Home(); err == nil {
u.Home = home
}
}
if !contains(config.IgnoreList, "shell") {
if shell, err := sysUser.Shell(); err == nil {
u.Shell = shell
}
}
return u, nil
}
goss-0.4.9/resource/validate.go 0000664 0000000 0000000 00000011644 14675050513 0016462 0 ustar 00root root 0000000 0000000 package resource
import (
"fmt"
"io"
"reflect"
"strings"
"time"
"github.com/goss-org/goss/matchers"
)
const (
Value = iota
Values
Contains
)
const (
SUCCESS = iota
FAIL
SKIP
UNKNOWN
)
const (
OutcomePass = "pass"
OutcomeFail = "fail"
OutcomeSkip = "skip"
OutcomeUnknown = "unknown"
)
var humanOutcomes map[int]string = map[int]string{
UNKNOWN: OutcomeUnknown,
SUCCESS: OutcomePass,
FAIL: OutcomeFail,
SKIP: OutcomeSkip,
}
func HumanOutcomes() map[int]string {
return humanOutcomes
}
type ValidateError string
func (g ValidateError) Error() string { return string(g) }
func toValidateError(err error) *ValidateError {
if err == nil {
return nil
}
ve := ValidateError(err.Error())
return &ve
}
type TestResult struct {
Successful bool `json:"successful" yaml:"successful"`
Skipped bool `json:"skipped" yaml:"skipped"`
// Resource data
ResourceId string `json:"resource-id" yaml:"resource-id"`
ResourceType string `json:"resource-type" yaml:"resource-type"`
Property string `json:"property" yaml:"property"`
// User added info
Title string `json:"title" yaml:"title"`
Meta meta `json:"meta" yaml:"meta"`
// Result
Result int `json:"result" yaml:"result"`
Err *ValidateError `json:"err" yaml:"err"`
MatcherResult matchers.MatcherResult `json:"matcher-result" yaml:"matcher-result"`
StartTime time.Time `json:"start-time" yaml:"start-time"`
EndTime time.Time `json:"end-time" yaml:"end-time"`
Duration time.Duration `json:"duration" yaml:"duration"`
}
// ToOutcome converts the enum to a human-friendly string.
func (tr TestResult) ToOutcome() string {
switch tr.Result {
case SUCCESS:
return OutcomePass
case FAIL:
return OutcomeFail
case SKIP:
return OutcomeSkip
default:
return OutcomeUnknown
}
}
func (t TestResult) SortKey() string {
return fmt.Sprintf("%s:%s", t.ResourceType, t.ResourceId)
}
func skipResult(typeS string, id string, title string, meta meta, property string, startTime time.Time) TestResult {
endTime := time.Now()
return TestResult{
Result: SKIP,
Skipped: true,
ResourceType: typeS,
ResourceId: id,
Title: title,
Meta: meta,
Property: property,
StartTime: startTime,
EndTime: endTime,
Duration: endTime.Sub(startTime),
}
}
func ValidateValue(res ResourceRead, property string, expectedValue any, actual any, skip bool) TestResult {
if f, ok := actual.(func() (io.Reader, error)); ok {
if _, ok := expectedValue.([]any); !ok {
actual = func() (string, error) {
v, err := f()
if err != nil {
return "", err
}
i, err := matchers.ReaderToString{}.Transform(v)
if err != nil {
return "", err
}
return i.(string), nil
}
}
}
return ValidateGomegaValue(res, property, expectedValue, actual, skip)
}
func ValidateGomegaValue(res ResourceRead, property string, expectedValue any, actual any, skip bool) TestResult {
id := res.ID()
title := res.GetTitle()
meta := res.GetMeta()
typ := reflect.TypeOf(res)
typeS := strings.Split(typ.String(), ".")[1]
startTime := time.Now()
if skip {
return skipResult(
typeS,
id,
title,
meta,
property,
startTime,
)
}
var foundValue any
var gomegaMatcher matchers.GossMatcher
var err error
switch f := actual.(type) {
case func() (bool, error):
foundValue, err = f()
case func() (string, error):
foundValue, err = f()
case func() (int, error):
foundValue, err = f()
case func() ([]string, error):
foundValue, err = f()
case func() (any, error):
foundValue, err = f()
case func() (io.Reader, error):
foundValue, err = f()
gomegaMatcher = matchers.HavePatterns(expectedValue)
default:
err = fmt.Errorf("Unknown method signature: %t", f)
}
var success bool
if gomegaMatcher == nil && err == nil {
gomegaMatcher, err = matcherToGomegaMatcher(expectedValue)
}
if err != nil {
endTime := time.Now()
return TestResult{
Result: FAIL,
ResourceType: typeS,
ResourceId: id,
Title: title,
Meta: meta,
Property: property,
Err: toValidateError(err),
StartTime: startTime,
EndTime: endTime,
Duration: endTime.Sub(startTime),
}
}
success, err = gomegaMatcher.Match(foundValue)
var matcherResult matchers.MatcherResult
result := SUCCESS
if success {
matcherResult = matchers.MatcherResult{
Actual: foundValue,
Message: "matches expectation",
Expected: expectedValue,
}
} else {
matcherResult = gomegaMatcher.FailureResult(foundValue)
result = FAIL
}
endTime := time.Now()
return TestResult{
Result: result,
ResourceType: typeS,
ResourceId: id,
Title: title,
Meta: meta,
Property: property,
MatcherResult: matcherResult,
Err: toValidateError(err),
StartTime: startTime,
EndTime: endTime,
Duration: endTime.Sub(startTime),
}
}
goss-0.4.9/resource/validate_test.go 0000664 0000000 0000000 00000010107 14675050513 0017512 0 ustar 00root root 0000000 0000000 package resource
import (
"encoding/json"
"fmt"
"io"
"strings"
"testing"
)
type FakeResource struct {
id string
}
func (f *FakeResource) ID() string {
return f.id
}
func (f *FakeResource) GetTitle() string { return "title" }
func (f *FakeResource) GetMeta() meta { return meta{"foo": "bar"} }
var stringTests = []struct {
in, in2 any
want int
}{
{"", "", SUCCESS},
{"foo", "foo", SUCCESS},
{"foo", "bar", FAIL},
{"foo", "", FAIL},
{true, true, SUCCESS},
}
func TestValidateValue(t *testing.T) {
for _, c := range stringTests {
inFunc := func() (any, error) {
return c.in2, nil
}
got := ValidateValue(&FakeResource{""}, "", c.in, inFunc, false)
if got.Result != c.want {
t.Errorf("%+v: got %v, want %v", c, got.Result, c.want)
}
}
}
func TestValidateValueErr(t *testing.T) {
for _, c := range stringTests {
inFunc := func() (any, error) {
return c.in2, fmt.Errorf("some err")
}
got := ValidateValue(&FakeResource{""}, "", c.in, inFunc, false)
if got.Result != FAIL {
t.Errorf("%+v: got %v, want %v", c, got.Result, FAIL)
}
}
}
func TestValidateValueSkip(t *testing.T) {
for _, c := range stringTests {
inFunc := func() (any, error) {
return c.in2, nil
}
got := ValidateValue(&FakeResource{""}, "", c.in, inFunc, true)
if got.Result != SKIP {
t.Errorf("%+v: got %v, want %v", c, got.Result, SKIP)
}
}
}
func BenchmarkValidateValue(b *testing.B) {
inFunc := func() (any, error) {
return "foo", nil
}
for n := 0; n < b.N; n++ {
ValidateValue(&FakeResource{""}, "", "foo", inFunc, false)
}
}
var containsTests = []struct {
in []interface{}
in2 string
want int
}{
{[]interface{}{""}, "", SUCCESS},
{[]interface{}{"foo"}, "foo\nbar", SUCCESS},
{[]interface{}{"!foo"}, "foo\nbar", FAIL},
{[]interface{}{"!moo"}, "foo\nbar", SUCCESS},
{[]interface{}{"/fo.*/"}, "foo\nbar", SUCCESS},
{[]interface{}{"!/fo.*/"}, "foo\nbar", FAIL},
{[]interface{}{"!/mo.*/"}, "foo\nbar", SUCCESS},
{[]interface{}{"foo"}, "", FAIL},
{[]interface{}{`/\s/tmp\b/`}, "test /tmp bar", SUCCESS},
}
func TestValidateContains(t *testing.T) {
for _, c := range containsTests {
inFunc := func() (io.Reader, error) {
reader := strings.NewReader(c.in2)
return reader, nil
}
got := ValidateValue(&FakeResource{""}, "", c.in, inFunc, false)
if got.Result != c.want {
t.Errorf("%+v: got %v, want %v", c, got.Result, c.want)
}
}
}
func TestValidateContainsErr(t *testing.T) {
for _, c := range containsTests {
inFunc := func() (io.Reader, error) {
reader := strings.NewReader(c.in2)
return reader, fmt.Errorf("some err")
}
got := ValidateValue(&FakeResource{""}, "", c.in, inFunc, false)
if got.Result != FAIL {
t.Errorf("%+v: got %v, want %v", c, got.Result, FAIL)
}
}
}
func TestValidateContainsBadRegexErr(t *testing.T) {
inFunc := func() (io.Reader, error) {
reader := strings.NewReader("dummy")
return reader, nil
}
got := ValidateValue(&FakeResource{""}, "", []interface{}{"/*\\.* @@.*/"}, inFunc, false)
if got.Err == nil {
t.Errorf("Expected bad regex to raise error, got nil")
}
}
func TestValidateContainsSkip(t *testing.T) {
for _, c := range containsTests {
inFunc := func() (io.Reader, error) {
reader := strings.NewReader(c.in2)
return reader, nil
}
got := ValidateValue(&FakeResource{""}, "", c.in, inFunc, true)
if got.Result != SKIP {
t.Errorf("%+v: got %v, want %v", c, got.Result, SKIP)
}
}
}
func TestResultMarshaling(t *testing.T) {
inFunc := func() (io.Reader, error) {
return nil, fmt.Errorf("dummy error")
}
res := ValidateValue(&FakeResource{}, "", []string{"x"}, inFunc, false)
if res.Err == nil {
t.Fatalf("Expected to receive an error")
}
if res.Err.Error() != "dummy error" {
t.Fatalf("expected to receive 'dummy error', got: %v", res.Err.Error())
}
rj, _ := json.Marshal(res)
res = TestResult{}
err := json.Unmarshal(rj, &res)
if err != nil {
t.Fatalf("could not unmarshal result: %v", err)
}
if res.Err == nil {
t.Fatalf("Expected to receive an error")
}
if res.Err.Error() != "dummy error" {
t.Fatalf("expected to receive 'dummy error', got: %v", res.Err.Error())
}
}
goss-0.4.9/serve.go 0000664 0000000 0000000 00000011711 14675050513 0014161 0 ustar 00root root 0000000 0000000 package goss
import (
"bytes"
"fmt"
"log"
"net/http"
"strings"
"sync"
"time"
"github.com/fatih/color"
"github.com/goss-org/goss/outputs"
"github.com/goss-org/goss/resource"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
"github.com/patrickmn/go-cache"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func Serve(c *util.Config) error {
err := setLogLevel(c)
if err != nil {
return err
}
endpoint := c.Endpoint
health, err := newHealthHandler(c)
if err != nil {
return err
}
http.Handle(endpoint, health)
http.Handle("/metrics", promhttp.Handler())
log.Printf("[INFO] Starting to listen on: %s", c.ListenAddress)
return http.ListenAndServe(c.ListenAddress, nil)
}
func newHealthHandler(c *util.Config) (*healthHandler, error) {
color.NoColor = true
cache := cache.New(c.Cache, 30*time.Second)
cfg, err := getGossConfig(c.Vars, c.VarsInline, c.Spec)
if err != nil {
return nil, err
}
output, err := getOutputer(c.NoColor, c.OutputFormat)
if err != nil {
return nil, err
}
health := &healthHandler{
c: c,
gossConfig: *cfg,
sys: system.New(c.PackageManager),
outputer: output,
cache: cache,
gossMu: &sync.Mutex{},
maxConcurrent: c.MaxConcurrent,
}
return health, nil
}
type res struct {
body bytes.Buffer
statusCode int
}
type healthHandler struct {
c *util.Config
gossConfig GossConfig
sys *system.System
outputer outputs.Outputer
cache *cache.Cache
gossMu *sync.Mutex
maxConcurrent int
}
func (h healthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
outputFormat, outputer, err := h.negotiateResponseContentType(r)
if err != nil {
log.Printf("[DEBUG] Warn: Using process-level output-format. %s", err)
outputFormat = h.c.OutputFormat
outputer = h.outputer
}
negotiatedContentType := h.responseContentType(outputFormat)
log.Printf("[TRACE] %v: requesting health probe", r.RemoteAddr)
resp := h.processAndEnsureCached(negotiatedContentType, outputer)
w.Header().Set(http.CanonicalHeaderKey("Content-Type"), negotiatedContentType) //nolint:gosimple
w.WriteHeader(resp.statusCode)
logBody := ""
if resp.statusCode != http.StatusOK {
logBody = " - " + resp.body.String()
}
resp.body.WriteTo(w)
log.Printf("[DEBUG] %v: status %d%s", r.RemoteAddr, resp.statusCode, logBody)
}
func (h healthHandler) processAndEnsureCached(negotiatedContentType string, outputer outputs.Outputer) res {
var tra [][]resource.TestResult
cacheKey := "res"
tmp, found := h.cache.Get(cacheKey)
if found {
log.Printf("[TRACE] Returning cached[%s].", cacheKey)
tra = tmp.([][]resource.TestResult)
} else {
log.Printf("Stale cache[%s], running tests", cacheKey)
h.sys = system.New(h.c.PackageManager)
tra = h.validate()
h.cache.SetDefault(cacheKey, tra)
}
trc := testResultArrayToChan(tra)
return h.output(trc, outputer)
}
func (h healthHandler) output(trc <-chan []resource.TestResult, outputer outputs.Outputer) res {
var b bytes.Buffer
outputConfig := util.OutputConfig{
FormatOptions: h.c.FormatOptions,
}
exitCode := outputer.Output(&b, trc, outputConfig)
resp := res{
body: b,
}
if exitCode == 0 {
resp.statusCode = http.StatusOK
} else {
resp.statusCode = http.StatusServiceUnavailable
}
return resp
}
func (h healthHandler) validate() [][]resource.TestResult {
h.sys = system.New(h.c.PackageManager)
res := make([][]resource.TestResult, 0)
tr := validate(h.sys, h.gossConfig, h.c.DisabledResourceTypes, h.maxConcurrent)
for i := range tr {
res = append(res, i)
}
return res
}
func testResultArrayToChan(tra [][]resource.TestResult) <-chan []resource.TestResult {
c := make(chan []resource.TestResult)
go func(c chan []resource.TestResult) {
defer close(c)
for _, i := range tra {
c <- i
}
}(c)
return c
}
const (
// https://en.wikipedia.org/wiki/Media_type
mediaTypePrefix = "application/vnd.goss-"
)
func (h healthHandler) negotiateResponseContentType(r *http.Request) (string, outputs.Outputer, error) {
acceptHeader := r.Header[http.CanonicalHeaderKey("Accept")]
var outputer outputs.Outputer
outputName := ""
for _, acceptCandidate := range acceptHeader {
acceptCandidate = strings.TrimSpace(acceptCandidate)
if strings.HasPrefix(acceptCandidate, mediaTypePrefix) {
outputName = strings.TrimPrefix(acceptCandidate, mediaTypePrefix)
} else if strings.EqualFold("application/json", acceptCandidate) || strings.EqualFold("text/json", acceptCandidate) {
outputName = "json"
} else {
outputName = ""
}
var err error
outputer, err = outputs.GetOutputer(outputName)
if err != nil {
continue
}
}
if outputer == nil {
return "", nil, fmt.Errorf("Accept header on request missing or invalid. Accept header: %v", acceptHeader)
}
return outputName, outputer, nil
}
func (h healthHandler) responseContentType(outputName string) string {
if outputName == "json" {
return "application/json"
}
return fmt.Sprintf("%s%s", mediaTypePrefix, outputName)
}
goss-0.4.9/serve_test.go 0000664 0000000 0000000 00000021257 14675050513 0015226 0 ustar 00root root 0000000 0000000 package goss
import (
"bytes"
"log"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"time"
"github.com/goss-org/goss/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestServeWithNoContentNegotiation(t *testing.T) {
t.Parallel()
tests := map[string]struct {
outputFormat string
specFile string
expectedHTTPStatus int
expectedContentType string
}{
"passing-json": {
outputFormat: "json",
specFile: filepath.Join("testdata", "passing.goss.yaml"),
expectedHTTPStatus: http.StatusOK,
expectedContentType: "application/json",
},
"failing-json": {
outputFormat: "json",
specFile: filepath.Join("testdata", "failing.goss.yaml"),
expectedHTTPStatus: http.StatusServiceUnavailable,
expectedContentType: "application/json",
},
"failing-default-output": {
outputFormat: "rspecish",
specFile: filepath.Join("testdata", "failing.goss.yaml"),
expectedHTTPStatus: http.StatusServiceUnavailable,
expectedContentType: "",
},
}
for testName := range tests {
tc := tests[testName]
t.Run(testName, func(t *testing.T) {
var logOutput bytes.Buffer
log.SetOutput(&logOutput)
config, err := util.NewConfig(
util.WithSpecFile(tc.specFile),
util.WithOutputFormat(tc.outputFormat),
)
require.NoError(t, err)
hh, err := newHealthHandler(config)
require.NoError(t, err)
req := makeRequest(t, config, nil)
rr := httptest.NewRecorder()
handler := http.HandlerFunc(hh.ServeHTTP)
handler.ServeHTTP(rr, req)
t.Logf("testName %q log output:\n%s", testName, logOutput.String())
assert.Equal(t, tc.expectedHTTPStatus, rr.Code)
if tc.expectedContentType != "" {
assert.Equal(t, tc.expectedContentType, rr.Result().Header.Get("Content-Type"))
}
})
}
}
func TestServeNegotiatingContent(t *testing.T) {
t.Parallel()
tests := map[string]struct {
acceptHeader []string
outputFormat string
specFile string
expectedHTTPStatus int
expectedContentType string
}{
"accept {blank} returns process-level format-option": {
acceptHeader: []string{
"",
},
outputFormat: "structured",
specFile: filepath.Join("testdata", "passing.goss.yaml"),
expectedHTTPStatus: http.StatusOK,
expectedContentType: "application/vnd.goss-structured",
},
"accept application/json": {
acceptHeader: []string{
"application/json",
},
outputFormat: "structured",
specFile: filepath.Join("testdata", "passing.goss.yaml"),
expectedHTTPStatus: http.StatusOK,
expectedContentType: "application/json",
},
"accept text/json translates to application/json": {
acceptHeader: []string{
"text/json",
},
outputFormat: "structured",
specFile: filepath.Join("testdata", "passing.goss.yaml"),
expectedHTTPStatus: http.StatusOK,
expectedContentType: "application/json",
},
"when accept is application/vnd.goss-json, return more widely known application/json": {
acceptHeader: []string{
"application/vnd.goss-json",
},
outputFormat: "structured",
specFile: filepath.Join("testdata", "passing.goss.yaml"),
expectedHTTPStatus: http.StatusOK,
expectedContentType: "application/json",
},
"accept header contains vendor-specific output format different from process-level": {
acceptHeader: []string{
"application/vnd.goss-rspecish",
},
outputFormat: "structured",
specFile: filepath.Join("testdata", "passing.goss.yaml"),
expectedHTTPStatus: http.StatusOK,
expectedContentType: "application/vnd.goss-rspecish",
},
"accept header contains nonsense": {
acceptHeader: []string{
"application/vnd.goss-nonexistent",
},
outputFormat: "structured",
specFile: filepath.Join("testdata", "passing.goss.yaml"),
expectedHTTPStatus: http.StatusOK,
expectedContentType: "application/vnd.goss-structured",
},
"accept header contains nonsense then valid": {
acceptHeader: []string{
"application/vnd.goss-nonexistent",
"application/json",
},
outputFormat: "structured",
specFile: filepath.Join("testdata", "passing.goss.yaml"),
expectedHTTPStatus: http.StatusOK,
expectedContentType: "application/json",
},
}
for testName := range tests {
tc := tests[testName]
t.Run(testName, func(t *testing.T) {
var logOutput bytes.Buffer
log.SetOutput(&logOutput)
config, err := util.NewConfig(
util.WithSpecFile(tc.specFile),
util.WithOutputFormat(tc.outputFormat),
)
require.NoError(t, err)
hh, err := newHealthHandler(config)
require.NoError(t, err)
req := makeRequest(t, config, map[string][]string{
"accept": tc.acceptHeader,
})
rr := httptest.NewRecorder()
handler := http.HandlerFunc(hh.ServeHTTP)
handler.ServeHTTP(rr, req)
t.Logf("testName %q log output:\n%s", testName, logOutput.String())
assert.Equal(t, tc.expectedHTTPStatus, rr.Code)
if tc.expectedContentType != "" {
assert.Equal(t, tc.expectedContentType, rr.Result().Header.Get("Content-Type"))
}
})
}
}
func TestServeCacheWithNoContentNegotiation(t *testing.T) {
var logOutput bytes.Buffer
log.SetOutput(&logOutput)
const cache = time.Duration(time.Millisecond * 100)
config, err := util.NewConfig(
util.WithSpecFile(filepath.Join("testdata", "passing.goss.yaml")),
util.WithCache(cache),
)
require.NoError(t, err)
hh, err := newHealthHandler(config)
require.NoError(t, err)
req := makeRequest(t, config, nil)
rr := httptest.NewRecorder()
handler := http.HandlerFunc(hh.ServeHTTP)
t.Run("fresh cache", func(t *testing.T) {
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Result().StatusCode)
assert.Contains(t, logOutput.String(), "Stale cache")
t.Log(logOutput.String())
logOutput.Reset()
})
t.Run("immediately re-request, cache should be warm", func(t *testing.T) {
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Result().StatusCode)
assert.NotContains(t, logOutput.String(), "Stale cache")
t.Log(logOutput.String())
logOutput.Reset()
})
t.Run("allow cache to expire, cache should be cold", func(t *testing.T) {
time.Sleep(cache + 5*time.Millisecond)
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Result().StatusCode)
assert.Contains(t, logOutput.String(), "Stale cache")
t.Log(logOutput.String())
logOutput.Reset()
})
}
func TestServeCacheNegotiatingContent(t *testing.T) {
var logOutput bytes.Buffer
log.SetOutput(&logOutput)
const cache = time.Duration(time.Millisecond * 100)
config, err := util.NewConfig(
util.WithSpecFile(filepath.Join("testdata", "passing.goss.yaml")),
util.WithCache(cache),
util.WithOutputFormat("structured"),
)
require.NoError(t, err)
hh, err := newHealthHandler(config)
require.NoError(t, err)
rr := httptest.NewRecorder()
handler := http.HandlerFunc(hh.ServeHTTP)
t.Run("fresh cache", func(t *testing.T) {
req := makeRequest(t, config, map[string][]string{
"accept": {"application/json"},
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Result().StatusCode)
assert.Contains(t, logOutput.String(), "Stale cache")
t.Log(logOutput.String())
logOutput.Reset()
})
t.Run("immediately re-request, cache should be warm", func(t *testing.T) {
req := makeRequest(t, config, map[string][]string{
"accept": {"application/json"},
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Result().StatusCode)
assert.NotContains(t, logOutput.String(), "Stale cache")
t.Log(logOutput.String())
logOutput.Reset()
})
t.Run("immediately re-request but different accept header, cache should be warm", func(t *testing.T) {
req := makeRequest(t, config, map[string][]string{
"accept": {"application/vnd.goss-rspecish"},
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Result().StatusCode)
assert.NotContains(t, logOutput.String(), "Stale cache")
t.Log(logOutput.String())
logOutput.Reset()
})
t.Run("allow cache to expire, cache should be cold", func(t *testing.T) {
time.Sleep(cache + 5*time.Millisecond)
req := makeRequest(t, config, map[string][]string{
"accept": {"application/json"},
})
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Result().StatusCode)
assert.Contains(t, logOutput.String(), "Stale cache")
t.Log(logOutput.String())
logOutput.Reset()
})
}
func makeRequest(t *testing.T, config *util.Config, headers map[string][]string) *http.Request {
req, err := http.NewRequest("GET", config.Endpoint, nil)
require.NoError(t, err)
for header, vals := range headers {
for _, v := range vals {
req.Header.Add(header, v)
}
}
return req
}
goss-0.4.9/store.go 0000664 0000000 0000000 00000015662 14675050513 0014202 0 ustar 00root root 0000000 0000000 package goss
import (
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"gopkg.in/yaml.v3"
"github.com/goss-org/goss/resource"
"github.com/goss-org/goss/util"
)
const (
UNSET = iota
JSON
YAML
)
var outStoreFormat = UNSET
var currentTemplateFilter TemplateFilter
var debug = false
func getStoreFormatFromFileName(f string) (int, error) {
ext := filepath.Ext(f)
switch ext {
case ".json":
return JSON, nil
case ".yaml", ".yml":
return YAML, nil
default:
return 0, fmt.Errorf("unknown file extension: %v", ext)
}
}
func getStoreFormatFromData(data []byte) (int, error) {
var v any
if err := unmarshalJSON(data, &v); err == nil {
return JSON, nil
}
if err := unmarshalYAML(data, &v); err == nil {
return YAML, nil
}
return 0, fmt.Errorf("unable to determine format from content")
}
// ReadJSON Reads json file returning GossConfig
func ReadJSON(filePath string) (GossConfig, error) {
file, err := os.ReadFile(filePath)
if err != nil {
return GossConfig{}, fmt.Errorf("file error: %v", err)
}
return ReadJSONData(file, false)
}
type TmplVars struct {
Vars map[string]any
}
func (t *TmplVars) Env() map[string]string {
env := make(map[string]string)
for _, i := range os.Environ() {
sep := strings.Index(i, "=")
env[i[0:sep]] = i[sep+1:]
}
return env
}
func loadVars(varsFile string, varsInline string) (map[string]any, error) {
vars, err := varsFromFile(varsFile)
if err != nil {
return nil, fmt.Errorf("loading vars file '%s'\n%w", varsFile, err)
}
varsExtra, err := varsFromString(varsInline)
if err != nil {
return nil, fmt.Errorf("loading inline vars\n%w", err)
}
for k, v := range varsExtra {
vars[k] = v
}
return vars, nil
}
func varsFromFile(varsFile string) (map[string]any, error) {
vars := make(map[string]any)
if varsFile == "" {
return vars, nil
}
data, err := os.ReadFile(varsFile)
if err != nil {
return vars, err
}
format, err := getStoreFormatFromData(data)
if err != nil {
return nil, err
}
if err := unmarshal(data, &vars, format); err != nil {
return vars, err
}
return vars, nil
}
func varsFromString(varsString string) (map[string]any, error) {
vars := make(map[string]any)
if varsString == "" {
return vars, nil
}
data := []byte(varsString)
format, err := getStoreFormatFromData(data)
if err != nil {
return nil, err
}
if err := unmarshal(data, &vars, format); err != nil {
return vars, err
}
return vars, nil
}
// ReadJSONData Reads json byte array returning GossConfig
func ReadJSONData(data []byte, detectFormat bool) (GossConfig, error) {
var err error
if currentTemplateFilter != nil {
data, err = currentTemplateFilter(data)
if err != nil {
return GossConfig{}, err
}
if debug {
fmt.Println("DEBUG: file after text/template render")
fmt.Println(string(data))
}
}
format := outStoreFormat
if detectFormat {
format, err = getStoreFormatFromData(data)
if err != nil {
return GossConfig{}, err
}
}
gossConfig := NewGossConfig()
// Horrible, but will do for now
if err := unmarshal(data, gossConfig, format); err != nil {
return *gossConfig, err
}
return *gossConfig, nil
}
// RenderJSON reads json file recursively returning string
func RenderJSON(c *util.Config) (string, error) {
var err error
debug = c.Debug
currentTemplateFilter, err = NewTemplateFilter(c.Vars, c.VarsInline)
if err != nil {
return "", err
}
outStoreFormat, err = getStoreFormatFromFileName(c.Spec)
if err != nil {
return "", err
}
j, err := ReadJSON(c.Spec)
if err != nil {
return "", err
}
gossConfig, err := mergeJSONData(j, 0, filepath.Dir(c.Spec))
if err != nil {
return "", err
}
b, err := marshal(gossConfig)
if err != nil {
return "", fmt.Errorf("rendering failed: %v", err)
}
return string(b), nil
}
func mergeJSONData(gossConfig GossConfig, depth int, path string) (GossConfig, error) {
depth++
if depth >= 50 {
return GossConfig{}, fmt.Errorf("max depth of 50 reached, possibly due to dependency loop in goss file")
}
// Our return gossConfig
ret := *NewGossConfig()
ret = mergeGoss(ret, gossConfig)
// Sort the gossfiles to ensure consistent ordering
var keys []string
for k := range gossConfig.Gossfiles {
keys = append(keys, k)
}
sort.Strings(keys)
// Merge gossfiles in sorted order
for _, k := range keys {
g := gossConfig.Gossfiles[k]
var fpath string
if strings.HasPrefix(g.GetGossfile(), "/") {
fpath = g.GetGossfile()
} else {
fpath = filepath.Join(path, g.GetGossfile())
}
if g.GetSkip() {
// Do not process gossfiles with the skip attribute
continue
}
matches, err := filepath.Glob(fpath)
if err != nil {
return ret, fmt.Errorf("error in expanding glob pattern: %q", err)
}
if matches == nil {
return ret, fmt.Errorf("no matched files were found: %q", fpath)
}
for _, match := range matches {
fdir := filepath.Dir(match)
j, err := ReadJSON(match)
if err != nil {
return GossConfig{}, fmt.Errorf("could not read json data in %s: %s", match, err)
}
j, err = mergeJSONData(j, depth, fdir)
if err != nil {
return ret, fmt.Errorf("could not write json data: %s", err)
}
ret = mergeGoss(ret, j)
}
}
return ret, nil
}
func WriteJSON(filePath string, gossConfig GossConfig) error {
jsonData, err := marshal(gossConfig)
if err != nil {
return fmt.Errorf("failed to write %s: %s", filePath, err)
}
// check if the auto added json data is empty before writing to file.
emptyConfig := *NewGossConfig()
emptyData, err := marshal(emptyConfig)
if err != nil {
return fmt.Errorf("failed to write %s: %s", filePath, err)
}
if string(emptyData) == string(jsonData) {
log.Printf("Can't write empty configuration file. Please check resource name(s).")
return nil
}
if err := os.WriteFile(filePath, jsonData, 0644); err != nil {
return fmt.Errorf("failed to write %s: %s", filePath, err)
}
return nil
}
func resourcePrint(fileName string, res resource.ResourceRead, announce bool) {
resMap := map[string]resource.ResourceRead{res.ID(): res}
oj, _ := marshal(resMap)
typ := reflect.TypeOf(res)
typs := strings.Split(typ.String(), ".")[1]
if announce {
fmt.Printf("Adding %s to '%s':\n\n%s\n\n", typs, fileName, string(oj))
}
}
func marshal(gossConfig any) ([]byte, error) {
switch outStoreFormat {
case JSON:
return marshalJSON(gossConfig)
case YAML:
return marshalYAML(gossConfig)
default:
return nil, fmt.Errorf("StoreFormat unset")
}
}
func unmarshal(data []byte, v any, storeFormat int) error {
switch storeFormat {
case JSON:
return unmarshalJSON(data, v)
case YAML:
return unmarshalYAML(data, v)
default:
return fmt.Errorf("StoreFormat unset")
}
}
func marshalJSON(gossConfig any) ([]byte, error) {
return json.MarshalIndent(gossConfig, "", " ")
}
func unmarshalJSON(data []byte, v any) error {
return json.Unmarshal(data, v)
}
func marshalYAML(gossConfig any) ([]byte, error) {
return yaml.Marshal(gossConfig)
}
func unmarshalYAML(data []byte, v any) error {
return yaml.Unmarshal(data, v)
}
goss-0.4.9/store_test.go 0000664 0000000 0000000 00000007567 14675050513 0015246 0 ustar 00root root 0000000 0000000 package goss
import (
"log"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_varsFromString(t *testing.T) {
tests := []struct {
name string
arg string
want map[string]any
wantErr bool
}{
{
name: "empty_string",
arg: ``,
want: map[string]any{},
wantErr: false,
},
{
name: "empty_JSON",
arg: `{}`,
want: map[string]any{},
wantErr: false,
},
{
name: "JSON_simple",
arg: `{"a": "a", "b": 1}`,
want: map[string]any{
"a": "a",
"b": float64(1),
},
wantErr: false,
},
{
name: "YAML_simple",
arg: `{a: a, b: 1}`,
want: map[string]any{
"a": "a",
"b": 1,
},
wantErr: false,
},
{
name: "JSON_float",
arg: `{"f": 1.23}`,
want: map[string]any{
"f": 1.23,
},
wantErr: false,
},
{
name: "YAML_float",
arg: `{f: 1.23}`,
want: map[string]any{
"f": 1.23,
},
wantErr: false,
},
{
name: "JSON_list",
arg: `{"l": ["l1", "l2", 3]}`,
want: map[string]any{
"l": []any{
"l1",
"l2",
float64(3),
},
},
wantErr: false,
},
{
name: "YAML_list",
arg: `{l: [l1, l2, 3]}`,
want: map[string]any{
"l": []any{
"l1",
"l2",
3,
},
},
wantErr: false,
},
{
name: "JSON_object",
arg: `{"o": {"oa": "a", "oo": { "oo1": 1 } } }`,
want: map[string]any{
"o": map[string]any{
"oa": "a",
"oo": map[string]any{
"oo1": float64(1),
},
},
},
wantErr: false,
},
{
name: "YAML_object",
arg: `{o: {oa: a, oo: { oo1: 1 } } }`,
want: map[string]any{
"o": map[string]any{
"oa": "a",
"oo": map[string]any{
"oo1": 1,
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := varsFromString(tt.arg)
assert.Equal(t, tt.want, got, "map contents")
assert.Equal(t, tt.wantErr, err != nil, "has error")
})
}
}
func Test_loadVars(t *testing.T) {
fileEmpty, fileEmptyClose := fileMaker(``)
defer fileEmptyClose()
fileNil, fileNilClose := fileMaker(``)
defer fileNilClose()
fileSimple, fileSimpleClose := fileMaker(`{a: a}`)
defer fileSimpleClose()
type args struct {
varsFile string
varsInline string
}
tests := []struct {
name string
args args
want map[string]any
wantErr bool
}{
{
name: "both_empty",
args: args{
varsFile: fileEmpty,
varsInline: `{}`,
},
want: map[string]any{},
wantErr: false,
},
{
name: "both_nil",
args: args{
varsFile: fileNil,
varsInline: `{}`,
},
want: map[string]any{},
wantErr: false,
},
{
name: "file_empty",
args: args{
varsFile: fileEmpty,
varsInline: `{b: b}`,
},
want: map[string]any{
"b": "b",
},
wantErr: false,
},
{
name: "inline_empty",
args: args{
varsFile: fileSimple,
varsInline: `{}`,
},
want: map[string]any{
"a": "a",
},
wantErr: false,
},
{
name: "no_overwrite",
args: args{
varsFile: fileSimple,
varsInline: `{b: b}`,
},
want: map[string]any{
"a": "a",
"b": "b",
},
wantErr: false,
},
{
name: "overwrite",
args: args{
varsFile: fileSimple,
varsInline: `{a: c, b: b}`,
},
want: map[string]any{
"a": "c",
"b": "b",
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := loadVars(tt.args.varsFile, tt.args.varsInline)
assert.Equal(t, tt.want, got, "map contents")
assert.Equal(t, tt.wantErr, err != nil, "has error")
})
}
}
func fileMaker(content string) (string, func()) {
bytes := []byte(content)
f, err := os.CreateTemp("", "*")
if err != nil {
log.Fatal(err)
}
_, err = f.Write(bytes)
if err != nil {
log.Fatal(err)
}
return f.Name(), func() {
if err := f.Close(); err != nil {
log.Fatal(err)
}
}
}
goss-0.4.9/system/ 0000775 0000000 0000000 00000000000 14675050513 0014031 5 ustar 00root root 0000000 0000000 goss-0.4.9/system/addr.go 0000664 0000000 0000000 00000002744 14675050513 0015301 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"net"
"strings"
"time"
"github.com/goss-org/goss/util"
)
type Addr interface {
Address() string
Exists() (bool, error)
Reachable() (bool, error)
}
type DefAddr struct {
address string
LocalAddress string
Timeout int
}
func NewDefAddr(_ context.Context, address string, system *System, config util.Config) Addr {
addr := normalizeAddress(address)
return &DefAddr{
address: addr,
LocalAddress: config.LocalAddress,
Timeout: config.TimeOutMilliSeconds(),
}
}
func (a *DefAddr) ID() string {
return a.address
}
func (a *DefAddr) Address() string {
return a.address
}
func (a *DefAddr) Exists() (bool, error) { return a.Reachable() }
func (a *DefAddr) Reachable() (bool, error) {
network, address := splitAddress(a.address)
var localAddr net.Addr
if network == "udp" {
localAddr = &net.UDPAddr{IP: net.ParseIP(a.LocalAddress)}
} else {
localAddr = &net.TCPAddr{IP: net.ParseIP(a.LocalAddress)}
}
d := net.Dialer{LocalAddr: localAddr, Timeout: time.Duration(a.Timeout) * time.Millisecond}
conn, err := d.Dial(network, address)
if err != nil {
return false, nil
}
conn.Close()
return true, nil
}
func splitAddress(fulladdress string) (network, address string) {
split := strings.SplitN(fulladdress, "://", 2)
if len(split) == 2 {
return split[0], split[1]
}
return "tcp", fulladdress
}
func normalizeAddress(fulladdress string) string {
net, addr := splitAddress(fulladdress)
return net + "://" + addr
}
goss-0.4.9/system/command.go 0000664 0000000 0000000 00000004104 14675050513 0015775 0 ustar 00root root 0000000 0000000 package system
import (
"bytes"
"context"
"fmt"
"io"
"os/exec"
"time"
"github.com/goss-org/goss/util"
)
type Command interface {
Command() string
Exists() (bool, error)
ExitStatus() (int, error)
Stdout() (io.Reader, error)
Stderr() (io.Reader, error)
}
type DefCommand struct {
Ctx context.Context
command string
exitStatus int
stdout io.Reader
stderr io.Reader
loaded bool
Timeout int
err error
}
func NewDefCommand(ctx context.Context, command string, system *System, config util.Config) Command {
return &DefCommand{
Ctx: ctx,
command: command,
Timeout: config.TimeOutMilliSeconds(),
}
}
func (c *DefCommand) setup() error {
if c.loaded {
return c.err
}
c.loaded = true
cmd := commandWrapper(c.command)
err := runCommand(cmd, c.Timeout)
// We don't care about ExitError since it's covered by status
if _, ok := err.(*exec.ExitError); !ok {
c.err = err
}
c.exitStatus = cmd.Status
stdoutB := cmd.Stdout.Bytes()
stderrB := cmd.Stderr.Bytes()
id := c.Ctx.Value("id")
logBytes(stdoutB, fmt.Sprintf("[Command][%s][stdout] ", id))
logBytes(stderrB, fmt.Sprintf("[Command][%s][stderr] ", id))
c.stdout = bytes.NewReader(stdoutB)
c.stderr = bytes.NewReader(stderrB)
return c.err
}
func (c *DefCommand) Command() string {
return c.command
}
func (c *DefCommand) ExitStatus() (int, error) {
err := c.setup()
return c.exitStatus, err
}
func (c *DefCommand) Stdout() (io.Reader, error) {
err := c.setup()
return c.stdout, err
}
func (c *DefCommand) Stderr() (io.Reader, error) {
err := c.setup()
return c.stderr, err
}
// Stub out
func (c *DefCommand) Exists() (bool, error) {
return false, nil
}
func runCommand(cmd *util.Command, timeout int) error {
c1 := make(chan bool, 1)
e1 := make(chan error, 1)
timeoutD := time.Duration(timeout) * time.Millisecond
go func() {
err := cmd.Run()
if err != nil {
e1 <- err
}
c1 <- true
}()
select {
case <-c1:
return nil
case err := <-e1:
return err
case <-time.After(timeoutD):
return fmt.Errorf("Command execution timed out (%s)", timeoutD)
}
}
goss-0.4.9/system/command_posix.go 0000664 0000000 0000000 00000000401 14675050513 0017213 0 ustar 00root root 0000000 0000000 //go:build linux || darwin || !windows
// +build linux darwin !windows
package system
import "github.com/goss-org/goss/util"
const linuxShell string = "sh"
func commandWrapper(cmd string) *util.Command {
return util.NewCommand(linuxShell, "-c", cmd)
}
goss-0.4.9/system/command_posix_test.go 0000664 0000000 0000000 00000000575 14675050513 0020266 0 ustar 00root root 0000000 0000000 //go:build linux || darwin || !windows
// +build linux darwin !windows
package system
import (
"os/exec"
"testing"
)
func TestCommandWrapper(t *testing.T) {
t.Parallel()
c := commandWrapper("echo hello world")
cmdPath, _ := exec.LookPath(linuxShell)
if c.Cmd.Path != cmdPath {
t.Errorf("Command not wrapped properly for OS. got %s, want: %s", c.Cmd.Path, cmdPath)
}
}
goss-0.4.9/system/command_windows.go 0000664 0000000 0000000 00000000361 14675050513 0017550 0 ustar 00root root 0000000 0000000 //go:build windows
// +build windows
package system
import "github.com/goss-org/goss/util"
const windowsShell string = "cmd"
func commandWrapper(cmd string) *util.Command {
return util.NewCommandForWindowsCmd(windowsShell, "/c", cmd)
}
goss-0.4.9/system/command_windows_test.go 0000664 0000000 0000000 00000001225 14675050513 0020607 0 ustar 00root root 0000000 0000000 //go:build windows
// +build windows
package system
import (
"os/exec"
"testing"
)
func TestCommandWrapper(t *testing.T) {
t.Parallel()
c := commandWrapper("echo hello world")
cmdPath, _ := exec.LookPath(windowsShell)
if c.Cmd.Path != cmdPath {
t.Errorf("Command not wrapped properly for Windows os. got %s, want: %s", c.Cmd.Path, cmdPath)
}
if c.Cmd.SysProcAttr.CmdLine != "/c echo hello world" {
t.Errorf("Command not wrapped properly for Windows cmd.exe. got %s, want: %s", c.Cmd.SysProcAttr.CmdLine, "/c echo hello world")
}
if len(c.Cmd.Args) != 1 {
t.Errorf("Args length should be blank. got: %d, want: %d", len(c.Cmd.Args), 1)
}
}
goss-0.4.9/system/dns.go 0000664 0000000 0000000 00000016522 14675050513 0015152 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"fmt"
"net"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/goss-org/goss/util"
"github.com/miekg/dns"
)
type DNS interface {
Host() string
Addrs() ([]string, error)
Resolvable() (bool, error)
Exists() (bool, error)
Server() string
Qtype() string
}
type DefDNS struct {
host string
resolvable bool
addrs []string
Timeout int
loaded bool
err error
server string
qtype string
}
func NewDefDNS(_ context.Context, host string, system *System, config util.Config) DNS {
var h string
var t string
splitHost := strings.SplitN(host, ":", 2)
if len(splitHost) == 2 && regexp.MustCompile(`^[A-Z]+$`).MatchString(splitHost[0]) {
h = splitHost[1]
t = splitHost[0]
} else {
h = host
}
return &DefDNS{
host: h,
Timeout: config.TimeOutMilliSeconds(),
server: config.Server,
qtype: t,
}
}
func (d *DefDNS) Host() string {
return d.host
}
func (d *DefDNS) Server() string {
return d.server
}
func (d *DefDNS) Qtype() string {
return d.qtype
}
func (d *DefDNS) setup() error {
if d.loaded {
return d.err
}
d.loaded = true
for i := 0; i < 3; i++ {
addrs, err := DNSlookup(d.host, d.server, d.qtype, d.Timeout)
if err != nil || len(addrs) == 0 {
d.resolvable = false
d.addrs = []string{}
// DNSError is resolvable == false, ignore error
if _, ok := err.(*net.DNSError); ok {
return nil
}
d.err = err
continue
}
sort.Strings(addrs)
d.resolvable = true
d.addrs = addrs
d.err = nil
return nil
}
return d.err
}
func (d *DefDNS) Addrs() ([]string, error) {
err := d.setup()
return d.addrs, err
}
func (d *DefDNS) Resolvable() (bool, error) {
err := d.setup()
return d.resolvable, err
}
// Stub out
func (d *DefDNS) Exists() (bool, error) {
return false, nil
}
func DNSlookup(host string, server string, qtype string, timeout int) ([]string, error) {
c1 := make(chan []string, 1)
e1 := make(chan error, 1)
timeoutD := time.Duration(timeout) * time.Millisecond
var addrs []string
var err error
go func() {
if server != "" {
c := new(dns.Client)
c.Timeout = timeoutD
m := new(dns.Msg)
switch qtype {
case "A":
addrs, err = LookupA(host, server, c, m)
case "AAAA":
addrs, err = LookupAAAA(host, server, c, m)
case "PTR":
addrs, err = LookupPTR(host, server, c, m)
case "CNAME":
addrs, err = LookupCNAME(host, server, c, m)
case "MX":
addrs, err = LookupMX(host, server, c, m)
case "NS":
addrs, err = LookupNS(host, server, c, m)
case "SRV":
addrs, err = LookupSRV(host, server, c, m)
case "TXT":
addrs, err = LookupTXT(host, server, c, m)
case "CAA":
addrs, err = LookupCAA(host, server, c, m)
default:
addrs, err = LookupHost(host, server, c, m)
}
} else {
addrs, err = net.LookupHost(host)
}
if err != nil {
e1 <- err
}
c1 <- addrs
}()
select {
case res := <-c1:
return res, nil
case err := <-e1:
return nil, err
case <-time.After(timeoutD):
return nil, fmt.Errorf("DNS lookup timed out (%s)", timeoutD)
}
}
// A and AAAA record lookup - similar to net.LookupHost
func LookupHost(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) {
a, _ := LookupA(host, server, c, m)
aaaa, _ := LookupAAAA(host, server, c, m)
addrs = append(a, aaaa...)
return
}
// A record lookup
func LookupA(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) {
m.SetQuestion(dns.Fqdn(host), dns.TypeA)
r, _, err := c.Exchange(m, parseServerString(server))
if err != nil {
return nil, err
}
for _, ans := range r.Answer {
if t, ok := ans.(*dns.A); ok {
addrs = append(addrs, t.A.String())
}
}
return
}
// parseServerString - Check if the DNS Server in server config has a port, if not ensure 53 is prefixed.
func parseServerString(server string) string {
srvhost, srvport, err := net.SplitHostPort(server)
if err != nil {
srvport = "53"
srvhost = server
}
return net.JoinHostPort(srvhost, srvport)
}
// AAAA (IPv6) record lookup
func LookupAAAA(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) {
m.SetQuestion(dns.Fqdn(host), dns.TypeAAAA)
r, _, err := c.Exchange(m, parseServerString(server))
if err != nil {
return nil, err
}
for _, ans := range r.Answer {
if t, ok := ans.(*dns.AAAA); ok {
addrs = append(addrs, t.AAAA.String())
}
}
return
}
// CNAME record lookup
func LookupCNAME(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) {
m.SetQuestion(dns.Fqdn(host), dns.TypeCNAME)
r, _, err := c.Exchange(m, parseServerString(server))
if err != nil {
return nil, err
}
for _, ans := range r.Answer {
if t, ok := ans.(*dns.CNAME); ok {
addrs = append(addrs, t.Target)
}
}
return
}
// MX record lookup
func LookupMX(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) {
m.SetQuestion(dns.Fqdn(host), dns.TypeMX)
r, _, err := c.Exchange(m, parseServerString(server))
if err != nil {
return nil, err
}
for _, ans := range r.Answer {
if t, ok := ans.(*dns.MX); ok {
mxstring := strconv.Itoa(int(t.Preference)) + " " + t.Mx
addrs = append(addrs, mxstring)
}
}
return
}
// NS record lookup
func LookupNS(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) {
m.SetQuestion(dns.Fqdn(host), dns.TypeNS)
r, _, err := c.Exchange(m, parseServerString(server))
if err != nil {
return nil, err
}
for _, ans := range r.Answer {
if t, ok := ans.(*dns.NS); ok {
addrs = append(addrs, t.Ns)
}
}
return
}
// SRV record lookup
func LookupSRV(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) {
m.SetQuestion(dns.Fqdn(host), dns.TypeSRV)
r, _, err := c.Exchange(m, parseServerString(server))
if err != nil {
return nil, err
}
for _, ans := range r.Answer {
if t, ok := ans.(*dns.SRV); ok {
prio := strconv.Itoa(int(t.Priority))
weight := strconv.Itoa(int(t.Weight))
port := strconv.Itoa(int(t.Port))
srvrec := strings.Join([]string{prio, weight, port, t.Target}, " ")
addrs = append(addrs, srvrec)
}
}
return
}
// TXT record lookup
func LookupTXT(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) {
m.SetQuestion(dns.Fqdn(host), dns.TypeTXT)
r, _, err := c.Exchange(m, parseServerString(server))
if err != nil {
return nil, err
}
for _, ans := range r.Answer {
if t, ok := ans.(*dns.TXT); ok {
addrs = append(addrs, t.Txt...)
}
}
return
}
// PTR record lookup
func LookupPTR(addr string, server string, c *dns.Client, m *dns.Msg) (name []string, err error) {
reverse, err := dns.ReverseAddr(addr)
if err != nil {
return nil, err
}
m.SetQuestion(reverse, dns.TypePTR)
r, _, err := c.Exchange(m, parseServerString(server))
if err != nil {
return nil, err
}
for _, ans := range r.Answer {
name = append(name, ans.(*dns.PTR).Ptr)
}
return
}
// CAA record lookup
func LookupCAA(host string, server string, c *dns.Client, m *dns.Msg) (addrs []string, err error) {
m.SetQuestion(dns.Fqdn(host), dns.TypeCAA)
r, _, err := c.Exchange(m, parseServerString(server))
if err != nil {
return nil, err
}
for _, ans := range r.Answer {
if t, ok := ans.(*dns.CAA); ok {
flag := strconv.Itoa(int(t.Flag))
caarec := strings.Join([]string{flag, t.Tag, t.Value}, " ")
addrs = append(addrs, caarec)
}
}
return
}
goss-0.4.9/system/dns_test.go 0000664 0000000 0000000 00000000737 14675050513 0016212 0 ustar 00root root 0000000 0000000 package system
import (
"testing"
)
func TestParseServerString(t *testing.T) {
tables := []struct {
x string
n string
}{
{"127.0.0.1", "127.0.0.1:53"},
{"127.0.0.1:53", "127.0.0.1:53"},
{"127.0.0.1:8600", "127.0.0.1:8600"},
{"1.1.1.1:53", "1.1.1.1:53"},
}
for _, table := range tables {
output := parseServerString(table.x)
if output != table.n {
t.Errorf("parseServerString (%s) was incorrect, got: %s, want: %s.", table.x, output, table.n)
}
}
}
goss-0.4.9/system/file.go 0000664 0000000 0000000 00000011344 14675050513 0015302 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"crypto/md5"
"crypto/sha256"
"crypto/sha512"
"fmt"
"hash"
"io"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
"github.com/goss-org/goss/util"
)
type File interface {
Path() string
Exists() (bool, error)
Contents() (io.Reader, error)
Mode() (string, error)
Size() (int, error)
Filetype() (string, error)
Owner() (string, error)
Uid() (int, error)
Group() (string, error)
Gid() (int, error)
LinkedTo() (string, error)
Md5() (string, error)
Sha256() (string, error)
Sha512() (string, error)
}
type hashFuncType string
const (
md5Hash hashFuncType = "md5"
sha256Hash hashFuncType = "sha256"
sha512Hash hashFuncType = "sha512"
)
type DefFile struct {
path string
realPath string
loaded bool
err error
}
func NewDefFile(_ context.Context, path string, system *System, config util.Config) File {
var err error
if !strings.HasPrefix(path, "~") {
path, err = filepath.Abs(path)
}
return &DefFile{path: path, err: err}
}
func (f *DefFile) setup() error {
if f.loaded || f.err != nil {
return f.err
}
f.loaded = true
if f.realPath, f.err = realPath(f.path); f.err != nil {
return f.err
}
return f.err
}
func (f *DefFile) Path() string {
return f.path
}
func (f *DefFile) Exists() (bool, error) {
if err := f.setup(); err != nil {
return false, err
}
_, err := os.Lstat(f.realPath)
if os.IsNotExist(err) {
return false, nil
}
return true, err
}
func (f *DefFile) Contents() (io.Reader, error) {
if err := f.setup(); err != nil {
return nil, err
}
fh, err := os.Open(f.realPath)
if err != nil {
return nil, err
}
return fh, nil
}
func (f *DefFile) Size() (int, error) {
if err := f.setup(); err != nil {
return 0, err
}
fi, err := os.Lstat(f.realPath)
if err != nil {
return 0, err
}
size := fi.Size()
return int(size), nil
}
func (f *DefFile) Filetype() (string, error) {
if err := f.setup(); err != nil {
return "", err
}
fi, err := os.Lstat(f.realPath)
if err != nil {
return "", err
}
switch {
case fi.Mode()&os.ModeSymlink == os.ModeSymlink:
return "symlink", nil
case fi.Mode()&os.ModeDevice == os.ModeDevice:
if fi.Mode()&os.ModeCharDevice == os.ModeCharDevice {
return "character-device", nil
}
return "block-device", nil
case fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe:
return "pipe", nil
case fi.Mode()&os.ModeSocket == os.ModeSocket:
return "socket", nil
case fi.IsDir():
return "directory", nil
case fi.Mode().IsRegular():
return "file", nil
}
// FIXME: file as a catchall?
return "file", nil
}
func (f *DefFile) LinkedTo() (string, error) {
if err := f.setup(); err != nil {
return "", err
}
dst, err := os.Readlink(f.realPath)
if err != nil {
return "", err
}
return dst, nil
}
func realPath(path string) (string, error) {
if !strings.HasPrefix(path, "~") {
return path, nil
}
pathS := strings.Split(path, "/")
f := pathS[0]
var usr *user.User
var err error
if f == "~" {
usr, err = user.Current()
} else {
usr, err = user.Lookup(f[1:])
}
if err != nil {
return "", err
}
pathS[0] = usr.HomeDir
realPath := strings.Join(pathS, "/")
realPath, err = filepath.Abs(realPath)
return realPath, err
}
func (f *DefFile) hash(hashFunc hashFuncType) (string, error) {
if err := f.setup(); err != nil {
return "", err
}
fh, err := os.Open(f.realPath)
if err != nil {
return "", err
}
defer fh.Close()
var hash hash.Hash
switch hashFunc {
case md5Hash:
hash = md5.New()
case sha256Hash:
hash = sha256.New()
case sha512Hash:
hash = sha512.New()
default:
return "", fmt.Errorf("Unsupported hash function %s", hashFunc)
}
if _, err := io.Copy(hash, fh); err != nil {
return "", err
}
return fmt.Sprintf("%x", hash.Sum(nil)), nil
}
func (f *DefFile) Md5() (string, error) {
return f.hash(md5Hash)
}
func (f *DefFile) Sha256() (string, error) {
return f.hash(sha256Hash)
}
func (f *DefFile) Sha512() (string, error) {
return f.hash(sha512Hash)
}
func getUserForUid(uid int) (string, error) {
if user, err := user.LookupId(strconv.Itoa(uid)); err == nil {
return user.Username, nil
}
cmd := util.NewCommand("getent", "passwd", strconv.Itoa(uid))
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("Error: no matching entries in passwd file. getent passwd: %v", err)
}
userS := strings.Split(cmd.Stdout.String(), ":")[0]
return userS, nil
}
func getGroupForGid(gid int) (string, error) {
if group, err := user.LookupGroupId(strconv.Itoa(gid)); err == nil {
return group.Name, nil
}
cmd := util.NewCommand("getent", "group", strconv.Itoa(gid))
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("Error: no matching entries in passwd file. getent group: %v", err)
}
groupS := strings.Split(cmd.Stdout.String(), ":")[0]
return groupS, nil
}
goss-0.4.9/system/file_posix.go 0000664 0000000 0000000 00000003366 14675050513 0016531 0 ustar 00root root 0000000 0000000 //go:build linux || darwin || !windows
// +build linux darwin !windows
package system
import (
"fmt"
"os"
"strconv"
"syscall"
)
func (f *DefFile) Mode() (string, error) {
mode, err := f.getFileInfo(func(fi os.FileInfo) string {
stat := fi.Sys().(*syscall.Stat_t)
return fmt.Sprintf("%04o", (stat.Mode & 07777))
})
if err != nil {
return "", err
}
return mode, nil
}
func (f *DefFile) Owner() (string, error) {
uidS, err := f.getFileInfo(func(fi os.FileInfo) string {
return fmt.Sprint(fi.Sys().(*syscall.Stat_t).Uid)
})
if err != nil {
return "", err
}
uid, err := strconv.Atoi(uidS)
if err != nil {
return "", err
}
return getUserForUid(uid)
}
func (f *DefFile) Uid() (int, error) {
uidS, err := f.getFileInfo(func(fi os.FileInfo) string {
return fmt.Sprint(fi.Sys().(*syscall.Stat_t).Uid)
})
if err != nil {
return -1, err
}
uid, err := strconv.Atoi(uidS)
if err != nil {
return -1, err
}
return uid, nil
}
func (f *DefFile) Group() (string, error) {
gidS, err := f.getFileInfo(func(fi os.FileInfo) string {
return fmt.Sprint(fi.Sys().(*syscall.Stat_t).Gid)
})
if err != nil {
return "", err
}
gid, err := strconv.Atoi(gidS)
if err != nil {
return "", err
}
return getGroupForGid(gid)
}
func (f *DefFile) Gid() (int, error) {
gidS, err := f.getFileInfo(func(fi os.FileInfo) string {
return fmt.Sprint(fi.Sys().(*syscall.Stat_t).Gid)
})
if err != nil {
return -1, err
}
gid, err := strconv.Atoi(gidS)
if err != nil {
return -1, err
}
return gid, nil
}
func (f *DefFile) getFileInfo(selectorFunc func(os.FileInfo) string) (string, error) {
if err := f.setup(); err != nil {
return "", err
}
fi, err := os.Lstat(f.realPath)
if err != nil {
return "", err
}
return selectorFunc(fi), nil
}
goss-0.4.9/system/file_windows.go 0000664 0000000 0000000 00000000774 14675050513 0017061 0 ustar 00root root 0000000 0000000 //go:build windows
// +build windows
package system
func (f *DefFile) Mode() (string, error) {
return "-1", nil // not applicable on Windows
}
func (f *DefFile) Owner() (string, error) {
return "-1", nil // not applicable on Windows
}
func (f *DefFile) Uid() (int, error) {
return -1, nil // not applicable on Windows
}
func (f *DefFile) Group() (string, error) {
return "-1", nil // not applicable on Windows
}
func (f *DefFile) Gid() (int, error) {
return -1, nil // not applicable on Windows
}
goss-0.4.9/system/gossfile.go 0000664 0000000 0000000 00000000706 14675050513 0016176 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"github.com/goss-org/goss/util"
)
type Gossfile interface {
Path() string
Exists() (bool, error)
}
type DefGossfile struct {
path string
}
func (g *DefGossfile) Path() string {
return g.path
}
// Stub out
func (g *DefGossfile) Exists() (bool, error) {
return false, nil
}
func NewDefGossfile(_ context.Context, path string, system *System, config util.Config) Gossfile {
return &DefGossfile{path: path}
}
goss-0.4.9/system/group.go 0000664 0000000 0000000 00000001432 14675050513 0015514 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"os/user"
"strconv"
"github.com/goss-org/goss/util"
)
type Group interface {
Groupname() string
Exists() (bool, error)
GID() (int, error)
}
type DefGroup struct {
groupname string
}
func NewDefGroup(_ context.Context, groupname string, system *System, config util.Config) Group {
return &DefGroup{groupname: groupname}
}
func (u *DefGroup) Groupname() string {
return u.groupname
}
func (u *DefGroup) Exists() (bool, error) {
_, err := user.LookupGroup(u.groupname)
if err != nil {
return false, nil
}
return true, nil
}
func (u *DefGroup) GID() (int, error) {
group, err := user.LookupGroup(u.groupname)
if err != nil {
return 0, err
}
gid, err := strconv.Atoi(group.Gid)
if err != nil {
return 0, err
}
return gid, nil
}
goss-0.4.9/system/http.go 0000664 0000000 0000000 00000011362 14675050513 0015342 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net/http"
"net/url"
"os"
"sort"
"strings"
"time"
"github.com/goss-org/goss/util"
)
const USER_AGENT_HEADER_PREFIX = "user-agent:"
const DEFAULT_USER_AGENT_PREFIX = "goss/"
type HTTP interface {
HTTP() string
Status() (int, error)
Headers() (io.Reader, error)
Body() (io.Reader, error)
Exists() (bool, error)
SetAllowInsecure(bool)
SetNoFollowRedirects(bool)
}
type DefHTTP struct {
http string
allowInsecure bool
noFollowRedirects bool
resp *http.Response
RequestHeader http.Header
RequestBody string
Timeout int
loaded bool
err error
Username string
Password string
CAFile string
CertFile string
KeyFile string
Method string
Proxy string
}
func NewDefHTTP(_ context.Context, httpStr string, system *System, config util.Config) HTTP {
headers := http.Header{}
if !hasUserAgentHeader(config.RequestHeader) {
config.RequestHeader = append(config.RequestHeader, fmt.Sprintf("%s %s%s", USER_AGENT_HEADER_PREFIX, DEFAULT_USER_AGENT_PREFIX, util.Version))
}
for _, r := range config.RequestHeader {
str := strings.SplitN(r, ": ", 2)
headers.Add(str[0], str[1])
}
return &DefHTTP{
http: httpStr,
allowInsecure: config.AllowInsecure,
Method: config.Method,
noFollowRedirects: config.NoFollowRedirects,
RequestHeader: headers,
RequestBody: config.RequestBody,
Timeout: config.TimeOutMilliSeconds(),
Username: config.Username,
Password: config.Password,
CAFile: config.CAFile,
CertFile: config.CertFile,
KeyFile: config.KeyFile,
Proxy: config.Proxy,
}
}
func HeaderToArray(header http.Header) (res []string) {
for name, values := range header {
for _, value := range values {
res = append(res, fmt.Sprintf("%s: %s", name, value))
}
}
sort.Strings(res)
return
}
func (u *DefHTTP) setup() error {
if u.loaded {
return u.err
}
u.loaded = true
if err := u.setupReal(); err != nil {
u.err = err
}
return u.err
}
func (u *DefHTTP) setupReal() error {
proxyURL := http.ProxyFromEnvironment
if u.Proxy != "" {
parseProxy, err := url.Parse(u.Proxy)
if err != nil {
return err
}
proxyURL = http.ProxyURL(parseProxy)
}
tlsConfig := &tls.Config{
InsecureSkipVerify: u.allowInsecure,
Renegotiation: tls.RenegotiateFreelyAsClient,
}
if u.CAFile != "" {
// FIXME: iotutil
caCert, err := os.ReadFile(u.CAFile)
if err != nil {
return err
}
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM(caCert)
if !ok {
return fmt.Errorf("Failed parse root certificate: %s", u.CAFile)
}
tlsConfig.RootCAs = roots
}
if u.CertFile != "" && u.KeyFile != "" {
cert, err := tls.LoadX509KeyPair(u.CertFile, u.KeyFile)
if err != nil {
return err
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
tr := &http.Transport{
TLSClientConfig: tlsConfig,
DisableKeepAlives: true,
Proxy: proxyURL,
}
client := &http.Client{
Transport: tr,
Timeout: time.Duration(u.Timeout) * time.Millisecond,
}
if u.noFollowRedirects {
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
}
req, err := http.NewRequest(u.Method, u.http, strings.NewReader(u.RequestBody))
if err != nil {
return err
}
req.Header = u.RequestHeader.Clone()
if host := req.Header.Get("Host"); host != "" {
req.Host = host
}
if u.Username != "" || u.Password != "" {
req.SetBasicAuth(u.Username, u.Password)
}
u.resp, u.err = client.Do(req)
return u.err
}
func (u *DefHTTP) Exists() (bool, error) {
if _, err := u.Status(); err != nil {
return false, err
}
return true, nil
}
func (u *DefHTTP) SetNoFollowRedirects(t bool) {
u.noFollowRedirects = t
}
func (u *DefHTTP) SetAllowInsecure(t bool) {
u.allowInsecure = t
}
func (u *DefHTTP) ID() string {
return u.http
}
func (u *DefHTTP) HTTP() string {
return u.http
}
func (u *DefHTTP) Status() (int, error) {
if err := u.setup(); err != nil {
return 0, err
}
return u.resp.StatusCode, nil
}
func (u *DefHTTP) Headers() (io.Reader, error) {
if err := u.setup(); err != nil {
return nil, err
}
var headerString = strings.Join(HeaderToArray(u.resp.Header), "\n")
return strings.NewReader(headerString), nil
}
func (u *DefHTTP) Body() (io.Reader, error) {
if err := u.setup(); err != nil {
return nil, err
}
return u.resp.Body, nil
}
func hasUserAgentHeader(headers []string) bool {
for _, header := range headers {
if strings.HasPrefix(strings.ToLower(header), USER_AGENT_HEADER_PREFIX) {
return true
}
}
return false
}
goss-0.4.9/system/interface.go 0000664 0000000 0000000 00000002510 14675050513 0016316 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"net"
"github.com/goss-org/goss/util"
)
type Interface interface {
Name() string
Exists() (bool, error)
Addrs() ([]string, error)
MTU() (int, error)
}
type DefInterface struct {
name string
loaded bool
exists bool
iface *net.Interface
err error
}
func NewDefInterface(_ context.Context, name string, systei *System, config util.Config) Interface {
return &DefInterface{
name: name,
}
}
func (i *DefInterface) setup() error {
if i.loaded {
return i.err
}
i.loaded = true
iface, err := net.InterfaceByName(i.name)
if err != nil {
i.exists = false
i.err = err
return i.err
}
i.iface = iface
i.exists = true
return nil
}
func (i *DefInterface) ID() string {
return i.name
}
func (i *DefInterface) Name() string {
return i.name
}
func (i *DefInterface) Exists() (bool, error) {
if err := i.setup(); err != nil {
return false, nil
}
return i.exists, nil
}
func (i *DefInterface) Addrs() ([]string, error) {
if err := i.setup(); err != nil {
return nil, err
}
addrs, err := i.iface.Addrs()
if err != nil {
return nil, err
}
var ret []string
for _, addr := range addrs {
ret = append(ret, addr.String())
}
return ret, nil
}
func (i *DefInterface) MTU() (int, error) {
if err := i.setup(); err != nil {
return 0, err
}
return i.iface.MTU, nil
}
goss-0.4.9/system/kernel_param.go 0000664 0000000 0000000 00000001313 14675050513 0017016 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"github.com/achanda/go-sysctl"
"github.com/goss-org/goss/util"
)
type KernelParam interface {
Key() string
Exists() (bool, error)
Value() (string, error)
}
type DefKernelParam struct {
key string
}
func NewDefKernelParam(_ context.Context, key string, system *System, config util.Config) KernelParam {
return &DefKernelParam{
key: key,
}
}
func (k *DefKernelParam) ID() string {
return k.key
}
func (k *DefKernelParam) Key() string {
return k.key
}
func (k *DefKernelParam) Exists() (bool, error) {
if _, err := k.Value(); err != nil {
return false, nil
}
return true, nil
}
func (k *DefKernelParam) Value() (string, error) {
return sysctl.Get(k.key)
}
goss-0.4.9/system/log.go 0000664 0000000 0000000 00000000342 14675050513 0015140 0 ustar 00root root 0000000 0000000 package system
import (
"bytes"
"log"
)
func logBytes(b []byte, prefix string) {
if len(b) == 0 {
return
}
lines := bytes.Split(b, []byte("\n"))
for _, l := range lines {
log.Printf("[DEBUG]%s %s", prefix, l)
}
}
goss-0.4.9/system/mount.go 0000664 0000000 0000000 00000005555 14675050513 0015534 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"fmt"
"strings"
"time"
"github.com/goss-org/goss/util"
"github.com/moby/sys/mountinfo"
"github.com/samber/lo"
)
type Mount interface {
MountPoint() string
Exists() (bool, error)
Opts() ([]string, error)
VfsOpts() ([]string, error)
Source() (string, error)
Filesystem() (string, error)
Usage() (int, error)
}
type DefMount struct {
mountPoint string
loaded bool
exists bool
mountInfo *mountinfo.Info
usage int
Timeout int
err error
}
func NewDefMount(_ context.Context, mountPoint string, system *System, config util.Config) Mount {
return &DefMount{
mountPoint: mountPoint,
Timeout: config.TimeOutMilliSeconds(),
}
}
func (m *DefMount) setup() error {
if m.loaded {
return m.err
}
m.loaded = true
mountInfo, err := getMount(m.mountPoint, m.Timeout)
if err != nil {
m.exists = false
m.err = err
return m.err
}
m.mountInfo = mountInfo
m.exists = true
usage, err := getUsage(m.mountPoint)
if err != nil {
m.err = err
return m.err
}
m.usage = usage
return nil
}
func (m *DefMount) ID() string {
return m.mountPoint
}
func (m *DefMount) MountPoint() string {
return m.mountPoint
}
func (m *DefMount) Exists() (bool, error) {
if err := m.setup(); err != nil {
return false, err
}
return m.exists, nil
}
func (m *DefMount) Opts() ([]string, error) {
if err := m.setup(); err != nil {
return nil, err
}
allOpts := splitMountInfo(m.mountInfo.Options)
return lo.Uniq(allOpts), nil
}
func (m *DefMount) VfsOpts() ([]string, error) {
if err := m.setup(); err != nil {
return nil, err
}
opts := splitMountInfo(m.mountInfo.VFSOptions)
return opts, nil
}
func (m *DefMount) Source() (string, error) {
if err := m.setup(); err != nil {
return "", err
}
return m.mountInfo.Source, nil
}
func (m *DefMount) Filesystem() (string, error) {
if err := m.setup(); err != nil {
return "", err
}
return m.mountInfo.FSType, nil
}
func (m *DefMount) Usage() (int, error) {
if err := m.setup(); err != nil {
return -1, err
}
return m.usage, nil
}
func getMount(mountpoint string, timeout int) (*mountinfo.Info, error) {
c1 := make(chan *mountinfo.Info, 1)
e1 := make(chan error, 1)
timeoutD := time.Duration(timeout) * time.Millisecond
go func() {
entries, err := mountinfo.GetMounts(mountinfo.SingleEntryFilter(mountpoint))
if err != nil {
e1 <- err
return
}
if len(entries) == 0 {
e1 <- fmt.Errorf("Mountpoint not found")
return
}
c1 <- entries[0]
}()
select {
case result := <-c1:
return result, nil
case err := <-e1:
return nil, err
case <-time.After(timeoutD):
return nil, fmt.Errorf("getMount operation timed out after %s milliseconds", timeoutD)
}
}
func splitMountInfo(s string) []string {
quoted := false
return strings.FieldsFunc(s, func(r rune) bool {
if r == '"' {
quoted = !quoted
}
return !quoted && r == ','
})
}
goss-0.4.9/system/mount_posix.go 0000664 0000000 0000000 00000000661 14675050513 0016747 0 ustar 00root root 0000000 0000000 //go:build linux || darwin || !windows
// +build linux darwin !windows
package system
import (
"math"
"syscall"
)
func getUsage(mountpoint string) (int, error) {
statfsOut := &syscall.Statfs_t{}
err := syscall.Statfs(mountpoint, statfsOut)
if err != nil {
return -1, err
}
percentageFree := float64(statfsOut.Bfree) / float64(statfsOut.Blocks)
usage := math.Round((1 - percentageFree) * 100)
return int(usage), nil
}
goss-0.4.9/system/mount_test.go 0000664 0000000 0000000 00000000604 14675050513 0016561 0 ustar 00root root 0000000 0000000 package system
import (
"testing"
"gotest.tools/v3/assert"
)
func TestSplitMountInfo(t *testing.T) {
in := "rw,context=\"system_u:object_r:container_file_t:s0:c174,c741\",size=65536k,mode=755"
want := []string{
"rw",
"context=\"system_u:object_r:container_file_t:s0:c174,c741\"",
"size=65536k",
"mode=755",
}
got := splitMountInfo(in)
assert.DeepEqual(t, got, want)
}
goss-0.4.9/system/mount_windows.go 0000664 0000000 0000000 00000000242 14675050513 0017272 0 ustar 00root root 0000000 0000000 //go:build windows
// +build windows
package system
import "errors"
func getUsage(mountpoint string) (int, error) {
return 0, errors.New("Not implemented")
}
goss-0.4.9/system/package.go 0000664 0000000 0000000 00000001443 14675050513 0015755 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"errors"
"github.com/goss-org/goss/util"
)
type Package interface {
Name() string
Exists() (bool, error)
Installed() (bool, error)
Versions() ([]string, error)
}
var ErrNullPackage = errors.New("Could not detect Package type on this system, please use --package flag to explicity set it")
type NullPackage struct {
name string
}
func NewNullPackage(_ context.Context, name string, system *System, config util.Config) Package {
return &NullPackage{name: name}
}
func (p *NullPackage) Name() string { return p.name }
func (p *NullPackage) Exists() (bool, error) { return p.Installed() }
func (p *NullPackage) Installed() (bool, error) {
return false, ErrNullPackage
}
func (p *NullPackage) Versions() ([]string, error) {
return nil, ErrNullPackage
}
goss-0.4.9/system/package_alpine.go 0000664 0000000 0000000 00000002336 14675050513 0017307 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"errors"
"strings"
"github.com/goss-org/goss/util"
)
type AlpinePackage struct {
name string
versions []string
loaded bool
installed bool
}
func NewAlpinePackage(_ context.Context, name string, system *System, config util.Config) Package {
return &AlpinePackage{name: name}
}
func (p *AlpinePackage) setup() {
if p.loaded {
return
}
p.loaded = true
cmd := util.NewCommand("apk", "version", p.name)
if err := cmd.Run(); err != nil {
return
}
for _, l := range strings.Split(strings.TrimSpace(cmd.Stdout.String()), "\n") {
if strings.HasPrefix(l, "Installed:") || strings.HasPrefix(l, "WARNING") {
continue
}
ver := strings.TrimPrefix(strings.Fields(l)[0], p.name+"-")
p.versions = append(p.versions, ver)
}
if len(p.versions) > 0 {
p.installed = true
}
}
func (p *AlpinePackage) Name() string {
return p.name
}
func (p *AlpinePackage) Exists() (bool, error) { return p.Installed() }
func (p *AlpinePackage) Installed() (bool, error) {
p.setup()
return p.installed, nil
}
func (p *AlpinePackage) Versions() ([]string, error) {
p.setup()
if len(p.versions) == 0 {
return p.versions, errors.New("Package version not found")
}
return p.versions, nil
}
goss-0.4.9/system/package_deb.go 0000664 0000000 0000000 00000002337 14675050513 0016572 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"errors"
"strings"
"github.com/goss-org/goss/util"
)
type DebPackage struct {
name string
versions []string
loaded bool
installed bool
}
func NewDebPackage(_ context.Context, name string, system *System, config util.Config) Package {
return &DebPackage{name: name}
}
func (p *DebPackage) setup() {
if p.loaded {
return
}
p.loaded = true
cmd := util.NewCommand("dpkg-query", "-f", "${Status} ${Version}\n", "-W", p.name)
if err := cmd.Run(); err != nil {
return
}
for _, l := range strings.Split(strings.TrimSpace(cmd.Stdout.String()), "\n") {
if !(strings.HasPrefix(l, "install ok installed") || strings.HasPrefix(l, "hold ok installed")) {
continue
}
ver := strings.Fields(l)[3]
p.versions = append(p.versions, ver)
}
if len(p.versions) > 0 {
p.installed = true
}
}
func (p *DebPackage) Name() string {
return p.name
}
func (p *DebPackage) Exists() (bool, error) { return p.Installed() }
func (p *DebPackage) Installed() (bool, error) {
p.setup()
return p.installed, nil
}
func (p *DebPackage) Versions() ([]string, error) {
p.setup()
if len(p.versions) == 0 {
return p.versions, errors.New("Package version not found")
}
return p.versions, nil
}
goss-0.4.9/system/package_pacman.go 0000664 0000000 0000000 00000002237 14675050513 0017276 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"errors"
"strings"
"github.com/goss-org/goss/util"
)
type PacmanPackage struct {
name string
versions []string
loaded bool
installed bool
}
func NewPacmanPackage(_ context.Context, name string, system *System, config util.Config) Package {
return &PacmanPackage{name: name}
}
func (p *PacmanPackage) setup() {
if p.loaded {
return
}
p.loaded = true
// TODO: extract versions
cmd := util.NewCommand("pacman", "-Q", "--color", "never", "--noconfirm", p.name)
if err := cmd.Run(); err != nil {
return
}
p.installed = true
// the output format is "pkgname version\n", so if we split the string on
// whitespace, the version is the second item.
p.versions = []string{strings.Fields(cmd.Stdout.String())[1]}
}
func (p *PacmanPackage) Name() string {
return p.name
}
func (p *PacmanPackage) Exists() (bool, error) { return p.Installed() }
func (p *PacmanPackage) Installed() (bool, error) {
p.setup()
return p.installed, nil
}
func (p *PacmanPackage) Versions() ([]string, error) {
p.setup()
if len(p.versions) == 0 {
return p.versions, errors.New("Package version not found")
}
return p.versions, nil
}
goss-0.4.9/system/package_rpm.go 0000664 0000000 0000000 00000002075 14675050513 0016635 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"errors"
"strings"
"github.com/goss-org/goss/util"
)
type RpmPackage struct {
name string
versions []string
loaded bool
installed bool
}
func NewRpmPackage(_ context.Context, name string, system *System, config util.Config) Package {
return &RpmPackage{name: name}
}
func (p *RpmPackage) setup() {
if p.loaded {
return
}
p.loaded = true
cmd := util.NewCommand("rpm", "-q", "--nosignature", "--nohdrchk", "--nodigest", "--qf", "%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\n", p.name)
if err := cmd.Run(); err != nil {
return
}
p.installed = true
p.versions = strings.Split(strings.TrimSpace(cmd.Stdout.String()), "\n")
}
func (p *RpmPackage) Name() string {
return p.name
}
func (p *RpmPackage) Exists() (bool, error) { return p.Installed() }
func (p *RpmPackage) Installed() (bool, error) {
p.setup()
return p.installed, nil
}
func (p *RpmPackage) Versions() ([]string, error) {
p.setup()
if len(p.versions) == 0 {
return p.versions, errors.New("Package version not found")
}
return p.versions, nil
}
goss-0.4.9/system/package_test.go 0000664 0000000 0000000 00000000432 14675050513 0017011 0 ustar 00root root 0000000 0000000 package system
import (
"testing"
)
func TestIsSupportedPackageManager(t *testing.T) {
if IsSupportedPackageManager("na") {
t.Fatal("na should not be a valid package manager")
}
if !IsSupportedPackageManager("rpm") {
t.Fatal("rpm should be a valid package manager")
}
}
goss-0.4.9/system/port.go 0000664 0000000 0000000 00000005024 14675050513 0015345 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"strconv"
"strings"
"github.com/goss-org/GOnetstat"
"github.com/goss-org/goss/util"
)
type Port interface {
Port() string
Exists() (bool, error)
Listening() (bool, error)
IP() ([]string, error)
}
type DefPort struct {
port string
sysPorts map[string][]GOnetstat.Process
}
func NewDefPort(_ context.Context, port string, system *System, config util.Config) Port {
p := normalizePort(port)
return &DefPort{
port: p,
sysPorts: system.Ports(),
}
}
func splitPort(fullport string) (network, port string) {
split := strings.SplitN(fullport, ":", 2)
if len(split) == 2 {
return split[0], split[1]
}
return "tcp", fullport
}
func normalizePort(fullport string) string {
net, addr := splitPort(fullport)
return net + ":" + addr
}
func (p *DefPort) Port() string {
return p.port
}
func (p *DefPort) Exists() (bool, error) { return p.Listening() }
func (p *DefPort) Listening() (bool, error) {
if _, ok := p.sysPorts[p.port]; ok {
return true, nil
}
return false, nil
}
func (p *DefPort) IP() ([]string, error) {
var ips []string
for _, entry := range p.sysPorts[p.port] {
ips = append(ips, entry.Ip)
}
return ips, nil
}
// FIXME: Is there a better way to do this rather than ignoring errors?
func GetPorts(lookupPids bool) map[string][]GOnetstat.Process {
ports := make(map[string][]GOnetstat.Process)
netstat, _ := GOnetstat.Tcp(lookupPids)
var net string
// netPorts := make(map[string]GOnetstat.Process)
// ports["tcp"] = netPorts
net = "tcp"
for _, entry := range netstat {
if entry.State == "LISTEN" {
port := strconv.FormatInt(entry.Port, 10)
ports[net+":"+port] = append(ports[net+":"+port], entry)
}
}
netstat, _ = GOnetstat.Tcp6(lookupPids)
// netPorts = make(map[string]GOnetstat.Process)
// ports["tcp6"] = netPorts
net = "tcp6"
for _, entry := range netstat {
if entry.State == "LISTEN" {
port := strconv.FormatInt(entry.Port, 10)
ports[net+":"+port] = append(ports[net+":"+port], entry)
}
}
netstat, _ = GOnetstat.Udp(lookupPids)
// netPorts = make(map[string]GOnetstat.Process)
// ports["udp"] = netPorts
net = "udp"
for _, entry := range netstat {
port := strconv.FormatInt(entry.Port, 10)
ports[net+":"+port] = append(ports[net+":"+port], entry)
}
netstat, _ = GOnetstat.Udp6(lookupPids)
// netPorts = make(map[string]GOnetstat.Process)
// ports["udp6"] = netPorts
net = "udp6"
for _, entry := range netstat {
port := strconv.FormatInt(entry.Port, 10)
ports[net+":"+port] = append(ports[net+":"+port], entry)
}
return ports
}
goss-0.4.9/system/process.go 0000664 0000000 0000000 00000002504 14675050513 0016037 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"github.com/goss-org/go-ps"
"github.com/goss-org/goss/util"
)
type Process interface {
Executable() string
Exists() (bool, error)
Running() (bool, error)
Pids() ([]int, error)
}
type DefProcess struct {
executable string
procMap map[string][]ps.Process
err error
}
func NewDefProcess(_ context.Context, executable string, system *System, config util.Config) Process {
pmap, err := system.ProcMap()
return &DefProcess{
executable: executable,
procMap: pmap,
err: err,
}
}
func (p *DefProcess) Executable() string {
return p.executable
}
func (p *DefProcess) Exists() (bool, error) { return p.Running() }
func (p *DefProcess) Pids() ([]int, error) {
var pids []int
if p.err != nil {
return pids, p.err
}
for _, proc := range p.procMap[p.executable] {
pids = append(pids, proc.Pid())
}
return pids, nil
}
func (p *DefProcess) Running() (bool, error) {
if p.err != nil {
return false, p.err
}
if _, ok := p.procMap[p.executable]; ok {
return true, nil
}
return false, nil
}
func GetProcs() (map[string][]ps.Process, error) {
pmap := make(map[string][]ps.Process)
processes, err := ps.Processes()
if err != nil {
return pmap, err
}
for _, p := range processes {
pmap[p.Executable()] = append(pmap[p.Executable()], p)
}
return pmap, nil
}
goss-0.4.9/system/service.go 0000664 0000000 0000000 00000000405 14675050513 0016017 0 ustar 00root root 0000000 0000000 package system
import "strings"
type Service interface {
Service() string
Exists() (bool, error)
Enabled() (bool, error)
Running() (bool, error)
RunLevels() ([]string, error)
}
func invalidService(s string) bool {
return strings.ContainsRune(s, '/')
}
goss-0.4.9/system/service_init.go 0000664 0000000 0000000 00000004673 14675050513 0017055 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"fmt"
"os"
"path/filepath"
"regexp"
"github.com/goss-org/goss/util"
)
type ServiceInit struct {
service string
alpine bool
runlevel string
}
func NewServiceInit(_ context.Context, service string, system *System, config util.Config) Service {
return &ServiceInit{service: service}
}
func NewAlpineServiceInit(_ context.Context, service string, system *System, config util.Config) Service {
runlevel := config.RunLevel
if runlevel == "" {
runlevel = "sysinit"
}
return &ServiceInit{service: service, alpine: true, runlevel: runlevel}
}
func (s *ServiceInit) Service() string {
return s.service
}
func (s *ServiceInit) Exists() (bool, error) {
if invalidService(s.service) {
return false, nil
}
if _, err := os.Stat(fmt.Sprintf("/etc/init.d/%s", s.service)); err == nil {
return true, err
}
return false, nil
}
func (s *ServiceInit) Enabled() (bool, error) {
if invalidService(s.service) {
return false, nil
}
var runLevels []string
var err error
if s.alpine {
runLevels, err = alpineServiceRunLevels(s.service)
} else {
runLevels, err = initServiceRunLevels(s.service)
}
return len(runLevels) != 0, err
}
func (s *ServiceInit) RunLevels() ([]string, error) {
if invalidService(s.service) {
return nil, nil
}
if s.alpine {
return alpineServiceRunLevels(s.service)
} else {
return initServiceRunLevels(s.service)
}
}
func (s *ServiceInit) Running() (bool, error) {
if invalidService(s.service) {
return false, nil
}
cmd := util.NewCommand("service", s.service, "status")
cmd.Run()
if cmd.Status == 0 {
return true, cmd.Err
}
return false, nil
}
func initServiceRunLevels(service string) ([]string, error) {
var runLevels []string
matches, err := filepath.Glob(fmt.Sprintf("/etc/rc*.d/S[0-9][0-9]%s", service))
if err != nil {
return nil, err
}
re := regexp.MustCompile("/etc/rc([0-9]+).d/")
for _, m := range matches {
matches := re.FindStringSubmatch(m)
if matches != nil {
runLevels = append(runLevels, matches[1])
}
}
return runLevels, nil
}
func alpineServiceRunLevels(service string) ([]string, error) {
var runLevels []string
matches, err := filepath.Glob(fmt.Sprintf("/etc/runlevels/*/%s", service))
if err != nil {
return nil, err
}
re := regexp.MustCompile("/etc/runlevels/([^/]+)")
for _, m := range matches {
matches := re.FindStringSubmatch(m)
if matches != nil {
runLevels = append(runLevels, matches[1])
}
}
return runLevels, nil
}
goss-0.4.9/system/service_systemd.go 0000664 0000000 0000000 00000003613 14675050513 0017573 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"fmt"
"strings"
"github.com/goss-org/goss/util"
)
type ServiceSystemd struct {
service string
legacy bool
}
func NewServiceSystemd(_ context.Context, service string, system *System, config util.Config) Service {
return &ServiceSystemd{
service: service,
}
}
func NewServiceSystemdLegacy(_ context.Context, service string, system *System, config util.Config) Service {
return &ServiceSystemd{
service: service,
legacy: true,
}
}
func (s *ServiceSystemd) Service() string {
return s.service
}
func (s *ServiceSystemd) Exists() (bool, error) {
if invalidService(s.service) {
return false, nil
}
cmd := util.NewCommand("systemctl", "-q", "list-unit-files", "--type=service")
cmd.Run()
if strings.Contains(cmd.Stdout.String(), fmt.Sprintf("%s.service", s.service)) {
return true, cmd.Err
}
if s.legacy {
// Fallback on sysv
sysv := &ServiceInit{service: s.service}
if e, err := sysv.Exists(); e && err == nil {
return true, nil
}
}
return false, nil
}
func (s *ServiceSystemd) Enabled() (bool, error) {
if invalidService(s.service) {
return false, nil
}
cmd := util.NewCommand("systemctl", "-q", "is-enabled", s.service)
cmd.Run()
if cmd.Status == 0 {
return true, cmd.Err
}
if s.legacy {
// Fallback on sysv
sysv := &ServiceInit{service: s.service}
if en, err := sysv.Enabled(); en && err == nil {
return true, nil
}
}
return false, nil
}
func (s *ServiceSystemd) Running() (bool, error) {
if invalidService(s.service) {
return false, nil
}
cmd := util.NewCommand("systemctl", "-q", "is-active", s.service)
cmd.Run()
if cmd.Status == 0 {
return true, cmd.Err
}
if s.legacy {
// Fallback on sysv
sysv := &ServiceInit{service: s.service}
if r, err := sysv.Running(); r && err == nil {
return true, nil
}
}
return false, nil
}
func (s *ServiceSystemd) RunLevels() ([]string, error) {
return nil, nil
}
goss-0.4.9/system/service_upstart.go 0000664 0000000 0000000 00000003773 14675050513 0017614 0 ustar 00root root 0000000 0000000 package system
import (
"bufio"
"context"
"fmt"
"os"
"regexp"
"strings"
"github.com/goss-org/goss/util"
)
type ServiceUpstart struct {
service string
}
var upstartEnabled = regexp.MustCompile(`^\s*start on`)
var upstartDisabled = regexp.MustCompile(`^manual`)
func NewServiceUpstart(_ context.Context, service string, system *System, config util.Config) Service {
return &ServiceUpstart{service: service}
}
func (s *ServiceUpstart) Service() string {
return s.service
}
func (s *ServiceUpstart) Exists() (bool, error) {
// upstart
if _, err := os.Stat(fmt.Sprintf("/etc/init/%s.conf", s.service)); err == nil {
return true, nil
}
// Fallback on sysv
sysv := &ServiceInit{service: s.service}
if e, err := sysv.Exists(); e && err == nil {
return true, nil
}
return false, nil
}
func (s *ServiceUpstart) Enabled() (bool, error) {
if fh, err := os.Open(fmt.Sprintf("/etc/init/%s.override", s.service)); err == nil {
scanner := bufio.NewScanner(fh)
for scanner.Scan() {
line := scanner.Text()
if upstartDisabled.MatchString(line) {
return false, nil
}
}
}
// If no /etc/init/.override with `manual` keyword in it has been found
// Check the contents of the upstart manifest.
if fh, err := os.Open(fmt.Sprintf("/etc/init/%s.conf", s.service)); err == nil {
scanner := bufio.NewScanner(fh)
for scanner.Scan() {
line := scanner.Text()
if upstartEnabled.MatchString(line) {
return true, nil
}
}
}
// Fallback on sysv
sysv := &ServiceInit{service: s.service}
if en, err := sysv.Enabled(); en && err == nil {
return true, nil
}
return false, nil
}
func (s *ServiceUpstart) Running() (bool, error) {
cmd := util.NewCommand("service", s.service, "status")
cmd.Run()
out := cmd.Stdout.String()
if cmd.Status == 0 && (strings.Contains(out, "running") || strings.Contains(out, "online")) {
return true, cmd.Err
}
return false, nil
}
func (s *ServiceUpstart) RunLevels() ([]string, error) {
sysv := &ServiceInit{service: s.service}
return sysv.RunLevels()
}
goss-0.4.9/system/system.go 0000664 0000000 0000000 00000014201 14675050513 0015702 0 ustar 00root root 0000000 0000000 package system
import (
"bytes"
"context"
"os"
"os/exec"
"strconv"
"sync"
"github.com/goss-org/GOnetstat"
// This needs a better name
"github.com/goss-org/go-ps"
util2 "github.com/goss-org/goss/util"
)
type Resource interface {
Exists() (bool, error)
}
type System struct {
NewPackage func(context.Context, string, *System, util2.Config) Package
NewFile func(context.Context, string, *System, util2.Config) File
NewAddr func(context.Context, string, *System, util2.Config) Addr
NewPort func(context.Context, string, *System, util2.Config) Port
NewService func(context.Context, string, *System, util2.Config) Service
NewUser func(context.Context, string, *System, util2.Config) User
NewGroup func(context.Context, string, *System, util2.Config) Group
NewCommand func(context.Context, string, *System, util2.Config) Command
NewDNS func(context.Context, string, *System, util2.Config) DNS
NewProcess func(context.Context, string, *System, util2.Config) Process
NewGossfile func(context.Context, string, *System, util2.Config) Gossfile
NewKernelParam func(context.Context, string, *System, util2.Config) KernelParam
NewMount func(context.Context, string, *System, util2.Config) Mount
NewInterface func(context.Context, string, *System, util2.Config) Interface
NewHTTP func(context.Context, string, *System, util2.Config) HTTP
ports map[string][]GOnetstat.Process
portsOnce sync.Once
procMap map[string][]ps.Process
procOnce sync.Once
}
func (s *System) Ports() map[string][]GOnetstat.Process {
s.portsOnce.Do(func() {
s.ports = GetPorts(false)
})
return s.ports
}
func (s *System) ProcMap() (map[string][]ps.Process, error) {
var err error
s.procOnce.Do(func() {
s.procMap, err = GetProcs()
})
return s.procMap, err
}
func New(packageManager string) *System {
sys := &System{
NewFile: NewDefFile,
NewAddr: NewDefAddr,
NewPort: NewDefPort,
NewUser: NewDefUser,
NewGroup: NewDefGroup,
NewCommand: NewDefCommand,
NewDNS: NewDefDNS,
NewProcess: NewDefProcess,
NewGossfile: NewDefGossfile,
NewKernelParam: NewDefKernelParam,
NewMount: NewDefMount,
NewInterface: NewDefInterface,
NewHTTP: NewDefHTTP,
}
sys.detectService()
sys.detectPackage(packageManager)
return sys
}
// detectPackage adds the correct package creation function to a System struct
func (sys *System) detectPackage(p string) {
if p != "dpkg" && p != "apk" && p != "pacman" && p != "rpm" {
p = DetectPackageManager()
}
switch p {
case "dpkg":
sys.NewPackage = NewDebPackage
case "apk":
sys.NewPackage = NewAlpinePackage
case "pacman":
sys.NewPackage = NewPacmanPackage
default:
sys.NewPackage = NewRpmPackage
}
}
// detectService adds the correct service creation function to a System struct
func (sys *System) detectService() {
switch DetectService() {
case "upstart":
sys.NewService = NewServiceUpstart
case "systemd":
sys.NewService = NewServiceSystemd
case "systemdlegacy":
sys.NewService = NewServiceSystemdLegacy
case "alpineinit":
sys.NewService = NewAlpineServiceInit
default:
sys.NewService = NewServiceInit
}
}
// SupportedPackageManagers is a list of package managers we support
func SupportedPackageManagers() []string {
return []string{"apk", "dpkg", "pacman", "rpm"}
}
// IsSupportedPackageManager determines if p is a supported package manager
func IsSupportedPackageManager(p string) bool {
for _, m := range SupportedPackageManagers() {
if m == p {
return true
}
}
return false
}
// DetectPackageManager attempts to detect whether or not the system is using
// "dpkg", "rpm", "apk", or "pacman" package managers. It first attempts to
// detect the distro. If that fails, it falls back to finding package manager
// executables. If that fails, it returns the empty string.
func DetectPackageManager() string {
switch DetectDistro() {
case "ubuntu":
return "dpkg"
case "redhat":
return "rpm"
case "alpine":
return "apk"
case "arch":
return "pacman"
case "debian":
return "dpkg"
}
for _, manager := range []string{"dpkg", "rpm", "apk", "pacman"} {
if HasCommand(manager) {
return manager
}
}
return ""
}
// DetectService attempts to detect what kind of service management the system
// is using, "systemd", "upstart", "alpineinit", or "init". It looks for systemctl
// command to detect systemd, and falls back on DetectDistro otherwise. If it can't
// decide, it returns "init".
func DetectService() string {
if HasCommand("systemctl") {
if isLegacySystemd() {
return "systemdlegacy"
}
return "systemd"
}
// Centos Docker container doesn't run systemd, so we detect it or use init.
switch DetectDistro() {
case "ubuntu":
return "upstart"
case "alpine":
return "alpineinit"
case "arch":
return "systemd"
}
return "init"
}
// DetectDistro attempts to detect which Linux distribution this computer is
// using. One of "ubuntu", "redhat" (including Centos), "alpine", "arch", or
// "debian". If it can't decide, it returns an empty string.
func DetectDistro() string {
if b, e := os.ReadFile("/etc/lsb-release"); e == nil && bytes.Contains(b, []byte("Ubuntu")) {
return "ubuntu"
} else if isRedhat() {
return "redhat"
} else if _, err := os.Stat("/etc/alpine-release"); err == nil {
return "alpine"
} else if _, err := os.Stat("/etc/arch-release"); err == nil {
return "arch"
} else if _, err := os.Stat("/etc/debian_version"); err == nil {
return "debian"
}
return ""
}
// HasCommand returns whether or not an executable by this name is on the PATH.
func HasCommand(cmd string) bool {
if _, err := exec.LookPath(cmd); err == nil {
return true
}
return false
}
func isLegacySystemd() bool {
if b, err := os.ReadFile("/etc/debian_version"); err == nil {
i := bytes.Index(b, []byte("."))
if i < 0 {
return false
}
if major, err := strconv.Atoi(string(b[:i])); err == nil {
return major < 9
}
}
return false
}
func isRedhat() bool {
if _, err := os.Stat("/etc/redhat-release"); err == nil {
return true
} else if _, err := os.Stat("/etc/system-release"); err == nil {
return true
}
return false
}
goss-0.4.9/system/system_test.go 0000664 0000000 0000000 00000002175 14675050513 0016750 0 ustar 00root root 0000000 0000000 package system
import (
"reflect"
"runtime"
"testing"
)
type noInputs func() string
// test that a function with no inputs returns one of the expected strings
func testOutputs(f noInputs, validOutputs []string, t *testing.T) {
output := f()
// use reflect to get the name of the function
name := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
failed := true
for _, valid := range validOutputs {
if output == valid {
failed = false
}
}
if failed {
t.Errorf("Function %v returned %v, which is not one of %v", name, output, validOutputs)
}
}
func TestPackageManager(t *testing.T) {
t.Parallel()
testOutputs(
DetectPackageManager,
[]string{"dpkg", "rpm", "apk", "pacman", ""},
t,
)
}
func TestDetectService(t *testing.T) {
t.Parallel()
testOutputs(
DetectService,
[]string{"systemd", "init", "alpineinit", "upstart", ""},
t,
)
}
func TestDetectDistro(t *testing.T) {
t.Parallel()
testOutputs(
DetectDistro,
[]string{"ubuntu", "redhat", "alpine", "arch", "debian", ""},
t,
)
}
func TestHasCommand(t *testing.T) {
t.Parallel()
if !HasCommand("sh") {
t.Error("System didn't have sh!")
}
}
goss-0.4.9/system/user.go 0000664 0000000 0000000 00000002301 14675050513 0015332 0 ustar 00root root 0000000 0000000 package system
import (
"context"
"os/user"
"strconv"
"github.com/goss-org/goss/util"
)
type User interface {
Username() string
Exists() (bool, error)
UID() (int, error)
GID() (int, error)
Groups() ([]string, error)
Home() (string, error)
Shell() (string, error)
}
type DefUser struct {
username string
}
func NewDefUser(_ context.Context, username string, system *System, config util.Config) User {
return &DefUser{username: username}
}
func (u *DefUser) Username() string {
return u.username
}
func (u *DefUser) Exists() (bool, error) {
_, err := user.Lookup(u.username)
if err != nil {
return false, nil
}
return true, nil
}
func (u *DefUser) UID() (int, error) {
user, err := user.Lookup(u.username)
if err != nil {
return 0, err
}
uid, err := strconv.Atoi(user.Uid)
if err != nil {
return 0, err
}
return uid, nil
}
func (u *DefUser) GID() (int, error) {
user, err := user.Lookup(u.username)
if err != nil {
return 0, err
}
gid, err := strconv.Atoi(user.Gid)
if err != nil {
return 0, err
}
return gid, nil
}
func (u *DefUser) Home() (string, error) {
user, err := user.Lookup(u.username)
if err != nil {
return "", err
}
return user.HomeDir, nil
}
goss-0.4.9/system/user_group_unix.go 0000664 0000000 0000000 00000002325 14675050513 0017617 0 ustar 00root root 0000000 0000000 //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package system
import (
"bufio"
"io"
"os"
"sort"
"strconv"
"strings"
)
func groupsForUser(user string, pgid int, grp io.Reader) ([]string, error) {
s := bufio.NewScanner(grp)
out := []string{}
for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
text := s.Text()
if text == "" {
continue
}
// see: man 5 group
// group_name:password:GID:user_list
// Name:Pass:Gid:List
// root:x:0:root
// adm:x:4:root,adm,daemon
parts := strings.Split(text, ":")
if len(parts) != 4 {
continue
}
gid, err := strconv.Atoi(parts[2])
if err == nil {
if gid == pgid {
out = append(out, parts[0])
continue
}
}
for _, g := range strings.Split(parts[3], ",") {
if g == user {
out = append(out, parts[0])
continue
}
}
}
sort.Strings(out)
return out, nil
}
func (u *DefUser) Groups() ([]string, error) {
grp, err := os.Open("/etc/group")
if err != nil {
return nil, err
}
defer grp.Close()
pgid, err := u.GID()
if err != nil {
return nil, err
}
return groupsForUser(u.username, pgid, grp)
}
goss-0.4.9/system/user_group_unix_test.go 0000664 0000000 0000000 00000002016 14675050513 0020653 0 ustar 00root root 0000000 0000000 //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package system
import (
"strings"
"testing"
)
func TestGroupsForUser(t *testing.T) {
grp := `badline
testgrp1:*:100:bob,jack,jill
testgrp2:*:101:bob,jack
testgrp3:*:102:jill
testgrp4:*:103:`
var cases = []struct {
user string
gid int
expect []string
}{
{"bob", 100, []string{"testgrp1", "testgrp2"}},
{"jack", 100, []string{"testgrp1", "testgrp2"}},
{"jill", 103, []string{"testgrp1", "testgrp3", "testgrp4"}},
{"other", 103, []string{"testgrp4"}},
{"other", 105, []string{}},
}
for _, c := range cases {
res, err := groupsForUser(c.user, c.gid, strings.NewReader(grp))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(res) != len(c.expect) {
t.Fatalf("result %#v does not match %#v", res, c.expect)
}
for i, e := range c.expect {
if res[i] != e {
t.Fatalf("result %#v does not match %#v", res, c.expect)
}
}
}
}
goss-0.4.9/system/user_group_windows.go 0000664 0000000 0000000 00000001035 14675050513 0020323 0 ustar 00root root 0000000 0000000 package system
import (
"fmt"
"os/user"
"sort"
)
func (u *DefUser) Groups() ([]string, error) {
usr, err := user.Lookup(u.username)
if err != nil {
return nil, err
}
var groupList []string
ids, err := usr.GroupIds()
if err != nil {
return nil, err
}
for _, gid := range ids {
group, err := user.LookupGroupId(gid)
if err != nil {
return nil, fmt.Errorf("Unable to find groups for user %v: %v", usr.Username, err)
}
groupList = append(groupList, group.Name)
}
sort.Strings(groupList)
return groupList, nil
}
goss-0.4.9/system/user_unix.go 0000664 0000000 0000000 00000001270 14675050513 0016401 0 ustar 00root root 0000000 0000000 //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package system
import (
"bufio"
"fmt"
"os"
"strings"
)
func (u *DefUser) Shell() (string, error) {
passwd, err := os.Open("/etc/passwd")
if err != nil {
return "", err
}
defer passwd.Close()
lines := bufio.NewReader(passwd)
for {
line, _, err := lines.ReadLine()
if err != nil {
break
}
fs := strings.Split(string(line), ":")
if len(fs) != 7 {
return "", fmt.Errorf("invalid entry in /etc/passwd")
}
if fs[0] == u.username {
return fs[6], nil
}
}
return "", fmt.Errorf("unknown user %s", u.username)
}
goss-0.4.9/system/user_unsupported.go 0000664 0000000 0000000 00000000452 14675050513 0020007 0 ustar 00root root 0000000 0000000 //go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
package system
import (
"fmt"
)
func (u *DefUser) Shell() (string, error) {
return "", fmt.Errorf("unsupported operating system")
}
goss-0.4.9/template.go 0000664 0000000 0000000 00000005162 14675050513 0014653 0 ustar 00root root 0000000 0000000 package goss
import (
"bytes"
"fmt"
"os"
"regexp"
"strconv"
"strings"
"text/template"
"github.com/Masterminds/sprig/v3"
)
// TemplateFilter is the type of the Goss Template Filter which include custom variables and functions.
type TemplateFilter func([]byte) ([]byte, error)
// NewTemplateFilter creates a new Template Filter based in the file and inline variables.
func NewTemplateFilter(varsFile string, varsInline string) (func([]byte) ([]byte, error), error) {
vars, err := loadVars(varsFile, varsInline)
if err != nil {
return nil, fmt.Errorf("failed while loading vars file %q: %v", varsFile, err)
}
tVars := &TmplVars{Vars: vars}
f := func(data []byte) ([]byte, error) {
t := template.New("test").Funcs(sprig.TxtFuncMap()).Funcs(funcMap)
tmpl, err := t.Parse(string(data))
if err != nil {
return []byte{}, err
}
tmpl.Option("missingkey=error")
var doc bytes.Buffer
err = tmpl.Execute(&doc, tVars)
if err != nil {
return []byte{}, err
}
return doc.Bytes(), nil
}
return f, nil
}
func mkSlice(args ...any) []any {
return args
}
func readFile(f string) (string, error) {
b, err := os.ReadFile(f)
if err != nil {
return "", err
}
return strings.TrimSpace(string(b)), nil
}
func getEnv(key string, def ...string) string {
val := os.Getenv(key)
if val == "" && len(def) > 0 {
return def[0]
}
return os.Getenv(key)
}
func regexMatch(re, s string) (bool, error) {
compiled, err := regexp.Compile(re)
if err != nil {
return false, err
}
return compiled.MatchString(s), nil
}
// return named parenthesized subexpresions, if received, or stringfied (Sprig "get" need strings) keys like array
func findStringSubmatch(pattern, input string) map[string]interface{} {
re := regexp.MustCompile(pattern)
els := re.FindStringSubmatch(input)
elsMap := make(map[string]interface{})
elsMapNamed := make(map[string]interface{})
// create always elsMaps but returns elsMapNamed if exists named parenthesized subexps
for i := 0; i < len(els); i++ {
// convert i to string according returned (https://github.com/goss-org/goss/pull/895#issuecomment-2075716706)
elsMap[strconv.Itoa(i)] = els[i]
if re.SubexpNames()[i] != "" {
elsMapNamed[re.SubexpNames()[i]] = els[i]
}
}
// returns elsMapNamed if exists named parenthesized subexps
if len(elsMapNamed) > 0 {
return elsMapNamed
}
return elsMap
}
var funcMap = template.FuncMap{
"mkSlice": mkSlice,
"readFile": readFile,
"getEnv": getEnv,
"regexMatch": regexMatch,
"toUpper": strings.ToUpper,
"toLower": strings.ToLower,
"findStringSubmatch": findStringSubmatch,
}
goss-0.4.9/testdata/ 0000775 0000000 0000000 00000000000 14675050513 0014316 5 ustar 00root root 0000000 0000000 goss-0.4.9/testdata/failing.goss.yaml 0000664 0000000 0000000 00000000222 14675050513 0017561 0 ustar 00root root 0000000 0000000 ---
command:
hello world:
exit-status: 1
exec: "echo hello world"
stdout:
- did not echo this
stderr: []
timeout: 10000
goss-0.4.9/testdata/matching_basic.yaml 0000664 0000000 0000000 00000005022 14675050513 0020134 0 ustar 00root root 0000000 0000000 matching:
# Basic matchers
basic_string:
content: 'this is a test'
matches: 'this is a test'
basic_string_oneline:
content: |
this is a test1
matches: |
this is a test1
basic_string_multiline:
content: |
this is a test1
this is a test2
this is a test3
matches: |
this is a test1
this is a test2
this is a test3
basic_string_regexp:
content: 'this is a test'
matches:
match-regexp: '^this'
basic_string_skip:
skip: true
content: 'this is a test'
matches: 'this is a test'
basic_semver:
content: '1.2.3'
matches:
semver-constraint: '>=1.2.0'
basic_int:
content: 42
matches: 42
basic_array:
content:
- 'group1'
- 'group2'
- 'group3'
matches:
- 'group1'
- 'group2'
basic_array_matchers:
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
- contain-element:
have-suffix: r
basic_reader:
as-reader: true
content: |
foo bar
moo cow
matches:
- 'foo'
- '/^m.*w$/'
- '!ftw'
- '!/^ERROR:/'
# Negated
negated_basic_string:
content: 'this is a test'
matches:
not: 'this is a failing test'
negated_basic_string_regexp:
content: 'this is a test'
matches:
not:
match-regexp: '^foo'
negated_basic_int:
content: 42
matches:
not: 43
negated_basic_array:
content:
- 'group1'
- 'group2'
- 'group3'
matches:
not:
- 'group1'
- 'group2'
- 'group2'
- 'group4'
negated_basic_array_matchers:
content: [foo, bar, moo]
matches:
and:
- not:
contain-elements: [fox, box]
- not: [fox, bax] # same as above
- not:
equal: [fox, bax, mox] # order matters, exact match
- not:
consist-of: [have-suffix: x, have-prefix: t, box] # order doesn't matter, can use matchers
- not:
contain-element:
have-prefix: x
negated_basic_reader:
as-reader: true
content: |
foo bar
moo cow
matches:
not:
contain-elements:
- 'fox'
- '/^t.*w$/'
- '!foo'
- '!/^foo/'
goss-0.4.9/testdata/matching_basic_failing.yaml 0000664 0000000 0000000 00000007301 14675050513 0021627 0 ustar 00root root 0000000 0000000 matching:
# Basic matchers
basic_string:
content: 'this is a test'
matches: 'this is a failing test'
basic_string_oneline:
content: |
this is a test1
matches: |
this is a test9
basic_string_multiline:
content: |
this is a test1
this is a test2
this is a test3
matches: |
this is a test1
this is a test9
this is a test3
basic_string_have_prefix:
content: 'foo'
matches:
have-prefix: 'g'
basic_string_have_suffix:
content: 'foo'
matches:
have-suffix: 'x'
basic_string_contain_substring:
content: 'foo'
matches:
contain-substring: 'x'
basic_string_regexp:
content: 'this is a test'
matches:
match-regexp: '^foo'
basic_semver:
content: '1.2.3'
matches:
or:
- semver-constraint: '>=9.9.0'
basic_len:
content: "123"
matches:
or:
- have-len: 2
basic_int:
content: 42
matches: 43
basic_array:
content:
- 'group1'
- 'group2'
- 'group3'
matches:
- 'group1'
- 'group2'
- 'group2'
- 'group4'
basic_array_matchers:
content: [foo, bar, moo]
matches:
or:
- contain-elements: [fox, box]
- [fox, bax] # same as above
- equal: [fox, bax, mox] # order matters, exact match
- consist-of: [fox, have-prefix: t, box] # order doesn't matter, can use matchers
- contain-element:
have-prefix: x
- contain-element:
have-suffix: x
basic_array_consists_of:
content: [foo, bar, moo]
matches:
consist-of: [fox, have-prefix: t, box] # order doesn't matter, can use matchers
basic_reader:
as-reader: true
content: |
foo bar
moo cow
matches:
- 'fox'
- '/^t.*w$/'
- '!foo'
- '!/^foo/'
# Negated
negated_basic_string:
content: 'this is a test'
matches:
not: 'this is a test'
negatedbasic_string_regexp:
content: 'this is a test'
matches:
not:
match-regexp: '^this'
negatedbasic_string_have_prefix:
content: 'foo'
matches:
not:
have-prefix: 'f'
negatedbasic_string_have_suffix:
content: 'foo'
matches:
not:
have-suffix: 'o'
negatedbasic_string_contain_substring:
content: 'foo'
matches:
not:
contain-substring: 'oo'
negatedbasic_len:
content: '123'
matches:
not:
have-len: 3
negated_basic_int:
content: 42
matches:
not: 42
negated_and:
content: 42
matches:
not:
and:
- 42
- 42
negated_basic_array:
content:
- 'group1'
- 'group2'
- 'group3'
matches:
not:
- 'group1'
- 'group2'
- 'group3'
negated_basic_array_matchers:
content: [foo, bar, moo]
matches:
or:
- not:
contain-elements: [foo, bar]
- not:
[foo, bar] # same as above
- not:
equal: [foo, bar, moo] # order matters, exact match
- not:
consist-of: [foo, have-prefix: m, bar] # order doesn't matter, can use matchers
- not:
contain-element:
have-prefix: b
negated_basic_array_contain_element:
content: [foo, bar, moo]
matches:
not:
contain-element: foo
negated_basic_array_consists_of:
content: [foo, bar, moo]
matches:
not:
consist-of: [foo, have-prefix: m, bar] # order doesn't matter, can use matchers
negated_basic_reader:
as-reader: true
content: |
foo bar
moo cow
matches:
not:
- 'foo'
- '/^m.*w$/'
- '!ftw'
- '!/^ERROR:/'
goss-0.4.9/testdata/matching_transformers.yaml 0000664 0000000 0000000 00000004745 14675050513 0021613 0 ustar 00root root 0000000 0000000 matching:
basic_reader_as_array:
as-reader: true
content: |
foo bar
moo cow
matches:
and:
- contain-element: {contain-substring: 'foo'}
- contain-element: {match-regexp: '^m.*w$'}
- not: {contain-substring: 'ftw'}
- not: {match-regexp: '^ERROR:'}
test_numeric_string:
content: 128
matches:
and:
- '128'
- have-prefix: '1'
- have-suffix: '8'
- match-regexp: '\d{3}'
test_string_numeric:
content: '128'
matches:
and:
- 128
- 128.0
- le: 128
- gt: 120
test_string_float:
content: '128.3'
matches:
and:
- 128.3
- le: 129
- gt: 120.2
test_array:
content:
- '45'
- '46'
- '47'
matches:
- contain-element: {match-regexp: "4."}
- '45'
- and: [{ge: 46}, {le: 50}]
test_reader_using_string_matchers:
content: |
foo bar
15
moo cow
as-reader: true
matches:
and:
- have-len: 19
- |
foo bar
15
moo cow
- have-prefix: 'foo'
- have-suffix: "cow\n"
- contain-element:
have-prefix: 'moo'
- contain-elements:
- not: 'this_doesnt_exist'
- lt: 20
- have-prefix: 'moo'
test_reader_using_array:
content: |
foo bar
15
moo cow
as-reader: true
matches:
- "foo bar"
- "15"
- "moo cow"
test_reader_as_single_string:
content: 'cool'
as-reader: true
matches: 'cool'
test_reader_using_int_matchers:
content: '40'
as-reader: true
matches:
and:
- le: 250
- ge: 20
test_gjson_transform:
content: '{"foo": "bar", "moo": {"nested": "cow"}, "count": "15"}'
as-reader: true
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"}}
test_gjson_using_this_and_equal:
content: '{"foo": "bar", "baz": "bing"}'
matches:
gjson:
'@this':
equal:
foo: bar
baz: bing
test_gjson_have_key_array:
content: '{"arr": [{"nested": "cow"}, {"nested2": "moo"}]}'
matches:
gjson:
arr:
# or tests MarshalJSON
or:
- contain-elements:
- have-key: 'nested'
goss-0.4.9/testdata/matching_transformers_failing.yaml 0000664 0000000 0000000 00000006467 14675050513 0023307 0 ustar 00root root 0000000 0000000 matching:
basic_reader_as_array:
as-reader: true
content: |
foo bar
moo cow
matches:
and:
- contain-element: {contain-substring: 'fox'}
- contain-element: {match-regexp: '^t.*w$'}
- not: {contain-substring: 'foo'}
- not: {match-regexp: '^foo'}
test_numeric_string:
content: 128
matches:
and:
- '129'
- have-prefix: '2'
- have-suffix: '9'
- match-regexp: '\s{3}'
test_string_numeric:
content: '128'
matches:
and:
- 129
- 129.1
- le: 127
- gt: 130
test_string_float:
content: '128.3'
matches:
and:
- 129.3
- le: 127
- gt: 130.2
test_array:
content:
- '45'
- '46'
- '47'
matches:
- contain-element: {match-regexp: "5."}
- '55'
- and: [{ge: 56}, {le: 30}]
test_reader_using_string_matchers:
content: |
foo bar
15
moo cow
as-reader: true
matches:
and:
- have-len: 15
- |
fox bar
15
moo cow
- have-prefix: 'fox'
- have-suffix: "tow\n"
- contain-element:
have-prefix: 'too'
- contain-elements:
- not: 'moo cow'
- lt: 10
- have-prefix: 'tow'
test_reader_as_single_string:
content: 'cool'
as-reader: true
matches: 'not-cool'
test_reader_using_int_matchers:
content: '40'
as-reader: true
matches:
and:
- le: 20
- ge: 50
test_gjson_transform_simple:
content: '{"foo": "bar", "moo": {"nested": "cow"}, "count": "15"}'
as-reader: true
matches:
gjson:
moo.nested: cowx
test_gjson_transform_nested_prefix:
content: '{"foo": "bar", "moo": {"nested": "cow"}, "count": "15"}'
as-reader: true
matches:
gjson:
foo: {have-prefix: x}
test_gjson_transform_nested_count:
content: '{"foo": "bar", "moo": {"nested": "cow"}, "count": "15"}'
as-reader: true
matches:
gjson:
count: {le: 10}
test_gjson_transform_nested_this:
content: '{"foo": "bar", "moo": {"nested": "cow"}, "count": "15"}'
as-reader: true
matches:
gjson:
'@this': {have-key: "nope"}
test_gjson_transform_nested_and:
content: '{"foo": "bar", "moo": {"nested": "cow"}, "count": "15"}'
as-reader: true
matches:
gjson:
moo:
and:
- {have-key: "nope"}
- {not: {have-key: "nested"}}
test_gjson_transform_not_key:
content: '{"foo": "bar", "moo": {"nested": "cow"}, "count": "15"}'
as-reader: true
matches:
gjson:
moo:
not:
have-key: "nested"
test_gjson_using_this_and_equal:
content: '{"foo": "bar", "baz": "bing"}'
matches:
gjson:
'@this':
equal:
fox: bar
baz: bing
test_gjson_have_key_array:
content: '{"arr": [{"nested": "cow"}, {"nested2": "moo"}]}'
matches:
gjson:
'@this':
or:
- have-key: "fail"
test_gjson_not_found:
content: '{"arr": [{"nested": "cow"}, {"nested2": "moo"}]}'
matches:
gjson:
foo: 'bar'
test_gjson_invalid:
content: '{"arr"'
matches:
gjson:
'@this':
- have-key: "arr"
goss-0.4.9/testdata/out_matching_basic.0.documentation 0000664 0000000 0000000 00000003565 14675050513 0023102 0 ustar 00root root 0000000 0000000 Matching: basic_array: matches: matches expectation: ["group1","group2"]
Matching: basic_array_matchers: matches: matches expectation: {"and":[{"contain-elements":["foo","bar"]},["foo","bar"],{"equal":["foo","bar","moo"]},{"consist-of":["foo",{"have-prefix":"m"},"bar"]},{"contain-element":{"have-prefix":"b"}},{"contain-element":{"have-suffix":"r"}}]}
Matching: basic_int: matches: matches expectation: 42
Matching: basic_reader: matches: matches expectation: ["foo","/^m.*w$/","!ftw","!/^ERROR:/"]
Matching: basic_semver: matches: matches expectation: {"semver-constraint":">=1.2.0"}
Matching: basic_string: matches: matches expectation: "this is a test"
Matching: basic_string_multiline: matches: matches expectation: "this is a test1\nthis is a test2\nthis is a test3\n"
Matching: basic_string_oneline: matches: matches expectation: "this is a test1\n"
Matching: basic_string_regexp: matches: matches expectation: {"match-regexp":"^this"}
Matching: basic_string_skip: matches: skipped
Matching: negated_basic_array: matches: matches expectation: {"not":["group1","group2","group2","group4"]}
Matching: negated_basic_array_matchers: matches: matches expectation: {"and":[{"not":{"contain-elements":["fox","box"]}},{"not":["fox","bax"]},{"not":{"equal":["fox","bax","mox"]}},{"not":{"consist-of":[{"have-suffix":"x"},{"have-prefix":"t"},"box"]}},{"not":{"contain-element":{"have-prefix":"x"}}}]}
Matching: negated_basic_int: matches: matches expectation: {"not":43}
Matching: negated_basic_reader: matches: matches expectation: {"not":{"contain-elements":["fox","/^t.*w$/","!foo","!/^foo/"]}}
Matching: negated_basic_string: matches: matches expectation: {"not":"this is a failing test"}
Matching: negated_basic_string_regexp: matches: matches expectation: {"not":{"match-regexp":"^foo"}}
Failures/Skipped:
Matching: basic_string_skip: matches: skipped
Total Duration:
Count: 16, Failed: 0, Skipped: 1
goss-0.4.9/testdata/out_matching_basic.0.nagios 0000664 0000000 0000000 00000000067 14675050513 0021503 0 ustar 00root root 0000000 0000000 GOSS OK - Count: 16, Failed: 0, Skipped: 1, Duration:
goss-0.4.9/testdata/out_matching_basic.0.rspecish 0000664 0000000 0000000 00000000206 14675050513 0022036 0 ustar 00root root 0000000 0000000 .........S......
Failures/Skipped:
Matching: basic_string_skip: matches: skipped
Total Duration:
Count: 16, Failed: 0, Skipped: 1
goss-0.4.9/testdata/out_matching_basic.0.tap 0000664 0000000 0000000 00000003603 14675050513 0021006 0 ustar 00root root 0000000 0000000 1..16
ok 1 - Matching: basic_array: matches: matches expectation: ["group1","group2"]
ok 2 - Matching: basic_array_matchers: matches: matches expectation: {"and":[{"contain-elements":["foo","bar"]},["foo","bar"],{"equal":["foo","bar","moo"]},{"consist-of":["foo",{"have-prefix":"m"},"bar"]},{"contain-element":{"have-prefix":"b"}},{"contain-element":{"have-suffix":"r"}}]}
ok 3 - Matching: basic_int: matches: matches expectation: 42
ok 4 - Matching: basic_reader: matches: matches expectation: ["foo","/^m.*w$/","!ftw","!/^ERROR:/"]
ok 5 - Matching: basic_semver: matches: matches expectation: {"semver-constraint":">=1.2.0"}
ok 6 - Matching: basic_string: matches: matches expectation: "this is a test"
ok 7 - Matching: basic_string_multiline: matches: matches expectation: "this is a test1\nthis is a test2\nthis is a test3\n"
ok 8 - Matching: basic_string_oneline: matches: matches expectation: "this is a test1\n"
ok 9 - Matching: basic_string_regexp: matches: matches expectation: {"match-regexp":"^this"}
ok 10 - # SKIP Matching: basic_string_skip: matches: skipped
ok 11 - Matching: negated_basic_array: matches: matches expectation: {"not":["group1","group2","group2","group4"]}
ok 12 - Matching: negated_basic_array_matchers: matches: matches expectation: {"and":[{"not":{"contain-elements":["fox","box"]}},{"not":["fox","bax"]},{"not":{"equal":["fox","bax","mox"]}},{"not":{"consist-of":[{"have-suffix":"x"},{"have-prefix":"t"},"box"]}},{"not":{"contain-element":{"have-prefix":"x"}}}]}
ok 13 - Matching: negated_basic_int: matches: matches expectation: {"not":43}
ok 14 - Matching: negated_basic_reader: matches: matches expectation: {"not":{"contain-elements":["fox","/^t.*w$/","!foo","!/^foo/"]}}
ok 15 - Matching: negated_basic_string: matches: matches expectation: {"not":"this is a failing test"}
ok 16 - Matching: negated_basic_string_regexp: matches: matches expectation: {"not":{"match-regexp":"^foo"}}
goss-0.4.9/testdata/out_matching_basic_failing.1.documentation 0000664 0000000 0000000 00000022373 14675050513 0024572 0 ustar 00root root 0000000 0000000 Matching: basic_array: matches:
Expected
["group1","group2","group3"]
to contain elements matching
["group1","group2","group2","group4"]
the missing elements were
["group2","group4"]
Matching: basic_array_consists_of: matches:
Expected
["foo","bar","moo"]
to consist of
["fox",{"have-prefix":"t"},"box"]
the missing elements were
["fox",{"have-prefix":"t"},"box"]
the extra elements were
["foo","bar","moo"]
Matching: basic_array_matchers: matches:
Expected
["foo","bar","moo"]
to satisfy at least one of these matchers
[{"contain-elements":["fox","box"]},{"contain-elements":["fox","bax"]},["fox","bax","mox"],{"consist-of":["fox",{"have-prefix":"t"},"box"]},{"contain-element":{"have-prefix":"x"}},{"contain-element":{"have-suffix":"x"}}]
Matching: basic_int: matches:
Expected
42
to be numerically eq
43
Matching: basic_len: matches:
Expected
"123"
to satisfy at least one of these matchers
[{"have-len":2}]
Matching: basic_reader: matches:
Expected
"object: *strings.Reader"
to have patterns
["fox","/^t.*w$/","!foo","!/^foo/"]
the missing elements were
["fox","/^t.*w$/","!foo","!/^foo/"]
Matching: basic_semver: matches:
Expected
"1.2.3"
to satisfy at least one of these matchers
[{"semver-constraint":">=9.9.0"}]
Matching: basic_string: matches:
Expected
"this is a test"
to equal
"this is a failing test"
diff
--- test
+++ actual
@@ -1 +1 @@
-this is a failing test
+this is a test
Matching: basic_string_contain_substring: matches:
Expected
"foo"
to contain substring
"x"
diff
--- test
+++ actual
@@ -1 +1 @@
-x
+foo
Matching: basic_string_have_prefix: matches:
Expected
"foo"
to have prefix
"g"
diff
--- test
+++ actual
@@ -1 +1 @@
-g
+foo
Matching: basic_string_have_suffix: matches:
Expected
"foo"
to have suffix
"x"
diff
--- test
+++ actual
@@ -1 +1 @@
-x
+foo
Matching: basic_string_multiline: matches:
Expected
"this is a test1\nthis is a test2\nthis is a test3\n"
to equal
"this is a test1\nthis is a test9\nthis is a test3\n"
diff
--- test
+++ actual
@@ -1,3 +1,3 @@
this is a test1
-this is a test9
+this is a test2
this is a test3
Matching: basic_string_oneline: matches:
Expected
"this is a test1\n"
to equal
"this is a test9\n"
diff
--- test
+++ actual
@@ -1,2 +1,2 @@
-this is a test9
+this is a test1
Matching: basic_string_regexp: matches:
Expected
"this is a test"
to match regular expression
"^foo"
diff
--- test
+++ actual
@@ -1 +1 @@
-^foo
+this is a test
Matching: negated_and: matches:
Expected
42
not to satisfy all of these matchers
[{"eq":42},{"eq":42}]
Matching: negated_basic_array: matches:
Expected
["group1","group2","group3"]
not to contain elements matching
["group1","group2","group3"]
Matching: negated_basic_array_consists_of: matches:
Expected
["foo","bar","moo"]
not to consist of
["foo",{"have-prefix":"m"},"bar"]
Matching: negated_basic_array_contain_element: matches:
Expected
["foo","bar","moo"]
not to contain element matching
"foo"
Matching: negated_basic_array_matchers: matches:
Expected
["foo","bar","moo"]
to satisfy at least one of these matchers
[{"not":{"contain-elements":["foo","bar"]}},{"not":{"contain-elements":["foo","bar"]}},{"not":["foo","bar","moo"]},{"not":{"consist-of":["foo",{"have-prefix":"m"},"bar"]}},{"not":{"contain-element":{"have-prefix":"b"}}}]
Matching: negated_basic_int: matches:
Expected
42
not to be numerically eq
42
Matching: negated_basic_reader: matches:
Error
ContainElements matcher expects an array/slice/map. Got:
: foo bar
moo cow
Matching: negated_basic_string: matches:
Expected
"this is a test"
not to equal
"this is a test"
Matching: negatedbasic_len: matches:
Expected
"123"
not to have length
3
Matching: negatedbasic_string_contain_substring: matches:
Expected
"foo"
not to contain substring
"oo"
diff
--- test
+++ actual
@@ -1 +1 @@
-oo
+foo
Matching: negatedbasic_string_have_prefix: matches:
Expected
"foo"
not to have prefix
"f"
diff
--- test
+++ actual
@@ -1 +1 @@
-f
+foo
Matching: negatedbasic_string_have_suffix: matches:
Expected
"foo"
not to have suffix
"o"
diff
--- test
+++ actual
@@ -1 +1 @@
-o
+foo
Matching: negatedbasic_string_regexp: matches:
Expected
"this is a test"
not to match regular expression
"^this"
diff
--- test
+++ actual
@@ -1 +1 @@
-^this
+this is a test
Failures/Skipped:
Matching: basic_array: matches:
Expected
["group1","group2","group3"]
to contain elements matching
["group1","group2","group2","group4"]
the missing elements were
["group2","group4"]
Matching: basic_array_consists_of: matches:
Expected
["foo","bar","moo"]
to consist of
["fox",{"have-prefix":"t"},"box"]
the missing elements were
["fox",{"have-prefix":"t"},"box"]
the extra elements were
["foo","bar","moo"]
Matching: basic_array_matchers: matches:
Expected
["foo","bar","moo"]
to satisfy at least one of these matchers
[{"contain-elements":["fox","box"]},{"contain-elements":["fox","bax"]},["fox","bax","mox"],{"consist-of":["fox",{"have-prefix":"t"},"box"]},{"contain-element":{"have-prefix":"x"}},{"contain-element":{"have-suffix":"x"}}]
Matching: basic_int: matches:
Expected
42
to be numerically eq
43
Matching: basic_len: matches:
Expected
"123"
to satisfy at least one of these matchers
[{"have-len":2}]
Matching: basic_reader: matches:
Expected
"object: *strings.Reader"
to have patterns
["fox","/^t.*w$/","!foo","!/^foo/"]
the missing elements were
["fox","/^t.*w$/","!foo","!/^foo/"]
Matching: basic_semver: matches:
Expected
"1.2.3"
to satisfy at least one of these matchers
[{"semver-constraint":">=9.9.0"}]
Matching: basic_string: matches:
Expected
"this is a test"
to equal
"this is a failing test"
diff
--- test
+++ actual
@@ -1 +1 @@
-this is a failing test
+this is a test
Matching: basic_string_contain_substring: matches:
Expected
"foo"
to contain substring
"x"
diff
--- test
+++ actual
@@ -1 +1 @@
-x
+foo
Matching: basic_string_have_prefix: matches:
Expected
"foo"
to have prefix
"g"
diff
--- test
+++ actual
@@ -1 +1 @@
-g
+foo
Matching: basic_string_have_suffix: matches:
Expected
"foo"
to have suffix
"x"
diff
--- test
+++ actual
@@ -1 +1 @@
-x
+foo
Matching: basic_string_multiline: matches:
Expected
"this is a test1\nthis is a test2\nthis is a test3\n"
to equal
"this is a test1\nthis is a test9\nthis is a test3\n"
diff
--- test
+++ actual
@@ -1,3 +1,3 @@
this is a test1
-this is a test9
+this is a test2
this is a test3
Matching: basic_string_oneline: matches:
Expected
"this is a test1\n"
to equal
"this is a test9\n"
diff
--- test
+++ actual
@@ -1,2 +1,2 @@
-this is a test9
+this is a test1
Matching: basic_string_regexp: matches:
Expected
"this is a test"
to match regular expression
"^foo"
diff
--- test
+++ actual
@@ -1 +1 @@
-^foo
+this is a test
Matching: negated_and: matches:
Expected
42
not to satisfy all of these matchers
[{"eq":42},{"eq":42}]
Matching: negated_basic_array: matches:
Expected
["group1","group2","group3"]
not to contain elements matching
["group1","group2","group3"]
Matching: negated_basic_array_consists_of: matches:
Expected
["foo","bar","moo"]
not to consist of
["foo",{"have-prefix":"m"},"bar"]
Matching: negated_basic_array_contain_element: matches:
Expected
["foo","bar","moo"]
not to contain element matching
"foo"
Matching: negated_basic_array_matchers: matches:
Expected
["foo","bar","moo"]
to satisfy at least one of these matchers
[{"not":{"contain-elements":["foo","bar"]}},{"not":{"contain-elements":["foo","bar"]}},{"not":["foo","bar","moo"]},{"not":{"consist-of":["foo",{"have-prefix":"m"},"bar"]}},{"not":{"contain-element":{"have-prefix":"b"}}}]
Matching: negated_basic_int: matches:
Expected
42
not to be numerically eq
42
Matching: negated_basic_reader: matches:
Error
ContainElements matcher expects an array/slice/map. Got:
: foo bar
moo cow
Matching: negated_basic_string: matches:
Expected
"this is a test"
not to equal
"this is a test"
Matching: negatedbasic_len: matches:
Expected
"123"
not to have length
3
Matching: negatedbasic_string_contain_substring: matches:
Expected
"foo"
not to contain substring
"oo"
diff
--- test
+++ actual
@@ -1 +1 @@
-oo
+foo
Matching: negatedbasic_string_have_prefix: matches:
Expected
"foo"
not to have prefix
"f"
diff
--- test
+++ actual
@@ -1 +1 @@
-f
+foo
Matching: negatedbasic_string_have_suffix: matches:
Expected
"foo"
not to have suffix
"o"
diff
--- test
+++ actual
@@ -1 +1 @@
-o
+foo
Matching: negatedbasic_string_regexp: matches:
Expected
"this is a test"
not to match regular expression
"^this"
diff
--- test
+++ actual
@@ -1 +1 @@
-^this
+this is a test
Total Duration:
Count: 27, Failed: 27, Skipped: 0
goss-0.4.9/testdata/out_matching_basic_failing.1.rspecish 0000664 0000000 0000000 00000011312 14675050513 0023530 0 ustar 00root root 0000000 0000000 FFFFFFFFFFFFFFFFFFFFFFFFFFF
Failures/Skipped:
Matching: basic_array: matches:
Expected
["group1","group2","group3"]
to contain elements matching
["group1","group2","group2","group4"]
the missing elements were
["group2","group4"]
Matching: basic_array_consists_of: matches:
Expected
["foo","bar","moo"]
to consist of
["fox",{"have-prefix":"t"},"box"]
the missing elements were
["fox",{"have-prefix":"t"},"box"]
the extra elements were
["foo","bar","moo"]
Matching: basic_array_matchers: matches:
Expected
["foo","bar","moo"]
to satisfy at least one of these matchers
[{"contain-elements":["fox","box"]},{"contain-elements":["fox","bax"]},["fox","bax","mox"],{"consist-of":["fox",{"have-prefix":"t"},"box"]},{"contain-element":{"have-prefix":"x"}},{"contain-element":{"have-suffix":"x"}}]
Matching: basic_int: matches:
Expected
42
to be numerically eq
43
Matching: basic_len: matches:
Expected
"123"
to satisfy at least one of these matchers
[{"have-len":2}]
Matching: basic_reader: matches:
Expected
"object: *strings.Reader"
to have patterns
["fox","/^t.*w$/","!foo","!/^foo/"]
the missing elements were
["fox","/^t.*w$/","!foo","!/^foo/"]
Matching: basic_semver: matches:
Expected
"1.2.3"
to satisfy at least one of these matchers
[{"semver-constraint":">=9.9.0"}]
Matching: basic_string: matches:
Expected
"this is a test"
to equal
"this is a failing test"
diff
--- test
+++ actual
@@ -1 +1 @@
-this is a failing test
+this is a test
Matching: basic_string_contain_substring: matches:
Expected
"foo"
to contain substring
"x"
diff
--- test
+++ actual
@@ -1 +1 @@
-x
+foo
Matching: basic_string_have_prefix: matches:
Expected
"foo"
to have prefix
"g"
diff
--- test
+++ actual
@@ -1 +1 @@
-g
+foo
Matching: basic_string_have_suffix: matches:
Expected
"foo"
to have suffix
"x"
diff
--- test
+++ actual
@@ -1 +1 @@
-x
+foo
Matching: basic_string_multiline: matches:
Expected
"this is a test1\nthis is a test2\nthis is a test3\n"
to equal
"this is a test1\nthis is a test9\nthis is a test3\n"
diff
--- test
+++ actual
@@ -1,3 +1,3 @@
this is a test1
-this is a test9
+this is a test2
this is a test3
Matching: basic_string_oneline: matches:
Expected
"this is a test1\n"
to equal
"this is a test9\n"
diff
--- test
+++ actual
@@ -1,2 +1,2 @@
-this is a test9
+this is a test1
Matching: basic_string_regexp: matches:
Expected
"this is a test"
to match regular expression
"^foo"
diff
--- test
+++ actual
@@ -1 +1 @@
-^foo
+this is a test
Matching: negated_and: matches:
Expected
42
not to satisfy all of these matchers
[{"eq":42},{"eq":42}]
Matching: negated_basic_array: matches:
Expected
["group1","group2","group3"]
not to contain elements matching
["group1","group2","group3"]
Matching: negated_basic_array_consists_of: matches:
Expected
["foo","bar","moo"]
not to consist of
["foo",{"have-prefix":"m"},"bar"]
Matching: negated_basic_array_contain_element: matches:
Expected
["foo","bar","moo"]
not to contain element matching
"foo"
Matching: negated_basic_array_matchers: matches:
Expected
["foo","bar","moo"]
to satisfy at least one of these matchers
[{"not":{"contain-elements":["foo","bar"]}},{"not":{"contain-elements":["foo","bar"]}},{"not":["foo","bar","moo"]},{"not":{"consist-of":["foo",{"have-prefix":"m"},"bar"]}},{"not":{"contain-element":{"have-prefix":"b"}}}]
Matching: negated_basic_int: matches:
Expected
42
not to be numerically eq
42
Matching: negated_basic_reader: matches:
Error
ContainElements matcher expects an array/slice/map. Got:
: foo bar
moo cow
Matching: negated_basic_string: matches:
Expected
"this is a test"
not to equal
"this is a test"
Matching: negatedbasic_len: matches:
Expected
"123"
not to have length
3
Matching: negatedbasic_string_contain_substring: matches:
Expected
"foo"
not to contain substring
"oo"
diff
--- test
+++ actual
@@ -1 +1 @@
-oo
+foo
Matching: negatedbasic_string_have_prefix: matches:
Expected
"foo"
not to have prefix
"f"
diff
--- test
+++ actual
@@ -1 +1 @@
-f
+foo
Matching: negatedbasic_string_have_suffix: matches:
Expected
"foo"
not to have suffix
"o"
diff
--- test
+++ actual
@@ -1 +1 @@
-o
+foo
Matching: negatedbasic_string_regexp: matches:
Expected
"this is a test"
not to match regular expression
"^this"
diff
--- test
+++ actual
@@ -1 +1 @@
-^this
+this is a test
Total Duration:
Count: 27, Failed: 27, Skipped: 0
goss-0.4.9/testdata/out_matching_basic_failing.1.tap 0000664 0000000 0000000 00000007433 14675050513 0022505 0 ustar 00root root 0000000 0000000 1..27
not ok 1 - Matching: basic_array: matches: Expected ["group1","group2","group3"] to contain elements matching ["group1","group2","group2","group4"] the missing elements were ["group2","group4"]
not ok 2 - Matching: basic_array_consists_of: matches: Expected ["foo","bar","moo"] to consist of ["fox",{"have-prefix":"t"},"box"] the missing elements were ["fox",{"have-prefix":"t"},"box"] the extra elements were ["foo","bar","moo"]
not ok 3 - Matching: basic_array_matchers: matches: Expected ["foo","bar","moo"] to satisfy at least one of these matchers [{"contain-elements":["fox","box"]},{"contain-elements":["fox","bax"]},["fox","bax","mox"],{"consist-of":["fox",{"have-prefix":"t"},"box"]},{"contain-element":{"have-prefix":"x"}},{"contain-element":{"have-suffix":"x"}}]
not ok 4 - Matching: basic_int: matches: Expected 42 to be numerically eq 43
not ok 5 - Matching: basic_len: matches: Expected "123" to satisfy at least one of these matchers [{"have-len":2}]
not ok 6 - Matching: basic_reader: matches: Expected "object: *strings.Reader" to have patterns ["fox","/^t.*w$/","!foo","!/^foo/"] the missing elements were ["fox","/^t.*w$/","!foo","!/^foo/"]
not ok 7 - Matching: basic_semver: matches: Expected "1.2.3" to satisfy at least one of these matchers [{"semver-constraint":">=9.9.0"}]
not ok 8 - Matching: basic_string: matches: Expected "this is a test" to equal "this is a failing test"
not ok 9 - Matching: basic_string_contain_substring: matches: Expected "foo" to contain substring "x"
not ok 10 - Matching: basic_string_have_prefix: matches: Expected "foo" to have prefix "g"
not ok 11 - Matching: basic_string_have_suffix: matches: Expected "foo" to have suffix "x"
not ok 12 - Matching: basic_string_multiline: matches: Expected "this is a test1\nthis is a test2\nthis is a test3\n" to equal "this is a test1\nthis is a test9\nthis is a test3\n"
not ok 13 - Matching: basic_string_oneline: matches: Expected "this is a test1\n" to equal "this is a test9\n"
not ok 14 - Matching: basic_string_regexp: matches: Expected "this is a test" to match regular expression "^foo"
not ok 15 - Matching: negated_and: matches: Expected 42 not to satisfy all of these matchers [{"eq":42},{"eq":42}]
not ok 16 - Matching: negated_basic_array: matches: Expected ["group1","group2","group3"] not to contain elements matching ["group1","group2","group3"]
not ok 17 - Matching: negated_basic_array_consists_of: matches: Expected ["foo","bar","moo"] not to consist of ["foo",{"have-prefix":"m"},"bar"]
not ok 18 - Matching: negated_basic_array_contain_element: matches: Expected ["foo","bar","moo"] not to contain element matching "foo"
not ok 19 - Matching: negated_basic_array_matchers: matches: Expected ["foo","bar","moo"] to satisfy at least one of these matchers [{"not":{"contain-elements":["foo","bar"]}},{"not":{"contain-elements":["foo","bar"]}},{"not":["foo","bar","moo"]},{"not":{"consist-of":["foo",{"have-prefix":"m"},"bar"]}},{"not":{"contain-element":{"have-prefix":"b"}}}]
not ok 20 - Matching: negated_basic_int: matches: Expected 42 not to be numerically eq 42
not ok 21 - Matching: negated_basic_reader: matches: Error ContainElements matcher expects an array/slice/map. Got: : foo bar moo cow
not ok 22 - Matching: negated_basic_string: matches: Expected "this is a test" not to equal "this is a test"
not ok 23 - Matching: negatedbasic_len: matches: Expected "123" not to have length 3
not ok 24 - Matching: negatedbasic_string_contain_substring: matches: Expected "foo" not to contain substring "oo"
not ok 25 - Matching: negatedbasic_string_have_prefix: matches: Expected "foo" not to have prefix "f"
not ok 26 - Matching: negatedbasic_string_have_suffix: matches: Expected "foo" not to have suffix "o"
not ok 27 - Matching: negatedbasic_string_regexp: matches: Expected "this is a test" not to match regular expression "^this"
goss-0.4.9/testdata/out_matching_basic_failing.2.nagios 0000664 0000000 0000000 00000000076 14675050513 0023176 0 ustar 00root root 0000000 0000000 GOSS CRITICAL - Count: 27, Failed: 27, Skipped: 0, Duration:
goss-0.4.9/testdata/out_matching_transformers.0.documentation 0000664 0000000 0000000 00000003453 14675050513 0024542 0 ustar 00root root 0000000 0000000 Matching: basic_reader_as_array: matches: matches expectation: {"and":[{"contain-element":{"contain-substring":"foo"}},{"contain-element":{"match-regexp":"^m.*w$"}},{"not":{"contain-substring":"ftw"}},{"not":{"match-regexp":"^ERROR:"}}]}
Matching: test_array: matches: matches expectation: [{"contain-element":{"match-regexp":"4."}},"45",{"and":[{"ge":46},{"le":50}]}]
Matching: test_gjson_have_key_array: matches: matches expectation: {"gjson":{"arr":{"or":[{"contain-elements":[{"have-key":"nested"}]}]}}}
Matching: test_gjson_transform: matches: matches expectation: {"gjson":{"@this":{"have-key":"foo"},"count":{"le":25},"foo":{"have-prefix":"b"},"moo":{"and":[{"have-key":"nested"},{"not":{"have-key":"nested2"}}]},"moo.nested":"cow"}}
Matching: test_gjson_using_this_and_equal: matches: matches expectation: {"gjson":{"@this":{"equal":{"baz":"bing","foo":"bar"}}}}
Matching: test_numeric_string: matches: matches expectation: {"and":["128",{"have-prefix":"1"},{"have-suffix":"8"},{"match-regexp":"\\d{3}"}]}
Matching: test_reader_as_single_string: matches: matches expectation: "cool"
Matching: test_reader_using_array: matches: matches expectation: ["foo bar","15","moo cow"]
Matching: test_reader_using_int_matchers: matches: matches expectation: {"and":[{"le":250},{"ge":20}]}
Matching: test_reader_using_string_matchers: matches: matches expectation: {"and":[{"have-len":19},"foo bar\n15\nmoo cow\n",{"have-prefix":"foo"},{"have-suffix":"cow\n"},{"contain-element":{"have-prefix":"moo"}},{"contain-elements":[{"not":"this_doesnt_exist"},{"lt":20},{"have-prefix":"moo"}]}]}
Matching: test_string_float: matches: matches expectation: {"and":[128.3,{"le":129},{"gt":120.2}]}
Matching: test_string_numeric: matches: matches expectation: {"and":[128,128,{"le":128},{"gt":120}]}
Total Duration:
Count: 12, Failed: 0, Skipped: 0
goss-0.4.9/testdata/out_matching_transformers.0.nagios 0000664 0000000 0000000 00000000067 14675050513 0023147 0 ustar 00root root 0000000 0000000 GOSS OK - Count: 12, Failed: 0, Skipped: 0, Duration:
goss-0.4.9/testdata/out_matching_transformers.0.rspecish 0000664 0000000 0000000 00000000100 14675050513 0023473 0 ustar 00root root 0000000 0000000 ............
Total Duration:
Count: 12, Failed: 0, Skipped: 0
goss-0.4.9/testdata/out_matching_transformers.0.tap 0000664 0000000 0000000 00000003524 14675050513 0022454 0 ustar 00root root 0000000 0000000 1..12
ok 1 - Matching: basic_reader_as_array: matches: matches expectation: {"and":[{"contain-element":{"contain-substring":"foo"}},{"contain-element":{"match-regexp":"^m.*w$"}},{"not":{"contain-substring":"ftw"}},{"not":{"match-regexp":"^ERROR:"}}]}
ok 2 - Matching: test_array: matches: matches expectation: [{"contain-element":{"match-regexp":"4."}},"45",{"and":[{"ge":46},{"le":50}]}]
ok 3 - Matching: test_gjson_have_key_array: matches: matches expectation: {"gjson":{"arr":{"or":[{"contain-elements":[{"have-key":"nested"}]}]}}}
ok 4 - Matching: test_gjson_transform: matches: matches expectation: {"gjson":{"@this":{"have-key":"foo"},"count":{"le":25},"foo":{"have-prefix":"b"},"moo":{"and":[{"have-key":"nested"},{"not":{"have-key":"nested2"}}]},"moo.nested":"cow"}}
ok 5 - Matching: test_gjson_using_this_and_equal: matches: matches expectation: {"gjson":{"@this":{"equal":{"baz":"bing","foo":"bar"}}}}
ok 6 - Matching: test_numeric_string: matches: matches expectation: {"and":["128",{"have-prefix":"1"},{"have-suffix":"8"},{"match-regexp":"\\d{3}"}]}
ok 7 - Matching: test_reader_as_single_string: matches: matches expectation: "cool"
ok 8 - Matching: test_reader_using_array: matches: matches expectation: ["foo bar","15","moo cow"]
ok 9 - Matching: test_reader_using_int_matchers: matches: matches expectation: {"and":[{"le":250},{"ge":20}]}
ok 10 - Matching: test_reader_using_string_matchers: matches: matches expectation: {"and":[{"have-len":19},"foo bar\n15\nmoo cow\n",{"have-prefix":"foo"},{"have-suffix":"cow\n"},{"contain-element":{"have-prefix":"moo"}},{"contain-elements":[{"not":"this_doesnt_exist"},{"lt":20},{"have-prefix":"moo"}]}]}
ok 11 - Matching: test_string_float: matches: matches expectation: {"and":[128.3,{"le":129},{"gt":120.2}]}
ok 12 - Matching: test_string_numeric: matches: matches expectation: {"and":[128,128,{"le":128},{"gt":120}]}
goss-0.4.9/testdata/out_matching_transformers_failing.1.documentation 0000664 0000000 0000000 00000020466 14675050513 0026237 0 ustar 00root root 0000000 0000000 Matching: basic_reader_as_array: matches:
Expected
["foo bar","moo cow",""]
to contain element matching
{"contain-substring":"fox"}
the transform chain was
[{"to-array":{}}]
the raw value was
"foo bar\nmoo cow\n"
Matching: test_array: matches:
Expected
["45","46","47"]
to contain elements matching
[{"contain-element":{"match-regexp":"5."}},"55",{"and":[{"ge":56},{"le":30}]}]
the missing elements were
[{"contain-element":{"match-regexp":"5."}},"55",{"and":[{"ge":56},{"le":30}]}]
Matching: test_gjson_have_key_array: matches:
Expected
{"arr":[{"nested":"cow"},{"nested2":"moo"}]}
to satisfy at least one of these matchers
[{"have-key":"fail"}]
the transform chain was
[{"gjson":{"Path":"@this"}}]
the raw value was
"{\"arr\": [{\"nested\": \"cow\"}, {\"nested2\": \"moo\"}]}"
Matching: test_gjson_invalid: matches:
Error
matchers.Gjson{Path:"@this"}: Invalid json
the transform chain was
[{"gjson":{"Path":"@this"}}]
the raw value was
"{\"arr\""
Matching: test_gjson_not_found: matches:
Error
matchers.Gjson{Path:"foo"}: Path not found: foo
the transform chain was
[{"gjson":{"Path":"foo"}}]
the raw value was
"{\"arr\": [{\"nested\": \"cow\"}, {\"nested2\": \"moo\"}]}"
Matching: test_gjson_transform_nested_and: matches:
Expected
{"nested":"cow"}
to have key matching
"nope"
the transform chain was
[{"gjson":{"Path":"moo"}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_transform_nested_count: matches:
Expected
15
to be numerically le
10
the transform chain was
[{"gjson":{"Path":"count"}},{"to-numeric":{}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_transform_nested_prefix: matches:
Expected
"bar"
to have prefix
"x"
diff
--- test
+++ actual
@@ -1 +1 @@
-x
+bar
the transform chain was
[{"gjson":{"Path":"foo"}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_transform_nested_this: matches:
Expected
{"count":"15","foo":"bar","moo":{"nested":"cow"}}
to have key matching
"nope"
the transform chain was
[{"gjson":{"Path":"@this"}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_transform_not_key: matches:
Expected
{"nested":"cow"}
not to have key matching
"nested"
the transform chain was
[{"gjson":{"Path":"moo"}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_transform_simple: matches:
Expected
"cow"
to equal
"cowx"
diff
--- test
+++ actual
@@ -1 +1 @@
-cowx
+cow
the transform chain was
[{"gjson":{"Path":"moo.nested"}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_using_this_and_equal: matches:
Expected
{"baz":"bing","foo":"bar"}
to equal
{"baz":"bing","fox":"bar"}
the transform chain was
[{"gjson":{"Path":"@this"}}]
the raw value was
"{\"foo\": \"bar\", \"baz\": \"bing\"}"
Matching: test_numeric_string: matches:
Expected
"128"
to equal
"129"
diff
--- test
+++ actual
@@ -1 +1 @@
-129
+128
the transform chain was
[{"to-string":{}}]
the raw value was
128
Matching: test_reader_as_single_string: matches:
Expected
"cool"
to equal
"not-cool"
diff
--- test
+++ actual
@@ -1 +1 @@
-not-cool
+cool
Matching: test_reader_using_int_matchers: matches:
Expected
40
to be numerically le
20
the transform chain was
[{"to-numeric":{}}]
the raw value was
"40"
Matching: test_reader_using_string_matchers: matches:
Expected
"foo bar\n15\nmoo cow\n"
to have length
15
Matching: test_string_float: matches:
Expected
128.3
to be numerically eq
129.3
the transform chain was
[{"to-numeric":{}}]
the raw value was
"128.3"
Matching: test_string_numeric: matches:
Expected
128
to be numerically eq
129
the transform chain was
[{"to-numeric":{}}]
the raw value was
"128"
Failures/Skipped:
Matching: basic_reader_as_array: matches:
Expected
["foo bar","moo cow",""]
to contain element matching
{"contain-substring":"fox"}
the transform chain was
[{"to-array":{}}]
the raw value was
"foo bar\nmoo cow\n"
Matching: test_array: matches:
Expected
["45","46","47"]
to contain elements matching
[{"contain-element":{"match-regexp":"5."}},"55",{"and":[{"ge":56},{"le":30}]}]
the missing elements were
[{"contain-element":{"match-regexp":"5."}},"55",{"and":[{"ge":56},{"le":30}]}]
Matching: test_gjson_have_key_array: matches:
Expected
{"arr":[{"nested":"cow"},{"nested2":"moo"}]}
to satisfy at least one of these matchers
[{"have-key":"fail"}]
the transform chain was
[{"gjson":{"Path":"@this"}}]
the raw value was
"{\"arr\": [{\"nested\": \"cow\"}, {\"nested2\": \"moo\"}]}"
Matching: test_gjson_invalid: matches:
Error
matchers.Gjson{Path:"@this"}: Invalid json
the transform chain was
[{"gjson":{"Path":"@this"}}]
the raw value was
"{\"arr\""
Matching: test_gjson_not_found: matches:
Error
matchers.Gjson{Path:"foo"}: Path not found: foo
the transform chain was
[{"gjson":{"Path":"foo"}}]
the raw value was
"{\"arr\": [{\"nested\": \"cow\"}, {\"nested2\": \"moo\"}]}"
Matching: test_gjson_transform_nested_and: matches:
Expected
{"nested":"cow"}
to have key matching
"nope"
the transform chain was
[{"gjson":{"Path":"moo"}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_transform_nested_count: matches:
Expected
15
to be numerically le
10
the transform chain was
[{"gjson":{"Path":"count"}},{"to-numeric":{}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_transform_nested_prefix: matches:
Expected
"bar"
to have prefix
"x"
diff
--- test
+++ actual
@@ -1 +1 @@
-x
+bar
the transform chain was
[{"gjson":{"Path":"foo"}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_transform_nested_this: matches:
Expected
{"count":"15","foo":"bar","moo":{"nested":"cow"}}
to have key matching
"nope"
the transform chain was
[{"gjson":{"Path":"@this"}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_transform_not_key: matches:
Expected
{"nested":"cow"}
not to have key matching
"nested"
the transform chain was
[{"gjson":{"Path":"moo"}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_transform_simple: matches:
Expected
"cow"
to equal
"cowx"
diff
--- test
+++ actual
@@ -1 +1 @@
-cowx
+cow
the transform chain was
[{"gjson":{"Path":"moo.nested"}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_using_this_and_equal: matches:
Expected
{"baz":"bing","foo":"bar"}
to equal
{"baz":"bing","fox":"bar"}
the transform chain was
[{"gjson":{"Path":"@this"}}]
the raw value was
"{\"foo\": \"bar\", \"baz\": \"bing\"}"
Matching: test_numeric_string: matches:
Expected
"128"
to equal
"129"
diff
--- test
+++ actual
@@ -1 +1 @@
-129
+128
the transform chain was
[{"to-string":{}}]
the raw value was
128
Matching: test_reader_as_single_string: matches:
Expected
"cool"
to equal
"not-cool"
diff
--- test
+++ actual
@@ -1 +1 @@
-not-cool
+cool
Matching: test_reader_using_int_matchers: matches:
Expected
40
to be numerically le
20
the transform chain was
[{"to-numeric":{}}]
the raw value was
"40"
Matching: test_reader_using_string_matchers: matches:
Expected
"foo bar\n15\nmoo cow\n"
to have length
15
Matching: test_string_float: matches:
Expected
128.3
to be numerically eq
129.3
the transform chain was
[{"to-numeric":{}}]
the raw value was
"128.3"
Matching: test_string_numeric: matches:
Expected
128
to be numerically eq
129
the transform chain was
[{"to-numeric":{}}]
the raw value was
"128"
Total Duration:
Count: 18, Failed: 18, Skipped: 0
goss-0.4.9/testdata/out_matching_transformers_failing.1.rspecish 0000664 0000000 0000000 00000010332 14675050513 0025175 0 ustar 00root root 0000000 0000000 FFFFFFFFFFFFFFFFFF
Failures/Skipped:
Matching: basic_reader_as_array: matches:
Expected
["foo bar","moo cow",""]
to contain element matching
{"contain-substring":"fox"}
the transform chain was
[{"to-array":{}}]
the raw value was
"foo bar\nmoo cow\n"
Matching: test_array: matches:
Expected
["45","46","47"]
to contain elements matching
[{"contain-element":{"match-regexp":"5."}},"55",{"and":[{"ge":56},{"le":30}]}]
the missing elements were
[{"contain-element":{"match-regexp":"5."}},"55",{"and":[{"ge":56},{"le":30}]}]
Matching: test_gjson_have_key_array: matches:
Expected
{"arr":[{"nested":"cow"},{"nested2":"moo"}]}
to satisfy at least one of these matchers
[{"have-key":"fail"}]
the transform chain was
[{"gjson":{"Path":"@this"}}]
the raw value was
"{\"arr\": [{\"nested\": \"cow\"}, {\"nested2\": \"moo\"}]}"
Matching: test_gjson_invalid: matches:
Error
matchers.Gjson{Path:"@this"}: Invalid json
the transform chain was
[{"gjson":{"Path":"@this"}}]
the raw value was
"{\"arr\""
Matching: test_gjson_not_found: matches:
Error
matchers.Gjson{Path:"foo"}: Path not found: foo
the transform chain was
[{"gjson":{"Path":"foo"}}]
the raw value was
"{\"arr\": [{\"nested\": \"cow\"}, {\"nested2\": \"moo\"}]}"
Matching: test_gjson_transform_nested_and: matches:
Expected
{"nested":"cow"}
to have key matching
"nope"
the transform chain was
[{"gjson":{"Path":"moo"}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_transform_nested_count: matches:
Expected
15
to be numerically le
10
the transform chain was
[{"gjson":{"Path":"count"}},{"to-numeric":{}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_transform_nested_prefix: matches:
Expected
"bar"
to have prefix
"x"
diff
--- test
+++ actual
@@ -1 +1 @@
-x
+bar
the transform chain was
[{"gjson":{"Path":"foo"}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_transform_nested_this: matches:
Expected
{"count":"15","foo":"bar","moo":{"nested":"cow"}}
to have key matching
"nope"
the transform chain was
[{"gjson":{"Path":"@this"}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_transform_not_key: matches:
Expected
{"nested":"cow"}
not to have key matching
"nested"
the transform chain was
[{"gjson":{"Path":"moo"}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_transform_simple: matches:
Expected
"cow"
to equal
"cowx"
diff
--- test
+++ actual
@@ -1 +1 @@
-cowx
+cow
the transform chain was
[{"gjson":{"Path":"moo.nested"}}]
the raw value was
"{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
Matching: test_gjson_using_this_and_equal: matches:
Expected
{"baz":"bing","foo":"bar"}
to equal
{"baz":"bing","fox":"bar"}
the transform chain was
[{"gjson":{"Path":"@this"}}]
the raw value was
"{\"foo\": \"bar\", \"baz\": \"bing\"}"
Matching: test_numeric_string: matches:
Expected
"128"
to equal
"129"
diff
--- test
+++ actual
@@ -1 +1 @@
-129
+128
the transform chain was
[{"to-string":{}}]
the raw value was
128
Matching: test_reader_as_single_string: matches:
Expected
"cool"
to equal
"not-cool"
diff
--- test
+++ actual
@@ -1 +1 @@
-not-cool
+cool
Matching: test_reader_using_int_matchers: matches:
Expected
40
to be numerically le
20
the transform chain was
[{"to-numeric":{}}]
the raw value was
"40"
Matching: test_reader_using_string_matchers: matches:
Expected
"foo bar\n15\nmoo cow\n"
to have length
15
Matching: test_string_float: matches:
Expected
128.3
to be numerically eq
129.3
the transform chain was
[{"to-numeric":{}}]
the raw value was
"128.3"
Matching: test_string_numeric: matches:
Expected
128
to be numerically eq
129
the transform chain was
[{"to-numeric":{}}]
the raw value was
"128"
Total Duration:
Count: 18, Failed: 18, Skipped: 0
goss-0.4.9/testdata/out_matching_transformers_failing.1.tap 0000664 0000000 0000000 00000007456 14675050513 0024156 0 ustar 00root root 0000000 0000000 1..18
not ok 1 - Matching: basic_reader_as_array: matches: Expected ["foo bar","moo cow",""] to contain element matching {"contain-substring":"fox"} the transform chain was [{"to-array":{}}] the raw value was "foo bar\nmoo cow\n"
not ok 2 - Matching: test_array: matches: Expected ["45","46","47"] to contain elements matching [{"contain-element":{"match-regexp":"5."}},"55",{"and":[{"ge":56},{"le":30}]}] the missing elements were [{"contain-element":{"match-regexp":"5."}},"55",{"and":[{"ge":56},{"le":30}]}]
not ok 3 - Matching: test_gjson_have_key_array: matches: Expected {"arr":[{"nested":"cow"},{"nested2":"moo"}]} to satisfy at least one of these matchers [{"have-key":"fail"}] the transform chain was [{"gjson":{"Path":"@this"}}] the raw value was "{\"arr\": [{\"nested\": \"cow\"}, {\"nested2\": \"moo\"}]}"
not ok 4 - Matching: test_gjson_invalid: matches: Error matchers.Gjson{Path:"@this"}: Invalid json the transform chain was [{"gjson":{"Path":"@this"}}] the raw value was "{\"arr\""
not ok 5 - Matching: test_gjson_not_found: matches: Error matchers.Gjson{Path:"foo"}: Path not found: foo the transform chain was [{"gjson":{"Path":"foo"}}] the raw value was "{\"arr\": [{\"nested\": \"cow\"}, {\"nested2\": \"moo\"}]}"
not ok 6 - Matching: test_gjson_transform_nested_and: matches: Expected {"nested":"cow"} to have key matching "nope" the transform chain was [{"gjson":{"Path":"moo"}}] the raw value was "{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
not ok 7 - Matching: test_gjson_transform_nested_count: matches: Expected 15 to be numerically le 10 the transform chain was [{"gjson":{"Path":"count"}},{"to-numeric":{}}] the raw value was "{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
not ok 8 - Matching: test_gjson_transform_nested_prefix: matches: Expected "bar" to have prefix "x" the transform chain was [{"gjson":{"Path":"foo"}}] the raw value was "{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
not ok 9 - Matching: test_gjson_transform_nested_this: matches: Expected {"count":"15","foo":"bar","moo":{"nested":"cow"}} to have key matching "nope" the transform chain was [{"gjson":{"Path":"@this"}}] the raw value was "{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
not ok 10 - Matching: test_gjson_transform_not_key: matches: Expected {"nested":"cow"} not to have key matching "nested" the transform chain was [{"gjson":{"Path":"moo"}}] the raw value was "{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
not ok 11 - Matching: test_gjson_transform_simple: matches: Expected "cow" to equal "cowx" the transform chain was [{"gjson":{"Path":"moo.nested"}}] the raw value was "{\"foo\": \"bar\", \"moo\": {\"nested\": \"cow\"}, \"count\": \"15\"}"
not ok 12 - Matching: test_gjson_using_this_and_equal: matches: Expected {"baz":"bing","foo":"bar"} to equal {"baz":"bing","fox":"bar"} the transform chain was [{"gjson":{"Path":"@this"}}] the raw value was "{\"foo\": \"bar\", \"baz\": \"bing\"}"
not ok 13 - Matching: test_numeric_string: matches: Expected "128" to equal "129" the transform chain was [{"to-string":{}}] the raw value was 128
not ok 14 - Matching: test_reader_as_single_string: matches: Expected "cool" to equal "not-cool"
not ok 15 - Matching: test_reader_using_int_matchers: matches: Expected 40 to be numerically le 20 the transform chain was [{"to-numeric":{}}] the raw value was "40"
not ok 16 - Matching: test_reader_using_string_matchers: matches: Expected "foo bar\n15\nmoo cow\n" to have length 15
not ok 17 - Matching: test_string_float: matches: Expected 128.3 to be numerically eq 129.3 the transform chain was [{"to-numeric":{}}] the raw value was "128.3"
not ok 18 - Matching: test_string_numeric: matches: Expected 128 to be numerically eq 129 the transform chain was [{"to-numeric":{}}] the raw value was "128"
goss-0.4.9/testdata/out_matching_transformers_failing.2.nagios 0000664 0000000 0000000 00000000076 14675050513 0024642 0 ustar 00root root 0000000 0000000 GOSS CRITICAL - Count: 18, Failed: 18, Skipped: 0, Duration:
goss-0.4.9/testdata/passing.goss.yaml 0000664 0000000 0000000 00000000214 14675050513 0017615 0 ustar 00root root 0000000 0000000 ---
command:
hello world:
exit-status: 0
exec: "echo hello world"
stdout:
- hello world
stderr: []
timeout: 10000
goss-0.4.9/util/ 0000775 0000000 0000000 00000000000 14675050513 0013462 5 ustar 00root root 0000000 0000000 goss-0.4.9/util/build.go 0000664 0000000 0000000 00000000041 14675050513 0015103 0 ustar 00root root 0000000 0000000 package util
var Version string
goss-0.4.9/util/command.go 0000664 0000000 0000000 00000001564 14675050513 0015435 0 ustar 00root root 0000000 0000000 package util
import (
"bytes"
//"fmt"
"os/exec"
"syscall"
)
type Command struct {
name string
Cmd *exec.Cmd
Stdout, Stderr bytes.Buffer
Err error
Status int
}
func NewCommand(name string, arg ...string) *Command {
//fmt.Println(arg)
command := new(Command)
command.name = name
command.Cmd = exec.Command(name, arg...)
return command
}
func (c *Command) Run() error {
c.Cmd.Stdout = &c.Stdout
c.Cmd.Stderr = &c.Stderr
if _, err := exec.LookPath(c.name); err != nil {
c.Err = err
return c.Err
}
if err := c.Cmd.Start(); err != nil {
c.Err = err
return c.Err
}
if err := c.Cmd.Wait(); err != nil {
c.Err = err
if exiterr, ok := err.(*exec.ExitError); ok {
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
c.Status = status.ExitStatus()
}
}
} else {
c.Status = 0
}
return c.Err
}
goss-0.4.9/util/command_windows.go 0000664 0000000 0000000 00000001151 14675050513 0017177 0 ustar 00root root 0000000 0000000 //go:build windows
// +build windows
package util
import (
"strings"
//"fmt"
"os/exec"
"syscall"
)
func NewCommandForWindowsCmd(name string, arg ...string) *Command {
//fmt.Println(arg)
command := new(Command)
command.name = name
// cmd.exe has a unique unquoting algorithm
// provide the full command line in SysProcAttr.CmdLine, leaving Args empty.
// more information: https://golang.org/pkg/os/exec/#Command
command.Cmd = exec.Command(name)
command.Cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: false,
CmdLine: strings.Join(arg, " "),
CreationFlags: 0,
}
return command
}
goss-0.4.9/util/config.go 0000664 0000000 0000000 00000016627 14675050513 0015272 0 ustar 00root root 0000000 0000000 package util
import (
"encoding/json"
"fmt"
"io"
"reflect"
"strings"
"time"
"github.com/oleiade/reflections"
)
// ConfigOption manipulates Config
type ConfigOption func(c *Config) error
// Config is the runtime configuration for the goss system, the cli.Context gets
// converted to this and it allows other packages to embed goss by creating this
// structure and using it when adding, validating etc.
//
// NewConfig can be used to create this which will default to what the CLI assumes
// and allow manipulation via ConfigOption functions
type Config struct {
AllowInsecure bool
AnnounceToCLI bool
Cache time.Duration
Debug bool
Endpoint string
FormatOptions []string
IgnoreList []string
ListenAddress string
LocalAddress string
LogLevel string
MaxConcurrent int
Method string
NoColor *bool
NoFollowRedirects bool
OutputFormat string
OutputWriter io.Writer
PackageManager string
Password string
RequestBody string
Proxy string
RequestHeader []string
RetryTimeout time.Duration
RunLevel string
Server string
Sleep time.Duration
Spec string
Timeout time.Duration
Username string
CAFile string
CertFile string
KeyFile string
Vars string
VarsInline string
DisabledResourceTypes []string
}
// TimeOutMilliSeconds is the timeout as milliseconds
func (c *Config) TimeOutMilliSeconds() int {
return int(c.Timeout / time.Millisecond)
}
// NewConfig creates a default configuration modeled on the defaults the CLI sets, modified using opts
func NewConfig(opts ...ConfigOption) (rc *Config, err error) {
rc = &Config{
AllowInsecure: false,
AnnounceToCLI: false,
Cache: 5 * time.Second,
Debug: false,
Endpoint: "/healthz",
FormatOptions: []string{},
IgnoreList: []string{},
DisabledResourceTypes: []string{},
ListenAddress: ":8080",
LocalAddress: "",
LogLevel: "ERROR",
MaxConcurrent: 50,
NoColor: nil,
NoFollowRedirects: false,
OutputFormat: "structured", // most appropriate for package usage
PackageManager: "",
Password: "",
Proxy: "",
RequestHeader: nil,
RetryTimeout: 0,
Server: "",
Sleep: time.Second,
Spec: "",
Timeout: 0,
Username: "",
Vars: "",
VarsInline: "",
}
// NewConfig() is likely to be used when embedding goss or using as a package
// so assuming no color seems like a sane departure from CLI defaults
WithNoColor()(rc)
for _, opt := range opts {
err = opt(rc)
if err != nil {
return nil, err
}
}
return rc, nil
}
// WithSpecFile sets the path to the file holding spec contents
func WithSpecFile(f string) ConfigOption {
return func(c *Config) error {
c.Spec = f
return nil
}
}
// WithOutputFormat is the formatter to use for output
func WithOutputFormat(f string) ConfigOption {
return func(c *Config) error {
c.OutputFormat = f
return nil
}
}
// WithFormatOptions sets options used by the output format plugins, valid options are output.WithFormatOptions
func WithFormatOptions(opts ...string) ConfigOption {
return func(c *Config) error {
c.FormatOptions = append(c.FormatOptions, opts...)
return nil
}
}
// WithResultWriter sets the writer to write output format to when validating
func WithResultWriter(w io.Writer) ConfigOption {
return func(c *Config) error {
c.OutputWriter = w
return nil
}
}
// WithSleep sets the time to sleep between retries when WithRetryTimeout is set
func WithSleep(d time.Duration) ConfigOption {
return func(c *Config) error {
c.Sleep = d
return nil
}
}
// WithRetryTimeout sets the maximum amount of time checks can be retried, it's runtime + WithSleep
func WithRetryTimeout(d time.Duration) ConfigOption {
return func(c *Config) error {
c.RetryTimeout = d
return nil
}
}
// WithCache sets how long results may be cached for
func WithCache(d time.Duration) ConfigOption {
return func(c *Config) error {
c.Cache = d
return nil
}
}
// WithMaxConcurrency is the maximum concurrent test that can be run
func WithMaxConcurrency(mc int) ConfigOption {
return func(c *Config) error {
c.MaxConcurrent = mc
return nil
}
}
// WithNoColor disables colored output
func WithNoColor() ConfigOption {
return func(c *Config) error {
c.NoColor = func(b bool) *bool { return &b }(true)
return nil
}
}
// WithColor enables colored output
func WithColor() ConfigOption {
return func(c *Config) error {
c.NoColor = func(b bool) *bool { return &b }(false)
return nil
}
}
// WithPackageManager overrides the package manager to use
func WithPackageManager(p string) ConfigOption {
return func(c *Config) error {
c.PackageManager = p
return nil
}
}
// WithDebug enables debug output
func WithDebug() ConfigOption {
return func(c *Config) error {
c.Debug = true
return nil
}
}
// WithVarsFile is a json or yaml file containing variables to pass to the validator
func WithVarsFile(file string) ConfigOption {
return func(c *Config) error {
c.Vars = file
return nil
}
}
// WithVarsData uses v as variables to pass to the Validator
func WithVarsData(v any) ConfigOption {
return func(c *Config) error {
jv, err := json.Marshal(v)
if err != nil {
return err
}
c.VarsInline = string(jv)
return nil
}
}
// WithVarsBytes is a yaml or json byte stream to use as variables passed to the Validator
func WithVarsBytes(v []byte) ConfigOption {
return WithVarsString(string(v))
}
// WithVarsString is a yaml or json string to use as variables passed to the Validator
func WithVarsString(v string) ConfigOption {
return func(c *Config) error {
c.VarsInline = v
return nil
}
}
// WithDisabledResourceTypes ensures that any resource matching types listed will be skipped when validating
func WithDisabledResourceTypes(t ...string) ConfigOption {
return func(c *Config) error {
c.DisabledResourceTypes = append(c.DisabledResourceTypes, t...)
return nil
}
}
type OutputConfig struct {
FormatOptions []string
}
type format string
const (
JSON format = "json"
YAML format = "yaml"
)
func ValidateSections(unmarshal func(any) error, i any, whitelist map[string]bool) error {
// Get generic input
var toValidate map[string]map[string]any
if err := unmarshal(&toValidate); err != nil {
return err
}
// Run input through whitelist
typ := reflect.TypeOf(i)
typs := strings.Split(typ.String(), ".")[1]
for id, v := range toValidate {
for k := range v {
if !whitelist[k] {
return fmt.Errorf("invalid Attribute for %s:%s: %s", typs, id, k)
}
}
}
return nil
}
func WhitelistAttrs(i any, format format) (map[string]bool, error) {
validAttrs := make(map[string]bool)
tags, err := reflections.Tags(i, string(format))
if err != nil {
return nil, err
}
for _, v := range tags {
validAttrs[strings.Split(v, ",")[0]] = true
}
return validAttrs, nil
}
func IsValueInList(value string, list []string) bool {
for _, v := range list {
if strings.EqualFold(v, value) {
return true
}
}
return false
}
goss-0.4.9/util/config_test.go 0000664 0000000 0000000 00000001753 14675050513 0016323 0 ustar 00root root 0000000 0000000 package util
import (
"testing"
)
func TestWithVarsBytes(t *testing.T) {
vs := `{"hello":"world"}`
c, err := NewConfig(WithVarsBytes([]byte(vs)))
if err != nil {
t.Fatal(err.Error())
}
if c.VarsInline != vs {
t.Fatalf("expected %q got %q", vs, c.VarsInline)
}
}
func TestWithVarsString(t *testing.T) {
vs := `{"hello":"world"}`
c, err := NewConfig(WithVarsString(vs))
if err != nil {
t.Fatal(err.Error())
}
if c.VarsInline != vs {
t.Fatalf("expected %q got %q", vs, c.VarsInline)
}
}
func TestWithVarsFile(t *testing.T) {
c, err := NewConfig(WithVarsFile("/nonexisting"))
if err != nil {
t.Fatal(err.Error())
}
if c.Vars != "/nonexisting" {
t.Fatalf("expected '/nonexisting' got %q", c.Vars)
}
}
func TestWithVarsData(t *testing.T) {
c, err := NewConfig(WithVarsData(map[string]string{"hello": "world"}))
if err != nil {
t.Fatal(err.Error())
}
if c.VarsInline != `{"hello":"world"}` {
t.Fatalf("expected %q got %q", `{"hello":"world"}`, c.VarsInline)
}
}
goss-0.4.9/validate.go 0000664 0000000 0000000 00000010735 14675050513 0014633 0 ustar 00root root 0000000 0000000 package goss
import (
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"sync"
"time"
"github.com/fatih/color"
"github.com/onsi/gomega/format"
"github.com/goss-org/goss/outputs"
"github.com/goss-org/goss/resource"
"github.com/goss-org/goss/system"
"github.com/goss-org/goss/util"
)
func getGossConfig(vars string, varsInline string, specFile string) (cfg *GossConfig, err error) {
// handle stdin
var fh *os.File
var path, source string
var gossConfig GossConfig
currentTemplateFilter, err = NewTemplateFilter(vars, varsInline)
if err != nil {
return nil, err
}
if specFile == "-" {
source = "STDIN"
fh = os.Stdin
data, err := io.ReadAll(fh)
if err != nil {
return nil, err
}
outStoreFormat, err = getStoreFormatFromData(data)
if err != nil {
return nil, err
}
gossConfig, err = ReadJSONData(data, true)
if err != nil {
return nil, err
}
} else {
source = specFile
path = filepath.Dir(specFile)
outStoreFormat, err = getStoreFormatFromFileName(specFile)
if err != nil {
return nil, err
}
gossConfig, err = ReadJSON(specFile)
if err != nil {
return nil, err
}
}
gossConfig, err = mergeJSONData(gossConfig, 0, path)
if err != nil {
return nil, err
}
if len(gossConfig.Resources()) == 0 {
return nil, fmt.Errorf("found 0 tests, source: %v", source)
}
return &gossConfig, nil
}
func getOutputer(c *bool, format string) (outputs.Outputer, error) {
if c != nil && *c {
color.NoColor = true
}
if c != nil && !*c {
color.NoColor = false
}
return outputs.GetOutputer(format)
}
// ValidateResults performs validation and provides programmatic access to validation results
// no retries or outputs are supported
func ValidateResults(c *util.Config) (results <-chan []resource.TestResult, err error) {
gossConfig, err := getGossConfig(c.Vars, c.VarsInline, c.Spec)
if err != nil {
return nil, err
}
sys := system.New(c.PackageManager)
return validate(sys, *gossConfig, c.DisabledResourceTypes, c.MaxConcurrent), nil
}
// Validate performs validation, writes formatted output to stdout by default
// and supports retries and more, this is the full featured Validate used
// by the typical CLI invocation and will produce output to StdOut. Use
// ValidateResults for programmatic access
func Validate(c *util.Config) (code int, err error) {
err = setLogLevel(c)
if err != nil {
return 1, err
}
gossConfig, err := getGossConfig(c.Vars, c.VarsInline, c.Spec)
if err != nil {
return 78, err
}
return ValidateConfig(c, gossConfig)
}
func ValidateConfig(c *util.Config, gossConfig *GossConfig) (code int, err error) {
// Needed for contains-elements
// Maybe we don't use this and use custom
// contain_element_matcher is needed because it's single entry to avoid
// transform message
format.UseStringerRepresentation = true
outputConfig := util.OutputConfig{
FormatOptions: c.FormatOptions,
}
sys := system.New(c.PackageManager)
outputer, err := getOutputer(c.NoColor, c.OutputFormat)
if err != nil {
return 1, err
}
var ofh io.Writer
ofh = os.Stdout
if c.OutputWriter != nil {
ofh = c.OutputWriter
}
sleep := c.Sleep
retryTimeout := c.RetryTimeout
i := 1
startTime := time.Now()
for {
out := validate(sys, *gossConfig, c.DisabledResourceTypes, c.MaxConcurrent)
exitCode := outputer.Output(ofh, out, outputConfig)
if retryTimeout == 0 || exitCode == 0 {
return exitCode, nil
}
elapsed := time.Since(startTime)
if elapsed+sleep > retryTimeout {
return 3, fmt.Errorf("timeout of %s reached before tests entered a passing state", retryTimeout)
}
color.Red("Retrying in %s (elapsed/timeout time: %.3fs/%s)\n\n\n", sleep, elapsed.Seconds(), retryTimeout)
// Reset cache
sys = system.New(c.PackageManager)
time.Sleep(sleep)
i++
fmt.Printf("Attempt #%d:\n", i)
}
}
func validate(sys *system.System, gossConfig GossConfig, skipList []string, maxConcurrent int) <-chan []resource.TestResult {
out := make(chan []resource.TestResult)
in := make(chan resource.Resource)
go func() {
for _, t := range gossConfig.Resources() {
if util.IsValueInList(t.TypeName(), skipList) || util.IsValueInList(t.TypeKey(), skipList) {
t.SetSkip()
}
in <- t
}
close(in)
}()
workerCount := runtime.NumCPU() * 5
if workerCount > maxConcurrent {
workerCount = maxConcurrent
}
var wg sync.WaitGroup
for i := 0; i < workerCount; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for f := range in {
out <- f.Validate(sys)
}
}()
}
go func() {
wg.Wait()
close(out)
}()
return out
}