mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 18:29:53 +00:00
486 lines
14 KiB
Go
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)
|
|
})
|
|
}
|