diff --git a/cmd/preflight/cli/root.go b/cmd/preflight/cli/root.go index 19f8b81d9..012f869aa 100644 --- a/cmd/preflight/cli/root.go +++ b/cmd/preflight/cli/root.go @@ -71,6 +71,7 @@ that a cluster meets the requirements to run an application.`, // Dry run flag should be in cmd.PersistentFlags() flags made available to all subcommands // Adding here to avoid that cmd.Flags().Bool("dry-run", false, "print the preflight spec without running preflight checks") + cmd.Flags().Bool("no-uri", false, "When this flag is used, Preflight does not attempt to retrieve the spec referenced by the uri: field`") k8sutil.AddFlags(cmd.Flags()) diff --git a/internal/specs/specs.go b/internal/specs/specs.go index a63c3fd0d..2f442e355 100644 --- a/internal/specs/specs.go +++ b/internal/specs/specs.go @@ -361,3 +361,81 @@ func LoadFromCluster(ctx context.Context, client kubernetes.Interface, selectors RawSpecs: rawSpecs, }) } + +// LoadAdditionalSpecFromURIs loads additional specs from the URIs provided in the kinds. +// This function will modify kinds in place. +func LoadAdditionalSpecFromURIs(ctx context.Context, kinds *loader.TroubleshootKinds) { + + obj := reflect.ValueOf(*kinds) + + // iterate over all fields of the TroubleshootKinds + // e.g. SupportBundlesV1Beta2, PreflightsV1Beta2, etc. + for i := 0; i < obj.NumField(); i++ { + field := obj.Field(i) + if field.Kind() != reflect.Slice { + continue + } + + // look at each spec in the slice + // e.g. each spec in []PreflightsV1Beta2 + for count := 0; count < field.Len(); count++ { + currentSpec := field.Index(count) + specName := currentSpec.Type().Name() + + // check if .Spec.Uri exists + specField := currentSpec.FieldByName("Spec") + if !specField.IsValid() { + continue + } + uriField := specField.FieldByName("Uri") + if uriField.Kind() != reflect.String { + continue + } + + // download spec from URI + uri := uriField.String() + if uri == "" { + continue + } + rawSpec, err := downloadFromHttpURL(ctx, uri, nil) + if err != nil { + klog.Warningf("failed to download spec from URI %q: %v", uri, err) + continue + } + + // load spec from raw spec + uriKinds, err := loader.LoadSpecs(ctx, loader.LoadOptions{RawSpec: string(rawSpec)}) + if err != nil { + klog.Warningf("failed to load spec from URI %q: %v", uri, err) + continue + } + + // replace original spec with the loaded spec from URI + newSpec := getFirstSpecOf(uriKinds, specName) + if !newSpec.IsValid() { + klog.Warningf("failed to read spec of type %s in URI %s", specName, uri) + continue + } + currentSpec.Set(newSpec) + } + } +} + +// dynamically get spec from kinds of given name +// return first element of the spec slice +func getFirstSpecOf(kinds *loader.TroubleshootKinds, name string) reflect.Value { + obj := reflect.ValueOf(*kinds) + for i := 0; i < obj.NumField(); i++ { + field := obj.Field(i) + if field.Kind() != reflect.Slice { + continue + } + if field.Len() > 0 { + if field.Index(0).Type().Name() == name { + // return first element + return field.Index(0) + } + } + } + return reflect.Value{} +} diff --git a/internal/specs/specs_test.go b/internal/specs/specs_test.go index b32df9b29..e2f3fa2c2 100644 --- a/internal/specs/specs_test.go +++ b/internal/specs/specs_test.go @@ -224,3 +224,39 @@ spec: require.NoError(t, err) require.Len(t, specs.HostCollectorsV1Beta2, 1) } + +func TestLoadAdditionalSpecFromURIs(t *testing.T) { + m := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`apiVersion: troubleshoot.sh/v1beta2 +apiVersion: troubleshoot.sh/v1beta2 +kind: Preflight +metadata: + name: preflight-2 +spec: + collectors: + - ceph: {} +`)) + })) + defer m.Close() + kinds := loader.NewTroubleshootKinds() + kinds.PreflightsV1Beta2 = []troubleshootv1beta2.Preflight{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "preflight-1", + }, + Spec: troubleshootv1beta2.PreflightSpec{ + Uri: m.URL, + Collectors: []*troubleshootv1beta2.Collect{ + { + DNS: &troubleshootv1beta2.DNS{}, + }, + }, + }, + }, + } + + LoadAdditionalSpecFromURIs(context.Background(), kinds) + require.Len(t, kinds.PreflightsV1Beta2, 1) + require.Len(t, kinds.PreflightsV1Beta2[0].Spec.Collectors, 1) + require.NotNil(t, kinds.PreflightsV1Beta2[0].Spec.Collectors[0].Ceph) +} diff --git a/pkg/preflight/read_specs.go b/pkg/preflight/read_specs.go index 2a1f66b17..7f324f81b 100644 --- a/pkg/preflight/read_specs.go +++ b/pkg/preflight/read_specs.go @@ -29,6 +29,12 @@ func readSpecs(args []string) (*loader.TroubleshootKinds, error) { return nil, err } + // Load additional specs from URIs + // only when no-uri flag is not set + if !viper.GetBool("no-uri") { + specs.LoadAdditionalSpecFromURIs(ctx, kinds) + } + ret := loader.NewTroubleshootKinds() // Concatenate all preflight inclusterSpecs that don't have an upload destination