From ed0db3d0e02269e603e54503159c1b9de00a02df Mon Sep 17 00:00:00 2001 From: Mariell Hoversholm Date: Thu, 21 Nov 2024 14:14:30 +0100 Subject: [PATCH] feat: export metadata-less YAML resources (#96829) * feat: export metadata-less YAML resources * feat: add option to prefer YAML --- pkg/apis/provisioning/v0alpha1/types.go | 5 ++++ .../v0alpha1/zz_generated.openapi.go | 7 +++++ ...enerated.openapi_violation_exceptions.list | 1 + pkg/registry/apis/provisioning/export.go | 26 +++++++++++++------ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/pkg/apis/provisioning/v0alpha1/types.go b/pkg/apis/provisioning/v0alpha1/types.go index 23cdf025467..552cf7839cd 100644 --- a/pkg/apis/provisioning/v0alpha1/types.go +++ b/pkg/apis/provisioning/v0alpha1/types.go @@ -87,6 +87,11 @@ type RepositorySpec struct { // The value is a reference to the Kubernetes metadata name of the folder in the same namespace. Folder string `json:"folder,omitempty"` + // Should we prefer emitting YAML for this repository, e.g. upon export? + // Editing existing dashboards will continue to emit the file format used in the repository. (TODO: implement this) + // If you delete and then recreate a dashboard, it will switch to the preferred format. + PreferYAML bool `json:"preferYaml,omitempty"` + // Edit options within the repository Editing EditingOptions `json:"editing"` diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go index 0b90a221c3f..afdfb17c722 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go @@ -305,6 +305,13 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositorySpec(ref common.ReferenceCa Format: "", }, }, + "preferYaml": { + SchemaProps: spec.SchemaProps{ + Description: "Should we prefer emitting YAML for this repository, e.g. upon export? Editing existing dashboards will continue to emit the file format used in the repository. (TODO: implement this) If you delete and then recreate a dashboard, it will switch to the preferred format.", + Type: []string{"boolean"}, + Format: "", + }, + }, "editing": { SchemaProps: spec.SchemaProps{ Description: "Edit options within the repository", diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list index cd87ef03811..456e92d9f12 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -1,2 +1,3 @@ API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ResourceWrapper,Errors API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositorySpec,GitHub +API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositorySpec,PreferYAML diff --git a/pkg/registry/apis/provisioning/export.go b/pkg/registry/apis/provisioning/export.go index 5d6e1f5a457..98f0200c61f 100644 --- a/pkg/registry/apis/provisioning/export.go +++ b/pkg/registry/apis/provisioning/export.go @@ -10,6 +10,7 @@ import ( apiutils "github.com/grafana/grafana/pkg/apimachinery/utils" provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" + "gopkg.in/yaml.v3" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -132,22 +133,21 @@ func (c *exportConnector) Connect( folder := folders[item.GetAnnotations()[apiutils.AnnoKeyFolder]] - // TODO: Drop the metadata field before writing? - // TODO: Do we want this to export YAML instead maybe? - json, err := json.MarshalIndent(item.Object, "", "\t") + delete(item.Object, "metadata") + marshalledBody, baseFileName, err := c.marshalPreferredFormat(item.Object, name, repo) if err != nil { - slog.ErrorContext(ctx, "failed to marshal dashboard into JSON", + slog.ErrorContext(ctx, "failed to marshal dashboard into preferred format", "err", err, "dashboard", name, "namespace", ns) - responder.Error(apierrors.NewInternalError(fmt.Errorf("failed to marshal dashboard %s into json: %w", name, err))) + responder.Error(apierrors.NewInternalError(fmt.Errorf("failed to marshal dashboard %s: %w", name, err))) return } - fileName := filepath.Join(folder.CreatePath(), name+".json") + fileName := filepath.Join(folder.CreatePath(), baseFileName) // TODO: Upsert - if err := repo.Create(ctx, fileName, json, "export of dashboard "+name+" in ns "+ns); err != nil { - slog.ErrorContext(ctx, "failed to write dashboard JSON to repository", + if err := repo.Create(ctx, fileName, marshalledBody, "export of dashboard "+name+" in namespace "+ns); err != nil { + slog.ErrorContext(ctx, "failed to write dashboard model to repository", "err", err, "repository", repo.Config().GetName(), "dashboard", name, @@ -226,6 +226,16 @@ func (c *exportConnector) fetchFolderInfo( return folders, nil } +func (c *exportConnector) marshalPreferredFormat(obj any, name string, repo Repository) (body []byte, fileName string, err error) { + if repo.Config().Spec.PreferYAML { + body, err = yaml.Marshal(obj) + return body, name + ".yaml", err + } else { + body, err := json.MarshalIndent(obj, "", " ") + return body, name + ".json", err + } +} + var ( _ rest.Connecter = (*exportConnector)(nil) _ rest.Storage = (*exportConnector)(nil)