zephinzer/dev

View on GitHub
pkg/software/check.go

Summary

Maintainability
A
0 mins
Test Coverage
B
86%
package software

import (
    "bytes"
    "fmt"
    "os/exec"
    "regexp"
    "strings"
    "syscall"
)

// Check represents a functional check on whether the software exists.
// It works by running the command as specified in Command and comparing
// the output with the provided ExitCode, Stdout, and Stderr.
//
// Usage:
// 1. Use `.Run()` method to run the check, results will be stored in the `.observed` property
// 1. Use `.Verify()` method to verify that the `.observed` property matches the provided parameters
type Check struct {
    // Command is the command to run including arguments
    Command []string `json:"command" yaml:"command,omitempty"`
    // ExitCode is the expected exit code we can expect from the command running if defined
    ExitCode int `json:"exitCode" yaml:"exitCode,omitempty"`
    // Stdout is the expected output on stdout if defined
    Stdout *string `json:"stdout" yaml:"stdout,omitempty"`
    // Stderr is the expected output on stderr if defined
    Stderr *string `json:"stderr" yaml:"stderr,omitempty"`
    // observed contains the observed value after Run is executed
    observed *Check
}

// GetObserved returns the observed results, a nil indicates the check has
// never been run before
func (check Check) GetObserved() *Check {
    return check.observed
}

// Run executes the check, use check.Verify() to verify the results, if this
// function returns an error, it's a system-type error meaning it has nothing
// to do with the command itself, but with it's invocation on the host system
func (check *Check) Run() error {
    var command *exec.Cmd
    var stderr, stdout bytes.Buffer
    if len(check.Command) == 0 {
        return fmt.Errorf("no command specified")
    } else if len(check.Command) == 1 {
        command = exec.Command(check.Command[0])
    } else {
        command = exec.Command(check.Command[0], check.Command[1:]...)
    }
    command.Stdout = &stdout
    command.Stderr = &stderr
    if err := command.Start(); err != nil {
        return err
    }
    check.observed = &Check{
        Command: append([]string{"echo"}, strings.Join(check.Command, " ")),
    }
    if err := command.Wait(); err != nil {
        if exitErr, ok := err.(*exec.ExitError); ok {
            if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
                check.observed.ExitCode = status.ExitStatus()
            }
        } else {
            return err
        }
    }
    stderrOutput := strings.TrimSpace(stderr.String())
    check.observed.Stderr = &stderrOutput
    stdoutOutput := strings.TrimSpace(stdout.String())
    check.observed.Stdout = &stdoutOutput
    return nil
}

// Verify returns nil on veritfication succeeded, returns an error instance containing
// the reason why verification failed otherwise
func (check Check) Verify() error {
    var stdoutMatcher, stderrMatcher *regexp.Regexp
    if check.Stdout != nil {
        stdoutMatcher = regexp.MustCompile(*check.Stdout)
    }
    if check.Stderr != nil {
        stderrMatcher = regexp.MustCompile(*check.Stderr)
    }
    switch true {
    case check.observed == nil:
        return fmt.Errorf("Run() has not been called on this Check{} instance (Command: [%s])", strings.Join(check.Command, ","))
    case check.ExitCode != check.observed.ExitCode:
        return fmt.Errorf("expected exit code %v did not match actual exit code %v", check.ExitCode, check.observed.ExitCode)
    case check.Stdout != nil && !stdoutMatcher.Match([]byte(*check.observed.Stdout)):
        return fmt.Errorf("expected stdout '%s' did not match actual stdout '%s'", *check.Stdout, *check.observed.Stdout)
    case check.Stderr != nil && !stderrMatcher.Match([]byte(*check.observed.Stderr)):
        return fmt.Errorf("expected stderr '%s' did not match actual stderr '%s'", *check.Stderr, *check.observed.Stderr)
    }
    return nil
}