Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/gossfile.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ dns:
- ::1
server: 8.8.8.8 # Also supports server:port
timeout: 500 # in milliseconds (Only used when server attribute is provided)
retry_count: 1 # Enables retry mechanism when greater than 0; number of additional attempts
retry_delay: 5 # Delay (in seconds) before each retry attempt
```

It is possible to validate the following types of DNS records, but requires the ```server``` attribute be set:
Expand All @@ -217,13 +219,17 @@ dns:
server: 208.67.222.222
addrs:
- "a.dnstest.io."
retry_count: 2
retry_delay: 5

# Validate a PTR record
PTR:8.8.8.8:
resolvable: true
server: 8.8.8.8
addrs:
- "dns.google."
retry_count: 2
retry_delay: 5

# Validate and SRV record
SRV:_https._tcp.dnstest.io:
Expand All @@ -232,6 +238,8 @@ dns:
addrs:
- "0 5 443 a.dnstest.io."
- "10 10 443 b.dnstest.io."
retry_count: 2
retry_delay: 5
```

Please note that if you want `localhost` to **only** resolve `127.0.0.1` you'll need to use [Advanced Matchers](#advanced-matchers)
Expand Down Expand Up @@ -486,6 +494,8 @@ package:
versions:
- 2.2.15
skip: false
retry_count: 1 # Enables retry mechanism when greater than 0; number of additional attempts
retry_delay: 180 # Delay (in seconds) before each retry attempt
```

!!! note
Expand Down
27 changes: 23 additions & 4 deletions resource/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ type DNS struct {
Addrs matcher `json:"addrs,omitempty" yaml:"addrs,omitempty"`
Timeout int `json:"timeout" yaml:"timeout"`
Server string `json:"server,omitempty" yaml:"server,omitempty"`
RetryCount int `json:"retry_count,omitempty" yaml:"retry_count,omitempty"`
RetryDelay int `json:"retry_delay,omitempty" yaml:"retry_delay,omitempty"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
}

Expand Down Expand Up @@ -50,6 +52,8 @@ func (d *DNS) GetResolve() string {
}
return d.id
}
func (d *DNS) GetRetryCount() int { return d.RetryCount }
func (d *DNS) GetRetryDelay() int { return d.RetryDelay }

func (d *DNS) Validate(sys *system.System) []TestResult {
ctx := context.WithValue(context.Background(), idKey{}, d.ID())
Expand All @@ -58,19 +62,34 @@ func (d *DNS) Validate(sys *system.System) []TestResult {
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))
// Retry logic for resolvable
if d.RetryCount > 0 {
results = append(results, ValidateValueWithRetry(d, "resolvable", d.Resolvable, func() (any, error) {
sysDNS := sys.NewDNS(ctx, d.GetResolve(), sys, util.Config{Timeout: time.Duration(d.Timeout) * time.Millisecond, Server: d.Server})
return sysDNS.Resolvable()
}, skip, d.RetryCount, d.RetryDelay))
} else {
sysDNS := sys.NewDNS(ctx, d.GetResolve(), sys, util.Config{Timeout: time.Duration(d.Timeout) * time.Millisecond, Server: d.Server})
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))
if d.RetryCount > 0 {
results = append(results, ValidateValueWithRetry(d, "addrs", d.Addrs, func() (any, error) {
sysDNS := sys.NewDNS(ctx, d.GetResolve(), sys, util.Config{Timeout: time.Duration(d.Timeout) * time.Millisecond, Server: d.Server})
return sysDNS.Addrs()
}, skip, d.RetryCount, d.RetryDelay))
} else {
sysDNS := sys.NewDNS(ctx, d.GetResolve(), sys, util.Config{Timeout: time.Duration(d.Timeout) * time.Millisecond, Server: d.Server})
results = append(results, ValidateValue(d, "addrs", d.Addrs, sysDNS.Addrs, skip))
}
}
return results
}
Expand Down
47 changes: 37 additions & 10 deletions resource/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import (
)

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"`
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"`
RetryCount int `json:"retry_count,omitempty" yaml:"retry_count,omitempty"`
RetryDelay int `json:"retry_delay,omitempty" yaml:"retry_delay,omitempty"`
Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"`
}

const (
Expand Down Expand Up @@ -45,20 +47,45 @@ func (p *Package) GetName() string {
}
return p.id
}
func (p *Package) GetRetryCount() int { return p.RetryCount }
func (p *Package) GetRetryDelay() int { return p.RetryDelay }

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))

// Handle retry logic for installed check
if p.RetryCount > 0 {
results = append(results, ValidateValueWithRetry(p, "installed", p.Installed,
func() (any, error) {
sysPkg := sys.NewPackage(ctx, p.GetName(), sys, util.Config{})
return sysPkg.Installed()
}, skip, p.RetryCount, p.RetryDelay))
} else {
sysPkg := sys.NewPackage(ctx, p.GetName(), sys, util.Config{})
results = append(results, ValidateValue(p, "installed", p.Installed, sysPkg.Installed, skip))
}

if shouldSkip(results) {
skip = true
}

// Handle retry logic for versions check
if p.Versions != nil {
results = append(results, ValidateValue(p, "version", p.Versions, sysPkg.Versions, skip))
if p.RetryCount > 0 {
results = append(results, ValidateValueWithRetry(p, "version", p.Versions,
func() (any, error) {
sysPkg := sys.NewPackage(ctx, p.GetName(), sys, util.Config{})
return sysPkg.Versions()
}, skip, p.RetryCount, p.RetryDelay))
} else {
sysPkg := sys.NewPackage(ctx, p.GetName(), sys, util.Config{})
results = append(results, ValidateValue(p, "version", p.Versions, sysPkg.Versions, skip))
}
}

return results
}

Expand Down
5 changes: 5 additions & 0 deletions resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ type ResourceRead interface {
GetMeta() meta
}

type Retryable interface {
GetRetryCount() int
GetRetryDelay() int
}

type matcher any
type meta map[string]any

Expand Down
39 changes: 39 additions & 0 deletions resource/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,45 @@ func ValidateValue(res ResourceRead, property string, expectedValue any, actual
return ValidateGomegaValue(res, property, expectedValue, actual, skip)
}

func ValidateValueWithRetry(res ResourceRead, property string, expectedValue any, actualFunc func() (any, error), skip bool, retryCount int, retryDelay int) TestResult {
if skip {
// Return skip result immediately
skipFunc := func() (any, error) { return nil, nil }
return ValidateValue(res, property, expectedValue, skipFunc, skip)
}

maxRetries := retryCount + 1
if retryCount < 0 {
maxRetries = 1
}
delay := time.Duration(retryDelay) * time.Second
if delay <= 0 {
delay = 1 * time.Second // Default delay if not specified or invalid
}

var lastResult TestResult

for attempt := 0; attempt < maxRetries; attempt++ {
actual, err := actualFunc()

// Create a function that returns the current result
currentFunc := func() (any, error) { return actual, err }
result := ValidateValue(res, property, expectedValue, currentFunc, skip)
lastResult = result

if result.Result == SUCCESS {
return result
}

// If not the last attempt, wait before retrying
if attempt < maxRetries-1 {
time.Sleep(delay)
}
}

return lastResult
}

func ValidateGomegaValue(res ResourceRead, property string, expectedValue any, actual any, skip bool) TestResult {
id := res.ID()
title := res.GetTitle()
Expand Down