From ac7edd3032cdba056b895c7c0771c9e4f18ff16d Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 11 Apr 2025 19:04:00 +0300 Subject: [PATCH] Provisioning: Skip validation when writing v1 dashbaords (#103893) --- .../apis/provisioning/resources/fileformat.go | 4 +-- .../apis/provisioning/resources/parser.go | 26 +++++++++++++++---- .../provisioning/resources/parser_test.go | 2 +- .../apis/provisioning/resources/resources.go | 14 ++++++++-- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/pkg/registry/apis/provisioning/resources/fileformat.go b/pkg/registry/apis/provisioning/resources/fileformat.go index dfff81efa62..3c9d9841bf7 100644 --- a/pkg/registry/apis/provisioning/resources/fileformat.go +++ b/pkg/registry/apis/provisioning/resources/fileformat.go @@ -37,7 +37,7 @@ func ReadClassicResource(ctx context.Context, info *repository.FileInfo) (*unstr return nil, nil, "", err } } else { - return nil, nil, "", fmt.Errorf("classic resource must be JSON") + return nil, nil, "", fmt.Errorf("unable to read file") } // regular version headers exist @@ -64,7 +64,7 @@ func ReadClassicResource(ctx context.Context, info *repository.FileInfo) (*unstr value["tags"] != nil { gvk := &schema.GroupVersionKind{ Group: dashboard.GROUP, - Version: dashboard.VERSION, // v1 + Version: "v0alpha1", // no schema Kind: "Dashboard"} return &unstructured.Unstructured{ Object: map[string]interface{}{ diff --git a/pkg/registry/apis/provisioning/resources/parser.go b/pkg/registry/apis/provisioning/resources/parser.go index cdca95ea405..0601a9f1e17 100644 --- a/pkg/registry/apis/provisioning/resources/parser.go +++ b/pkg/registry/apis/provisioning/resources/parser.go @@ -138,7 +138,7 @@ func (r *parser) Parse(ctx context.Context, info *repository.FileInfo) (parsed * logger.Debug("failed to find GVK of the input data, trying fallback loader", "error", err) parsed.Obj, gvk, parsed.Classic, err = ReadClassicResource(ctx, info) if err != nil || gvk == nil { - return nil, err + return nil, apierrors.NewBadRequest("unable to read file as a resource") } } @@ -227,18 +227,25 @@ func (f *ParsedResource) DryRun(ctx context.Context) error { return err } + fieldValidation := "Strict" + if f.GVR == DashboardResource { + fieldValidation = "Ignore" // FIXME: temporary while we improve validation + } + // FIXME: shouldn't we check for the specific error? // Dry run CREATE or UPDATE f.Existing, _ = f.Client.Get(ctx, f.Obj.GetName(), metav1.GetOptions{}) if f.Existing == nil { f.Action = provisioning.ResourceActionCreate f.DryRunResponse, err = f.Client.Create(ctx, f.Obj, metav1.CreateOptions{ - DryRun: []string{"All"}, + DryRun: []string{"All"}, + FieldValidation: fieldValidation, }) } else { f.Action = provisioning.ResourceActionUpdate f.DryRunResponse, err = f.Client.Update(ctx, f.Obj, metav1.UpdateOptions{ - DryRun: []string{"All"}, + DryRun: []string{"All"}, + FieldValidation: fieldValidation, }) } return err @@ -262,13 +269,22 @@ func (f *ParsedResource) Run(ctx context.Context) error { f.Existing, _ = f.Client.Get(ctx, f.Obj.GetName(), metav1.GetOptions{}) } + fieldValidation := "Strict" + if f.GVR == DashboardResource { + fieldValidation = "Ignore" // FIXME: temporary while we improve validation + } + // Run update or create if f.Existing == nil { f.Action = provisioning.ResourceActionCreate - f.Upsert, err = f.Client.Create(ctx, f.Obj, metav1.CreateOptions{}) + f.Upsert, err = f.Client.Create(ctx, f.Obj, metav1.CreateOptions{ + FieldValidation: fieldValidation, + }) } else { f.Action = provisioning.ResourceActionUpdate - f.Upsert, err = f.Client.Update(ctx, f.Obj, metav1.UpdateOptions{}) + f.Upsert, err = f.Client.Update(ctx, f.Obj, metav1.UpdateOptions{ + FieldValidation: fieldValidation, + }) } return err diff --git a/pkg/registry/apis/provisioning/resources/parser_test.go b/pkg/registry/apis/provisioning/resources/parser_test.go index 1a02854ae2b..bf545f90b07 100644 --- a/pkg/registry/apis/provisioning/resources/parser_test.go +++ b/pkg/registry/apis/provisioning/resources/parser_test.go @@ -34,7 +34,7 @@ func TestParser(t *testing.T) { Data: []byte("hello"), // not a real resource }) require.Error(t, err) - require.Equal(t, "classic resource must be JSON", err.Error()) + require.Equal(t, "unable to read file as a resource", err.Error()) }) t.Run("dashboard parsing (with and without name)", func(t *testing.T) { diff --git a/pkg/registry/apis/provisioning/resources/resources.go b/pkg/registry/apis/provisioning/resources/resources.go index 26d79bf4485..7836581ddd1 100644 --- a/pkg/registry/apis/provisioning/resources/resources.go +++ b/pkg/registry/apis/provisioning/resources/resources.go @@ -177,10 +177,20 @@ func (r *ResourcesManager) WriteResourceFromFile(ctx context.Context, path strin parsed.Meta.SetUID("") parsed.Meta.SetResourceVersion("") + // TODO: use parsed.Run() (but that has an extra GET now!!) + fieldValidation := "Strict" + if parsed.GVR == DashboardResource { + fieldValidation = "Ignore" // FIXME: temporary while we improve validation + } + // Update or Create resource - parsed.Upsert, err = parsed.Client.Update(ctx, parsed.Obj, metav1.UpdateOptions{}) + parsed.Upsert, err = parsed.Client.Update(ctx, parsed.Obj, metav1.UpdateOptions{ + FieldValidation: fieldValidation, + }) if apierrors.IsNotFound(err) { - parsed.Upsert, err = parsed.Client.Create(ctx, parsed.Obj, metav1.CreateOptions{}) + parsed.Upsert, err = parsed.Client.Create(ctx, parsed.Obj, metav1.CreateOptions{ + FieldValidation: fieldValidation, + }) } return parsed.Obj.GetName(), parsed.GVK, err