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:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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':
|
||||
|
||||
Reference in New Issue
Block a user