POC/Provisioning: Parse and validate objects from repo (#96822)
This commit is contained in:
1 parent
81b56561c0
commit
079f47221d
13 files changed
+347
-117
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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"},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in new issue
Block a user