Files
troubleshoot/pkg/preflight/read_specs_test.go
Noah Campbell 8197ddecfe added --values and --set flags to lint command (#1907)
* added --values and --set flags to lint command

* Update lint_test.go
2025-10-23 13:20:21 -05:00

486 lines
14 KiB
Go

package preflight
import (
"os"
"path/filepath"
"testing"
"github.com/replicatedhq/troubleshoot/internal/testutils"
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
"github.com/replicatedhq/troubleshoot/pkg/loader"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type PreflightSpecsReadTests []PreflightSpecsReadTest
type PreflightSpecsReadTest struct {
name string
args []string
customStdin bool
stdinDataFile string
//
wantErr bool
wantPreflightSpec *troubleshootv1beta2.Preflight
// TODOLATER: tests around this
wantHostPreflightSpec *troubleshootv1beta2.HostPreflight
// TODOLATER: tests around this
wantUploadResultSpecs []troubleshootv1beta2.Preflight
}
// TODO: Simplify tests and rely on the loader tests
func TestPreflightSpecsRead(t *testing.T) {
// NOTE: don't use t.Parallel(), these tests manipulate os.Stdin
// A very simple preflight spec
preflightFile := filepath.Join(testutils.FileDir(), "../../testdata/preflightspec/troubleshoot_v1beta2_preflight_gotest.yaml")
//
expectPreflightSpec := troubleshootv1beta2.Preflight{
TypeMeta: metav1.TypeMeta{
Kind: "Preflight",
APIVersion: "troubleshoot.sh/v1beta2",
},
ObjectMeta: metav1.ObjectMeta{
Name: "go-test-preflight",
},
Spec: troubleshootv1beta2.PreflightSpec{
UploadResultsTo: "",
Collectors: []*troubleshootv1beta2.Collect{
{
Data: &troubleshootv1beta2.Data{
CollectorMeta: troubleshootv1beta2.CollectorMeta{
CollectorName: "",
},
Name: "example.json",
Data: `{
"foo": "bar",
"stuff": {
"foo": "bar",
"bar": true
},
"morestuff": [
{
"foo": {
"bar": 123
}
}
]
}
`,
},
},
},
RemoteCollectors: []*troubleshootv1beta2.RemoteCollect(nil),
Analyzers: []*troubleshootv1beta2.Analyze{
{
JsonCompare: &troubleshootv1beta2.JsonCompare{
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
CheckName: "Compare JSON Example",
},
CollectorName: "",
FileName: "example.json",
Path: "morestuff.[0].foo.bar",
Value: `123
`,
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "false",
Message: "The collected data does not match the value.",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: "true",
Message: "The collected data matches the value.",
},
},
},
},
},
},
},
}
// A HostPreflight spec
hostpreflightFile := filepath.Join(testutils.FileDir(), "../../examples/preflight/host/block-devices.yaml")
//
expectHostPreflightSpec := troubleshootv1beta2.HostPreflight{
TypeMeta: metav1.TypeMeta{
Kind: "HostPreflight",
APIVersion: "troubleshoot.sh/v1beta2",
},
ObjectMeta: metav1.ObjectMeta{
Name: "block",
},
Spec: troubleshootv1beta2.HostPreflightSpec{
Collectors: []*troubleshootv1beta2.HostCollect{
{
BlockDevices: &troubleshootv1beta2.HostBlockDevices{},
},
},
RemoteCollectors: []*troubleshootv1beta2.RemoteCollect(nil),
Analyzers: []*troubleshootv1beta2.HostAnalyze{
{
BlockDevices: &troubleshootv1beta2.BlockDevicesAnalyze{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: ".* == 1",
Message: "One available block device",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
When: ".* > 1",
Message: "Multiple available block devices",
},
},
{
Fail: &troubleshootv1beta2.SingleOutcome{
Message: "No available block devices",
},
},
},
},
},
},
},
}
// A more complexed preflight spec, which resides in a secret
preflightSecretFile := filepath.Join(testutils.FileDir(), "../../testdata/preflightspec/troubleshoot_v1beta2_preflight_secret_gotest.yaml")
//
expectSecretPreflightSpec := troubleshootv1beta2.Preflight{
TypeMeta: metav1.TypeMeta{
Kind: "Preflight",
APIVersion: "troubleshoot.sh/v1beta2",
},
ObjectMeta: metav1.ObjectMeta{
Name: "preflight-sample",
},
Spec: troubleshootv1beta2.PreflightSpec{
UploadResultsTo: "",
RemoteCollectors: []*troubleshootv1beta2.RemoteCollect(nil),
Analyzers: []*troubleshootv1beta2.Analyze{
{
NodeResources: &troubleshootv1beta2.NodeResources{
AnalyzeMeta: troubleshootv1beta2.AnalyzeMeta{
CheckName: "Node Count Check",
},
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "count() < 3",
Message: "The cluster needs a minimum of 3 nodes.",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "There are not enough nodes to run this application (3 or more)",
},
},
},
},
},
{
ClusterVersion: &troubleshootv1beta2.ClusterVersion{
Outcomes: []*troubleshootv1beta2.Outcome{
{
Fail: &troubleshootv1beta2.SingleOutcome{
When: "< 1.16.0",
Message: "The application requires at least Kubernetes 1.16.0, and recommends 1.18.0.",
URI: "https://kubernetes.io",
},
},
{
Warn: &troubleshootv1beta2.SingleOutcome{
When: "< 1.18.0",
Message: "Your cluster meets the minimum version of Kubernetes, but we recommend you update to 1.18.0 or later.",
URI: "https://kubernetes.io",
},
},
{
Pass: &troubleshootv1beta2.SingleOutcome{
Message: "Your cluster meets the recommended and required versions of Kubernetes.",
},
},
},
},
},
},
},
}
tests := PreflightSpecsReadTests{
// TODOLATER: URL support? local mock webserver? would prefer for these tests to not require internet :)
// TODOLATER: multidoc fixtures
PreflightSpecsReadTest{
name: "file-preflight",
args: []string{preflightFile},
customStdin: false,
wantErr: false,
wantPreflightSpec: &expectPreflightSpec,
wantHostPreflightSpec: nil,
wantUploadResultSpecs: nil,
},
PreflightSpecsReadTest{
name: "file-hostpreflight",
args: []string{hostpreflightFile},
customStdin: false,
wantErr: false,
wantPreflightSpec: nil,
wantHostPreflightSpec: &expectHostPreflightSpec,
wantUploadResultSpecs: nil,
},
PreflightSpecsReadTest{
name: "file-secret",
args: []string{preflightSecretFile},
customStdin: false,
wantErr: false,
wantPreflightSpec: &expectSecretPreflightSpec,
wantHostPreflightSpec: nil,
wantUploadResultSpecs: nil,
},
PreflightSpecsReadTest{
name: "stdin-preflight",
args: []string{"-"},
customStdin: true,
stdinDataFile: preflightFile,
wantErr: false,
wantPreflightSpec: &expectPreflightSpec,
wantHostPreflightSpec: nil,
wantUploadResultSpecs: nil,
},
PreflightSpecsReadTest{
name: "stdin-secret",
args: []string{"-"},
customStdin: true,
stdinDataFile: preflightSecretFile,
wantErr: false,
wantPreflightSpec: &expectSecretPreflightSpec,
wantHostPreflightSpec: nil,
wantUploadResultSpecs: nil,
},
PreflightSpecsReadTest{
name: "stdin-secret and file-preflight",
args: []string{"-", preflightFile},
customStdin: true,
stdinDataFile: preflightSecretFile,
wantErr: false,
wantPreflightSpec: concatSpecs(expectSecretPreflightSpec, expectPreflightSpec),
wantHostPreflightSpec: nil,
wantUploadResultSpecs: nil,
},
PreflightSpecsReadTest{
name: "stdin-secret and support-bundle secret",
args: []string{
"-",
filepath.Join(testutils.FileDir(), "../../testdata/supportbundle/labelled-specs/sb-spec-1.yaml"),
},
customStdin: true,
stdinDataFile: preflightSecretFile,
wantErr: false,
wantPreflightSpec: &expectSecretPreflightSpec,
wantHostPreflightSpec: nil,
wantUploadResultSpecs: nil,
},
PreflightSpecsReadTest{
name: "stdin-secret and redact secret",
args: []string{
"-",
filepath.Join(testutils.FileDir(), "../../testdata/supportbundle/labelled-specs/redact-spec-1.yaml"),
},
customStdin: true,
stdinDataFile: preflightSecretFile,
wantErr: false,
wantPreflightSpec: &expectSecretPreflightSpec,
wantHostPreflightSpec: nil,
wantUploadResultSpecs: nil,
},
/*
/* TODOLATER: needs a cluster with a spec installed?
PreflightSpecsReadTest{
name: "cluster-secret",
args: []string{"/secret/some-secret-spec"},
customStdin: false,
wantErr: false,
wantPreflightSpec: &expectSecretPreflightSpec,
wantHostPreflightSpec: &expectSecretHostPreflightSpec,
wantUploadResultsSpecs: expectSecretUploadResultSpecs,
},
*/
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt := tt // pin
specs, tErr := singleTestPreflightSpecsRead(t, &tt)
require.Equal(t, tt.wantErr, tErr != nil)
if tt.wantPreflightSpec != nil {
assert.Truef(t,
contains(specs.PreflightsV1Beta2, *tt.wantPreflightSpec),
"expected %v to contain %v", specs.PreflightsV1Beta2, *tt.wantPreflightSpec,
)
}
for _, wantUploadResultSpec := range tt.wantUploadResultSpecs {
assert.Truef(t,
contains(specs.PreflightsV1Beta2, wantUploadResultSpec),
"expected %v to contain %v", specs.PreflightsV1Beta2, wantUploadResultSpec,
)
}
if tt.wantHostPreflightSpec != nil {
assert.Truef(t,
contains(specs.HostPreflightsV1Beta2, *tt.wantHostPreflightSpec),
"expected %v to contain %v", testutils.AsJSON(t, specs.HostPreflightsV1Beta2), testutils.AsJSON(t, specs.HostPreflightsV1Beta2),
)
}
assert.Len(t, specs.SupportBundlesV1Beta2, 0)
assert.Len(t, specs.RedactorsV1Beta2, 0)
assert.Len(t, specs.AnalyzersV1Beta2, 0)
assert.Len(t, specs.CollectorsV1Beta2, 0)
assert.Len(t, specs.RemoteCollectorsV1Beta2, 0)
assert.Len(t, specs.HostCollectorsV1Beta2, 0)
})
}
}
func contains[T any](list []T, obj T) bool {
for _, item := range list {
if assert.ObjectsAreEqual(item, obj) {
return true
}
}
return false
}
func concatSpecs(target troubleshootv1beta2.Preflight, source troubleshootv1beta2.Preflight) *troubleshootv1beta2.Preflight {
newSpec := target.DeepCopy()
newSpec.Spec.Collectors = append(newSpec.Spec.Collectors, source.Spec.Collectors...)
newSpec.Spec.RemoteCollectors = append(newSpec.Spec.RemoteCollectors, source.Spec.RemoteCollectors...)
newSpec.Spec.Analyzers = append(newSpec.Spec.Analyzers, source.Spec.Analyzers...)
return newSpec
}
// Structured as a separate function so we can use defer appropriately
func singleTestPreflightSpecsRead(t *testing.T, tt *PreflightSpecsReadTest) (*loader.TroubleshootKinds, error) {
var tmpfile *os.File
var err error
if tt.customStdin == true {
tmpfile, err = os.CreateTemp("", "singleTestPreflightSpecsRead")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name()) // clean up
// Read in data file
// TODOLATER: just copy the file...?
stdinData, err := os.ReadFile(tt.stdinDataFile)
if err != nil {
t.Fatal(err)
}
if _, err := tmpfile.Write(stdinData); err != nil {
t.Fatal(err)
}
if _, err := tmpfile.Seek(0, 0); err != nil {
t.Fatal(err)
}
oldStdin := os.Stdin
defer func() { os.Stdin = oldStdin }() // Restore original Stdin
os.Stdin = tmpfile
}
kinds, err := readSpecs(tt.args)
if tt.customStdin == true {
if err = tmpfile.Close(); err != nil {
t.Fatal(err)
}
}
return kinds, err
}
func TestPreprocessV1Beta3Specs_RequiresValues(t *testing.T) {
// Save and restore viper state
oldValues := viper.Get("values")
oldSet := viper.Get("set")
defer func() {
viper.Set("values", oldValues)
viper.Set("set", oldSet)
}()
t.Run("v1beta3 without values should error", func(t *testing.T) {
// Clear viper values
viper.Set("values", []string{})
viper.Set("set", []string{})
v1beta3File := filepath.Join(testutils.FileDir(), "../../examples/preflight/simple-v1beta3.yaml")
_, _, err := preprocessV1Beta3Specs([]string{v1beta3File})
require.Error(t, err)
assert.Contains(t, err.Error(), "v1beta3 specs with Helm templates require a values file")
})
t.Run("v1beta3 with values file should succeed", func(t *testing.T) {
valuesFile := filepath.Join(testutils.FileDir(), "../../examples/preflight/values-v1beta3-1.yaml")
viper.Set("values", []string{valuesFile})
viper.Set("set", []string{})
v1beta3File := filepath.Join(testutils.FileDir(), "../../examples/preflight/simple-v1beta3.yaml")
processedArgs, tempFiles, err := preprocessV1Beta3Specs([]string{v1beta3File})
// Clean up temp files
defer func() {
for _, f := range tempFiles {
_ = os.Remove(f)
}
}()
require.NoError(t, err)
assert.NotNil(t, processedArgs)
})
t.Run("v1beta3 with --set values should succeed", func(t *testing.T) {
viper.Set("values", []string{})
viper.Set("set", []string{"kubernetes.enabled=true"})
v1beta3File := filepath.Join(testutils.FileDir(), "../../examples/preflight/simple-v1beta3.yaml")
processedArgs, tempFiles, err := preprocessV1Beta3Specs([]string{v1beta3File})
// Clean up temp files
defer func() {
for _, f := range tempFiles {
_ = os.Remove(f)
}
}()
require.NoError(t, err)
assert.NotNil(t, processedArgs)
})
t.Run("v1beta2 without values should succeed", func(t *testing.T) {
viper.Set("values", []string{})
viper.Set("set", []string{})
v1beta2File := filepath.Join(testutils.FileDir(), "../../testdata/preflightspec/troubleshoot_v1beta2_preflight_gotest.yaml")
processedArgs, tempFiles, err := preprocessV1Beta3Specs([]string{v1beta2File})
require.NoError(t, err)
assert.NotNil(t, processedArgs)
assert.Empty(t, tempFiles)
assert.Equal(t, []string{v1beta2File}, processedArgs)
})
}