1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
|
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
}
|