diff --git a/pkg/apis/provisioning/v0alpha1/types.go b/pkg/apis/provisioning/v0alpha1/types.go index 94c67ef24ea..23cdf025467 100644 --- a/pkg/apis/provisioning/v0alpha1/types.go +++ b/pkg/apis/provisioning/v0alpha1/types.go @@ -158,8 +158,17 @@ type ResourceWrapper struct { // The modified time in the remote file system Timestamp *metav1.Time `json:"timestamp,omitempty"` - // The raw resource body - Value common.Unstructured `json:"value"` + // If errors exist, show them here + Errors []string `json:"errors,omitempty"` + + // Different flavors of the same object + Resource ResourceObjects `json:"resource"` +} + +type ResourceObjects struct { + File common.Unstructured `json:"file,omitempty"` + Store common.Unstructured `json:"store,omitempty"` + DryRun common.Unstructured `json:"dryRun,omitempty"` } // Dummy object to return for webhooks diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go index 066a91e700b..86d5ccab6f6 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go @@ -193,6 +193,25 @@ func (in *RepositoryStatus) DeepCopy() *RepositoryStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceObjects) DeepCopyInto(out *ResourceObjects) { + *out = *in + in.File.DeepCopyInto(&out.File) + in.Store.DeepCopyInto(&out.Store) + in.DryRun.DeepCopyInto(&out.DryRun) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceObjects. +func (in *ResourceObjects) DeepCopy() *ResourceObjects { + if in == nil { + return nil + } + out := new(ResourceObjects) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourceWrapper) DeepCopyInto(out *ResourceWrapper) { *out = *in @@ -201,7 +220,12 @@ func (in *ResourceWrapper) DeepCopyInto(out *ResourceWrapper) { in, out := &in.Timestamp, &out.Timestamp *out = (*in).DeepCopy() } - in.Value.DeepCopyInto(&out.Value) + if in.Errors != nil { + in, out := &in.Errors, &out.Errors + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Resource.DeepCopyInto(&out.Resource) return } diff --git a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go index b28721eb917..0b90a221c3f 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go @@ -22,6 +22,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RepositoryList": schema_pkg_apis_provisioning_v0alpha1_RepositoryList(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RepositorySpec": schema_pkg_apis_provisioning_v0alpha1_RepositorySpec(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.RepositoryStatus": schema_pkg_apis_provisioning_v0alpha1_RepositoryStatus(ref), + "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceObjects": schema_pkg_apis_provisioning_v0alpha1_ResourceObjects(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceWrapper": schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.S3RepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_S3RepositoryConfig(ref), "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.WebhookResponse": schema_pkg_apis_provisioning_v0alpha1_WebhookResponse(ref), @@ -367,6 +368,35 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryStatus(ref common.Reference } } +func schema_pkg_apis_provisioning_v0alpha1_ResourceObjects(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "file": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"), + }, + }, + "store": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"), + }, + }, + "dryRun": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"}, + } +} + func schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -415,18 +445,34 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceWrapper(ref common.ReferenceC Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), }, }, - "value": { + "errors": { SchemaProps: spec.SchemaProps{ - Description: "The raw resource body", - Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"), + Description: "If errors exist, show them here", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "resource": { + SchemaProps: spec.SchemaProps{ + Description: "Different flavors of the same object", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceObjects"), }, }, }, - Required: []string{"value"}, + Required: []string{"resource"}, }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceObjects", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } 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 4f438a48a12..cd87ef03811 100644 --- a/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -1 +1,2 @@ +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 diff --git a/pkg/registry/apis/provisioning/client.go b/pkg/registry/apis/provisioning/client.go index f2b96d77a33..e9dedbdf3b1 100644 --- a/pkg/registry/apis/provisioning/client.go +++ b/pkg/registry/apis/provisioning/client.go @@ -15,9 +15,6 @@ type resourceClient struct { identities auth.BackgroundIdentityService client map[string]*dynamic.DynamicClient clientMu sync.Mutex - - kinds map[schema.GroupVersionKind]schema.GroupVersionResource - kindMu sync.Mutex } func newResourceClient(identities auth.BackgroundIdentityService) *resourceClient { @@ -49,22 +46,40 @@ func (c *resourceClient) Client(ns string) (*dynamic.DynamicClient, error) { return client, nil } -func (c *resourceClient) GVR(ns string, gvk schema.GroupVersionKind) (schema.GroupVersionResource, bool) { +type kindsLookup struct { + client *dynamic.DynamicClient + kinds map[schema.GroupVersionKind]schema.GroupVersionResource + kindMu sync.Mutex +} + +func newKindsLookup(client *dynamic.DynamicClient) *kindsLookup { + return &kindsLookup{ + client: client, + kinds: make(map[schema.GroupVersionKind]schema.GroupVersionResource), + } +} + +func (c *kindsLookup) Resource(gvk schema.GroupVersionKind) (schema.GroupVersionResource, bool) { c.kindMu.Lock() defer c.kindMu.Unlock() - // TODO... this needs to find the right GVR for a GVK - ok := true - var gvr schema.GroupVersionResource - switch gvk.Kind { - case "Dashboard": - gvr = gvk.GroupVersion().WithResource("dashboards") - case "Playlist": - gvr = gvk.GroupVersion().WithResource("playlists") - case "Folder": - gvr = gvk.GroupVersion().WithResource(folder.RESOURCE) - default: - ok = false + gvr, ok := c.kinds[gvk] + if !ok { + ok = true + switch gvk.Kind { + case "Dashboard": + gvr = gvk.GroupVersion().WithResource("dashboards") + case "Playlist": + gvr = gvk.GroupVersion().WithResource("playlists") + case "Folder": + gvr = gvk.GroupVersion().WithResource(folder.RESOURCE) + default: + ok = false + } + // TODO... use the client to get the resource! + if ok { + c.kinds[gvk] = gvr + } } return gvr, ok diff --git a/pkg/registry/apis/provisioning/files.go b/pkg/registry/apis/provisioning/files.go index fbb190e0b7f..5e73fc46eee 100644 --- a/pkg/registry/apis/provisioning/files.go +++ b/pkg/registry/apis/provisioning/files.go @@ -1,9 +1,7 @@ package provisioning import ( - "bytes" "context" - "encoding/json" "fmt" "io" "net/http" @@ -11,18 +9,15 @@ import ( "strings" "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer/yaml" "k8s.io/apiserver/pkg/registry/rest" - "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" ) type filesConnector struct { getter RepoGetter + client *resourceClient } func (*filesConnector) New() runtime.Object { @@ -93,9 +88,10 @@ func (s *filesConnector) Connect(ctx context.Context, name string, opts runtime. ref := r.URL.Query().Get("ref") message := r.URL.Query().Get("message") + code := http.StatusOK switch r.Method { case http.MethodGet: - obj, err = s.doRead(r.Context(), repo, filePath, ref) + obj, err, code = s.doRead(r.Context(), repo, filePath, ref) case http.MethodPost: obj, err = s.doWrite(r.Context(), false, repo, filePath, ref, message, r) case http.MethodPut: @@ -110,44 +106,48 @@ func (s *filesConnector) Connect(ctx context.Context, name string, opts runtime. responder.Error(err) return } - responder.Object(200, obj) + responder.Object(code, obj) }), nil } -func (s *filesConnector) getValidatedBody(_ context.Context, data []byte) (*unstructured.Unstructured, *schema.GroupVersionKind, error) { - obj, gvk, err := LoadYAMLOrJSON(bytes.NewBuffer(data)) - if err != nil { - obj, gvk, err = FallbackResourceLoader(data) - if err != nil { - return nil, nil, err - } +func (s *filesConnector) getParser(repo Repository) (*fileParser, error) { + ns := repo.Config().Namespace + if ns == "" { + return nil, fmt.Errorf("missing namespace") } - - // TODO? dry run??? and add validation errors/warnings? - fmt.Printf("TODO, get client and then dry run... %+v\n", gvk) - - return obj, gvk, err + client, err := s.client.Client(ns) // As system user + if err != nil { + return nil, err + } + return &fileParser{ + namespace: ns, + repo: repo, + client: client, + kinds: newKindsLookup(client), + }, nil } -func (s *filesConnector) doRead(ctx context.Context, repo Repository, path string, ref string) (*provisioning.ResourceWrapper, error) { +func (s *filesConnector) doRead(ctx context.Context, repo Repository, path string, ref string) (*provisioning.ResourceWrapper, error, int) { info, err := repo.Read(ctx, path, ref) if err != nil { - return nil, err + return nil, err, 0 } - obj, _, err := s.getValidatedBody(ctx, info.Data) + parser, err := s.getParser(repo) if err != nil { - return nil, err + return nil, err, 0 } - return &provisioning.ResourceWrapper{ - Path: info.Path, - Ref: info.Ref, - Hash: info.Hash, - Timestamp: info.Modified, - Value: v0alpha1.Unstructured{ - Object: obj.Object, - }, - }, nil + + parsed, err := parser.parse(ctx, info, true) + if err != nil { + return nil, err, 0 + } + + code := http.StatusOK + if len(parsed.errors) > 0 { + code = http.StatusNotAcceptable + } + return parsed.AsResourceWrapper(), nil, code } func (s *filesConnector) doWrite(ctx context.Context, update bool, repo Repository, path string, ref string, message string, req *http.Request) (runtime.Object, error) { @@ -158,37 +158,31 @@ func (s *filesConnector) doWrite(ctx context.Context, update bool, repo Reposito return nil, errors.NewForbidden(provisioning.RepositoryResourceInfo.GroupResource(), "creating files not enabled", nil) } - defer req.Body.Close() + defer func() { _ = req.Body.Close() }() data, err := io.ReadAll(req.Body) if err != nil { return nil, err } - obj, _, err := s.getValidatedBody(ctx, data) + info := &FileInfo{ + Data: data, + Path: path, + Ref: ref, + } + + parser, err := s.getParser(repo) if err != nil { return nil, err } - switch filepath.Ext(path) { - // JSON pretty print - case ".json": - data, err = json.MarshalIndent(obj, "", " ") - if err != nil { - return nil, err - } + parsed, err := parser.parse(ctx, info, true) + if err != nil { + return nil, err + } - // Write the value as yaml - case ".yaml", ".yml": - buff := bytes.NewBuffer(make([]byte, 0, 1024)) - err = yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme). - Encode(obj, buff) - if err != nil { - return nil, err - } - data = buff.Bytes() - - default: - return nil, fmt.Errorf("unexpected format") + data, err = parsed.ToSaveBytes() + if err != nil { + return nil, err } if update { @@ -200,9 +194,9 @@ func (s *filesConnector) doWrite(ctx context.Context, update bool, repo Reposito return nil, err } - fmt.Printf("TODO! trigger sync for this file we just wrote... %s\n", path) + fmt.Printf("TODO! write the file into real storage %s\n", path) - return obj, err + return parsed.obj, err } func (s *filesConnector) doDelete(ctx context.Context, repo Repository, path string, ref string, message string) (runtime.Object, error) { diff --git a/pkg/registry/apis/provisioning/github.go b/pkg/registry/apis/provisioning/github.go index 753d089b447..156cba1ea7a 100644 --- a/pkg/registry/apis/provisioning/github.go +++ b/pkg/registry/apis/provisioning/github.go @@ -36,7 +36,6 @@ func newGithubRepository(ctx context.Context, config *provisioning.Repository) * githubClient: githubClient, logger: slog.Default().With("logger", "github-repository"), } - } func (r *githubRepository) Config() *provisioning.Repository { @@ -374,7 +373,6 @@ func (r *githubRepository) updateWebhook(ctx context.Context, oldRepo *githubRep r.logger.Info("webhook secret reverted", "url", oldCfg.WebhookURL) return nil }, nil - } } diff --git a/pkg/registry/apis/provisioning/hello.go b/pkg/registry/apis/provisioning/hello.go index de939bcbf78..86883ceb423 100644 --- a/pkg/registry/apis/provisioning/hello.go +++ b/pkg/registry/apis/provisioning/hello.go @@ -2,17 +2,14 @@ package provisioning import ( "context" - "encoding/json" "fmt" "log/slog" "net/http" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/registry/rest" - "github.com/grafana/grafana/pkg/apis/dashboard/v1alpha1" "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" ) @@ -94,36 +91,6 @@ func (s *helloWorldSubresource) Connect(ctx context.Context, name string, opts r slog.InfoContext(ctx, "the conspicuous boolean", "bool", b) } - write := r.URL.Query().Get("list") - if write != "" { - gvr, ok := s.parent.client.GVR(repo.Namespace, schema.GroupVersionKind{ - Group: v1alpha1.GROUP, // dashboard.grafana.app - Version: v1alpha1.VERSION, // "v0alpha1", - Kind: "Dashboard", - }) - if !ok { - responder.Error(fmt.Errorf("no dashboard gvr")) - return - } - - client, err := s.parent.client.Client(repo.Namespace) - if err != nil { - responder.Error(err) - return - } - - ttt, err := client.Resource(gvr).Namespace(repo.Namespace).List(ctx, metav1.ListOptions{}) - if err != nil { - responder.Error(err) - return - } - - // responder not happy flipping types - out, _ := json.Marshal(ttt) - w.Write(out) - return - } - slog.InfoContext(ctx, "Got a repository", "name", repo.Name, "type", repo.Spec.Type, diff --git a/pkg/registry/apis/provisioning/parser.go b/pkg/registry/apis/provisioning/parser.go new file mode 100644 index 00000000000..ef64653169b --- /dev/null +++ b/pkg/registry/apis/provisioning/parser.go @@ -0,0 +1,172 @@ +package provisioning + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "path/filepath" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/yaml" + "k8s.io/client-go/dynamic" + + "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" +) + +type fileParser struct { + // Reader is always in the context of a single namespace + namespace string + + // The target repository + repo Repository + + // client helper (for this namespace?) + client *dynamic.DynamicClient + + kinds *kindsLookup +} + +type parsedFile struct { + // Original file info + info *FileInfo + // Parsed contents + obj *unstructured.Unstructured + // The Kind is defined in the file + gvk *schema.GroupVersionKind + // The Resource is found by mapping Kind to the right apiserver + gvr *schema.GroupVersionResource + // Client that can talk to this resource + client dynamic.ResourceInterface + + // The existing object (same name) + // ?? do we need/want the whole thing?? + existing *unstructured.Unstructured + + // The results from dry run + dryRunResponse *unstructured.Unstructured + + // If we got some errors + errors []error +} + +func (r *fileParser) parse(ctx context.Context, info *FileInfo, validate bool) (*parsedFile, error) { + obj, gvk, err := LoadYAMLOrJSON(bytes.NewBuffer(info.Data)) + if err != nil { + obj, gvk, err = FallbackResourceLoader(info.Data) + if err != nil { + return nil, err + } + } + parsed := &parsedFile{ + info: info, + obj: obj, + gvk: gvk, + } + + if r.namespace == "" { + return nil, fmt.Errorf("parser is not configured (missing namespace)") + } + + // Validate the namespace + if obj.GetNamespace() != "" && obj.GetNamespace() != r.namespace { + parsed.errors = append(parsed.errors, ErrNamespaceMismatch) + } + obj.SetNamespace(r.namespace) + + // When name is missing use the file path as the k8s name + if obj.GetName() == "" { + name := filepath.Base(info.Path) + suffix := filepath.Ext(name) + if suffix != "" { + name = strings.TrimSuffix(name, suffix) + } + obj.SetName(name) + } + + // We can not do anything more if no kind is defined + if gvk == nil { + return parsed, nil + } + + gvr, ok := r.kinds.Resource(*gvk) + if !ok { + return parsed, nil + } + + client := r.client.Resource(gvr).Namespace(r.namespace) + parsed.gvr = &gvr + parsed.client = client + if !validate { + return parsed, nil + } + + // Dry run CREATE or UPDATE + parsed.existing, _ = parsed.client.Get(ctx, obj.GetName(), metav1.GetOptions{}) + if parsed.existing == nil { + parsed.dryRunResponse, err = parsed.client.Create(ctx, obj, metav1.CreateOptions{ + DryRun: []string{"All"}, + }) + } else { + parsed.dryRunResponse, err = parsed.client.Update(ctx, obj, metav1.UpdateOptions{ + DryRun: []string{"All"}, + }) + } + if err != nil { + parsed.errors = append(parsed.errors, err) + } + return parsed, nil +} + +func (f *parsedFile) ToSaveBytes() ([]byte, error) { + // TODO... should use the validated one? + obj := f.obj + + switch filepath.Ext(f.info.Path) { + // JSON pretty print + case ".json": + return json.MarshalIndent(obj, "", " ") + + // Write the value as yaml + case ".yaml", ".yml": + buff := bytes.NewBuffer(make([]byte, 0, 1024)) + err := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme). + Encode(obj, buff) + if err != nil { + return nil, err + } + return buff.Bytes(), nil + + default: + return nil, fmt.Errorf("unexpected format") + } +} + +func (f *parsedFile) AsResourceWrapper() *provisioning.ResourceWrapper { + info := f.info + res := provisioning.ResourceObjects{} + if f.obj != nil { + res.File = v0alpha1.Unstructured{Object: f.obj.Object} + } + if f.existing != nil { + res.Store = v0alpha1.Unstructured{Object: f.existing.Object} + } + if f.dryRunResponse != nil { + res.DryRun = v0alpha1.Unstructured{Object: f.dryRunResponse.Object} + } + wrap := &provisioning.ResourceWrapper{ + Path: info.Path, + Ref: info.Ref, + Hash: info.Hash, + Timestamp: info.Modified, + Resource: res, + } + for _, err := range f.errors { + wrap.Errors = append(wrap.Errors, err.Error()) + } + return wrap +} diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index 46631ec673f..89da28866a2 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -158,6 +158,7 @@ func (b *ProvisioningAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserv } storage[provisioning.RepositoryResourceInfo.StoragePath("files")] = &filesConnector{ getter: b, + client: b.client, } storage[provisioning.RepositoryResourceInfo.StoragePath("export")] = exportConnector apiGroupInfo.VersionedResourcesStorageMap[provisioning.VERSION] = storage diff --git a/pkg/registry/apis/provisioning/utils.go b/pkg/registry/apis/provisioning/utils.go index a46c7d5f8bf..a68f96d425e 100644 --- a/pkg/registry/apis/provisioning/utils.go +++ b/pkg/registry/apis/provisioning/utils.go @@ -10,6 +10,8 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/yaml" yamlutil "k8s.io/apimachinery/pkg/util/yaml" + + "github.com/grafana/grafana/pkg/apis/dashboard" ) func FallbackResourceLoader(data []byte) (*unstructured.Unstructured, *schema.GroupVersionKind, error) { @@ -36,7 +38,7 @@ func FallbackResourceLoader(data []byte) (*unstructured.Unstructured, *schema.Gr value["schemaVersion"] != nil && value["tags"] != nil { gvk := &schema.GroupVersionKind{ - Group: "dashboards.grafana.app", + Group: dashboard.GROUP, Version: "v0alpha1", Kind: "Dashboard"} return &unstructured.Unstructured{ diff --git a/pkg/registry/apis/provisioning/utils_test.go b/pkg/registry/apis/provisioning/utils_test.go index ccae9d2efab..ebf9ff72f30 100644 --- a/pkg/registry/apis/provisioning/utils_test.go +++ b/pkg/registry/apis/provisioning/utils_test.go @@ -71,7 +71,7 @@ spec: require.NoError(t, err) require.Equal(t, &schema.GroupVersionKind{ - Group: "dashboards.grafana.app", + Group: "dashboard.grafana.app", Version: "v0alpha1", Kind: "Dashboard", }, gvk) diff --git a/pkg/services/apiserver/service.go b/pkg/services/apiserver/service.go index 433f50ddcde..efd4915af61 100644 --- a/pkg/services/apiserver/service.go +++ b/pkg/services/apiserver/service.go @@ -524,6 +524,7 @@ func (s *service) GetRestConfigForBackgroundWorker(requester func() identity.Req if err := s.NamedService.AwaitRunning(req.Context()); err != nil { return nil, err } + fmt.Printf("WORKER: %s/%v\n", req.Method, req.URL) ctx := identity.WithRequester(context.Background(), requester()) wrapped := grafanaresponsewriter.WrapHandler(s.handler) return wrapped(req.WithContext(ctx))