Provisioning: Only support YAML (#99421)

* Spec: Remove PreferYaml option

* Spec: Regenerate code from spec

* Provisioning: Only export YAML

* chore: remove last preferYaml refs
This commit is contained in:
Mariell Hoversholm
2025-01-23 14:30:19 +01:00
committed by GitHub
parent cba56ec916
commit 93fa0b1aed
15 changed files with 26 additions and 69 deletions
-5
View File
@@ -81,11 +81,6 @@ 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"`
@@ -880,13 +880,6 @@ 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",
@@ -8,6 +8,5 @@ API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provis
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,TestResults,Errors
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,WebhookStatus,SubscribedEvents
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
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,SyncStatus,JobID
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,WebhookResponse,Message
@@ -14,7 +14,6 @@ type RepositorySpecApplyConfiguration struct {
Title *string `json:"title,omitempty"`
Description *string `json:"description,omitempty"`
Folder *string `json:"folder,omitempty"`
PreferYAML *bool `json:"preferYaml,omitempty"`
Editing *EditingOptionsApplyConfiguration `json:"editing,omitempty"`
Type *provisioningv0alpha1.RepositoryType `json:"type,omitempty"`
Linting *bool `json:"linting,omitempty"`
@@ -53,14 +52,6 @@ func (b *RepositorySpecApplyConfiguration) WithFolder(value string) *RepositoryS
return b
}
// WithPreferYAML sets the PreferYAML field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the PreferYAML field is set to the value of the last call.
func (b *RepositorySpecApplyConfiguration) WithPreferYAML(value bool) *RepositorySpecApplyConfiguration {
b.PreferYAML = &value
return b
}
// WithEditing sets the Editing field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Editing field is set to the value of the last call.
@@ -2,7 +2,6 @@ package jobs
import (
"context"
"encoding/json"
"errors"
"fmt"
"path/filepath"
@@ -111,11 +110,11 @@ func (r *exporter) Export(ctx context.Context,
}
delete(item.Object, "metadata")
marshalledBody, baseFileName, err := r.marshalPreferredFormat(item.Object, name, r.repository)
marshalledBody, err := yaml.Marshal(item.Object)
if err != nil {
return nil, fmt.Errorf("failed to marshal dashboard %s: %w", name, err)
}
fileName := filepath.Join(folders.DirPath(folder), baseFileName)
fileName := filepath.Join(folders.DirPath(folder), name+".yaml")
logger = logger.With("file", fileName)
var ref string
@@ -145,16 +144,6 @@ func (r *exporter) Export(ctx context.Context,
}, nil
}
func (r *exporter) marshalPreferredFormat(obj any, name string, repo repository.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
}
}
func (r *exporter) fetchRepoFolderTree(ctx context.Context) (*folderTree, error) {
iface := r.client.Resource(schema.GroupVersionResource{
Group: "folder.grafana.app",
@@ -138,7 +138,7 @@ func (c *PullRequestCommenter) Process(ctx context.Context, job provisioning.Job
previews := make([]resourcePreview, 0, len(files))
for _, f := range files {
if c.parser.ShouldIgnore(f.Path) {
if resources.ShouldIgnorePath(f.Path) {
continue
}
@@ -148,7 +148,7 @@ func (r *Syncer) replicateTree(ctx context.Context, ref string) error {
continue
}
if r.parser.ShouldIgnore(entry.Path) {
if resources.ShouldIgnorePath(entry.Path) {
logger.Debug("ignoring file")
continue
}
@@ -303,7 +303,7 @@ func (r *Syncer) createFolderPath(ctx context.Context, filePath string) (string,
func (r *Syncer) replicateChanges(ctx context.Context, changes []repository.FileChange) error {
for _, change := range changes {
if r.parser.ShouldIgnore(change.Path) {
if resources.ShouldIgnorePath(change.Path) {
continue
}
@@ -11,7 +11,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
yamlutil "k8s.io/apimachinery/pkg/util/yaml"
"github.com/grafana/grafana-app-sdk/logging"
dashboard "github.com/grafana/grafana/pkg/apis/dashboard/v1alpha1"
@@ -80,16 +79,16 @@ func ReadClassicResource(ctx context.Context, info *repository.FileInfo) (*unstr
return nil, nil, "", ErrUnableToReadResourceBytes
}
func LoadYAMLOrJSON(input io.Reader) (*unstructured.Unstructured, *schema.GroupVersionKind, error) {
decoder := yamlutil.NewYAMLOrJSONDecoder(input, 1024)
var rawObj runtime.RawExtension
err := decoder.Decode(&rawObj)
// DecodeYAMLObject reads the input as YAML and outputs its Kubernetes resource, if it is one.
// Note that all JSON is also valid YAML, so this can also be used for JSON data.
func DecodeYAMLObject(input io.Reader) (*unstructured.Unstructured, *schema.GroupVersionKind, error) {
data, err := io.ReadAll(input)
if err != nil {
return nil, nil, err
}
obj, gvk, err := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme).
Decode(rawObj.Raw, nil, nil)
Decode(data, nil, nil)
if err != nil {
return nil, gvk, err
}
@@ -19,7 +19,7 @@ import (
func TestUtils(t *testing.T) {
t.Run("load playlist json", func(t *testing.T) {
obj, gvk, err := LoadYAMLOrJSON(bytes.NewReader([]byte(`{
obj, gvk, err := DecodeYAMLObject(bytes.NewReader([]byte(`{
"kind": "Playlist",
"apiVersion": "playlist.grafana.app/v0alpha1",
"metadata": {
@@ -40,7 +40,7 @@ func TestUtils(t *testing.T) {
})
t.Run("YAML Parsing", func(t *testing.T) {
obj, gvk, err := LoadYAMLOrJSON(bytes.NewReader([]byte("kind: xyz\npi: 3.1415")))
obj, gvk, err := DecodeYAMLObject(bytes.NewReader([]byte("kind: xyz\npi: 3.1415")))
require.NoError(t, err)
require.NotNil(t, gvk)
require.Equal(t, "xyz", obj.Object["kind"])
@@ -52,7 +52,7 @@ func TestUtils(t *testing.T) {
})
t.Run("load playlist yaml", func(t *testing.T) {
obj, gvk, err := LoadYAMLOrJSON(bytes.NewReader([]byte(`
obj, gvk, err := DecodeYAMLObject(bytes.NewReader([]byte(`
apiVersion: playlist.grafana.app/v0alpha1
kind: Playlist
metadata:
@@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"path"
"path/filepath"
"gopkg.in/yaml.v3"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -118,26 +117,17 @@ func (r *Parser) Client() *DynamicClient {
return r.client
}
func (r *Parser) ShouldIgnore(p string) bool {
ext := filepath.Ext(p)
if ext == ".yaml" || ext == ".json" {
return false
}
return true
}
func (r *Parser) Parse(ctx context.Context, info *repository.FileInfo, validate bool) (parsed *ParsedResource, err error) {
logger := logging.FromContext(ctx).With("path", info.Path, "validate", validate)
parsed = &ParsedResource{
Info: info,
}
if r.ShouldIgnore(info.Path) {
if ShouldIgnorePath(info.Path) {
return parsed, ErrUnableToReadResourceBytes
}
parsed.Obj, parsed.GVK, err = LoadYAMLOrJSON(bytes.NewBuffer(info.Data))
parsed.Obj, parsed.GVK, err = DecodeYAMLObject(bytes.NewBuffer(info.Data))
if err != nil {
logger.Debug("failed to find GVK of the input data", "error", err)
parsed.Obj, parsed.GVK, parsed.Classic, err = ReadClassicResource(ctx, info)
@@ -290,3 +280,14 @@ func (f *ParsedResource) AsResourceWrapper() *provisioning.ResourceWrapper {
}
return wrap
}
// ShouldIgnorePath determines if the path given is worth looking at.
// If this returns true, skip processing it.
// JSON and YAML files are valid paths. Anything else isn't.
func ShouldIgnorePath(p string) bool {
ext := path.Ext(p)
// .yaml is the official extension per the spec, but .yml is widespread, too. (even this repo uses the unofficial one a lot!)
return ext != ".yml" && ext != ".yaml" &&
// We only support YAML, but JSON is sometimes generated from other tools, and is a valid subset of YAML.
ext != ".json"
}
-4
View File
@@ -4040,10 +4040,6 @@
}
]
},
"preferYaml": {
"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": "boolean"
},
"s3": {
"description": "The repository in an S3 bucket. Mutually exclusive with local and github.",
"allOf": [
@@ -206,9 +206,6 @@ export function ConfigForm({ data }: ConfigFormProps) {
<Field label={'Linting'}>
<Switch {...register('linting')} id={'linting'} />
</Field>
<Field label={'Prefer YAML'}>
<Switch {...register('preferYaml')} id={'preferYaml'} />
</Field>
<FieldSet label={'Editing options'}>
<Field label={'Create'} description={'Enable creating files on repository'}>
<Switch {...register('editing.create')} id={'editing.create'} />
@@ -498,7 +498,6 @@ export type RepositorySpec = {
github?: GitHubRepositoryConfig;
linting?: boolean;
local?: LocalRepositoryConfig;
preferYaml?: boolean;
s3?: S3RepositoryConfig;
title: string;
type: 'github' | 'local' | 's3';
@@ -30,7 +30,6 @@ export type RepositorySpec = {
s3?: S3RepositoryConfig;
type: 'github' | 'local' | 's3';
editing: EditingOptions;
preferYaml?: boolean;
linting?: boolean;
};
@@ -8,7 +8,6 @@ export const dataToSpec = (data: RepositoryFormData): RepositorySpec => {
title: data.title || '',
editing: data.editing,
linting: data.linting,
preferYaml: data.preferYaml,
};
switch (data.type) {
case 'github':