From 5d4de8d8f3240fd9f993dc529c1b35ce9243a43f Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 22 Nov 2024 16:48:49 +0300 Subject: [PATCH] POC/Provisioning: Support write (after dryRun) (#96919) now writing --- pkg/registry/apis/provisioning/errors.go | 5 +- pkg/registry/apis/provisioning/files.go | 61 ++++++++++++++++-------- pkg/registry/apis/provisioning/parser.go | 13 ++--- pkg/registry/apis/provisioning/utils.go | 2 +- 4 files changed, 49 insertions(+), 32 deletions(-) diff --git a/pkg/registry/apis/provisioning/errors.go b/pkg/registry/apis/provisioning/errors.go index 290613c192e..b7700b15e86 100644 --- a/pkg/registry/apis/provisioning/errors.go +++ b/pkg/registry/apis/provisioning/errors.go @@ -6,6 +6,7 @@ import ( ) var ( - ErrFileNotFound = fs.ErrNotExist - ErrNamespaceMismatch = errors.New("the file namespace does not match target namespace") + ErrFileNotFound = fs.ErrNotExist + ErrNamespaceMismatch = errors.New("the file namespace does not match target namespace") + ErrUnableToReadResourceBytes = errors.New("unable to read bytes as a resource") ) diff --git a/pkg/registry/apis/provisioning/files.go b/pkg/registry/apis/provisioning/files.go index 5e73fc46eee..542449fd372 100644 --- a/pkg/registry/apis/provisioning/files.go +++ b/pkg/registry/apis/provisioning/files.go @@ -2,13 +2,15 @@ package provisioning import ( "context" + "errors" "fmt" "io" "net/http" "path/filepath" "strings" - "k8s.io/apimachinery/pkg/api/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" @@ -61,18 +63,18 @@ func (s *filesConnector) Connect(ctx context.Context, name string, opts runtime. prefix := fmt.Sprintf("/%s/files/", name) idx := strings.Index(r.URL.Path, prefix) if idx == -1 { - responder.Error(errors.NewBadRequest("invalid request path")) + responder.Error(apierrors.NewBadRequest("invalid request path")) return } filePath := r.URL.Path[idx+len(prefix):] if filePath == "" { - responder.Error(errors.NewBadRequest("missing path")) + responder.Error(apierrors.NewBadRequest("missing path")) return } if strings.Contains(filePath, "..") { - responder.Error(errors.NewBadRequest("invalid path navigation")) + responder.Error(apierrors.NewBadRequest("invalid path navigation")) return } @@ -80,18 +82,18 @@ func (s *filesConnector) Connect(ctx context.Context, name string, opts runtime. case ".json", ".yaml", ".yml": // ok default: - responder.Error(errors.NewBadRequest("only yaml and json files supported")) + responder.Error(apierrors.NewBadRequest("only yaml and json files supported")) return } - var obj runtime.Object + var obj *provisioning.ResourceWrapper ref := r.URL.Query().Get("ref") message := r.URL.Query().Get("message") code := http.StatusOK switch r.Method { case http.MethodGet: - obj, err, code = s.doRead(r.Context(), repo, filePath, ref) + code, obj, err = 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: @@ -99,7 +101,7 @@ func (s *filesConnector) Connect(ctx context.Context, name string, opts runtime. case http.MethodDelete: obj, err = s.doDelete(r.Context(), repo, filePath, ref, message) default: - err = errors.NewMethodNotSupported(provisioning.RepositoryResourceInfo.GroupResource(), r.Method) + err = apierrors.NewMethodNotSupported(provisioning.RepositoryResourceInfo.GroupResource(), r.Method) } if err != nil { @@ -127,35 +129,35 @@ func (s *filesConnector) getParser(repo Repository) (*fileParser, error) { }, nil } -func (s *filesConnector) doRead(ctx context.Context, repo Repository, path string, ref string) (*provisioning.ResourceWrapper, error, int) { +func (s *filesConnector) doRead(ctx context.Context, repo Repository, path string, ref string) (int, *provisioning.ResourceWrapper, error) { info, err := repo.Read(ctx, path, ref) if err != nil { - return nil, err, 0 + return 0, nil, err } parser, err := s.getParser(repo) if err != nil { - return nil, err, 0 + return 0, nil, err } parsed, err := parser.parse(ctx, info, true) if err != nil { - return nil, err, 0 + return 0, nil, err } code := http.StatusOK if len(parsed.errors) > 0 { code = http.StatusNotAcceptable } - return parsed.AsResourceWrapper(), nil, code + return code, parsed.AsResourceWrapper(), nil } -func (s *filesConnector) doWrite(ctx context.Context, update bool, repo Repository, path string, ref string, message string, req *http.Request) (runtime.Object, error) { +func (s *filesConnector) doWrite(ctx context.Context, update bool, repo Repository, path string, ref string, message string, req *http.Request) (*provisioning.ResourceWrapper, error) { settings := repo.Config().Spec.Editing if update && !settings.Update { - return nil, errors.NewForbidden(provisioning.RepositoryResourceInfo.GroupResource(), "updating files not enabled", nil) + return nil, apierrors.NewForbidden(provisioning.RepositoryResourceInfo.GroupResource(), "updating files not enabled", nil) } else if !settings.Create { - return nil, errors.NewForbidden(provisioning.RepositoryResourceInfo.GroupResource(), "creating files not enabled", nil) + return nil, apierrors.NewForbidden(provisioning.RepositoryResourceInfo.GroupResource(), "creating files not enabled", nil) } defer func() { _ = req.Body.Close() }() @@ -177,6 +179,9 @@ func (s *filesConnector) doWrite(ctx context.Context, update bool, repo Reposito parsed, err := parser.parse(ctx, info, true) if err != nil { + if errors.Is(err, ErrUnableToReadResourceBytes) { + return nil, apierrors.NewBadRequest("unable to read the request as a resource") + } return nil, err } @@ -194,15 +199,31 @@ func (s *filesConnector) doWrite(ctx context.Context, update bool, repo Reposito return nil, err } - fmt.Printf("TODO! write the file into real storage %s\n", path) + // Which context? request of background??? + // Behaves the same running sync after writing + if parsed.existing == nil { + obj, err := parsed.client.Create(ctx, parsed.obj, metav1.CreateOptions{}) + if err != nil { + parsed.errors = append(parsed.errors, err) + } else { + parsed.obj = obj + } + } else { + obj, err := parsed.client.Update(ctx, parsed.obj, metav1.UpdateOptions{}) + if err != nil { + parsed.errors = append(parsed.errors, err) + } else { + parsed.obj = obj + } + } - return parsed.obj, err + return parsed.AsResourceWrapper(), err } -func (s *filesConnector) doDelete(ctx context.Context, repo Repository, path string, ref string, message string) (runtime.Object, error) { +func (s *filesConnector) doDelete(ctx context.Context, repo Repository, path string, ref string, message string) (*provisioning.ResourceWrapper, error) { settings := repo.Config().Spec.Editing if !settings.Delete { - return nil, errors.NewForbidden(provisioning.RepositoryResourceInfo.GroupResource(), "deleting is not supported", nil) + return nil, apierrors.NewForbidden(provisioning.RepositoryResourceInfo.GroupResource(), "deleting is not supported", nil) } err := repo.Delete(ctx, path, message) diff --git a/pkg/registry/apis/provisioning/parser.go b/pkg/registry/apis/provisioning/parser.go index ef64653169b..0c80fb8c0f6 100644 --- a/pkg/registry/apis/provisioning/parser.go +++ b/pkg/registry/apis/provisioning/parser.go @@ -8,10 +8,10 @@ import ( "path/filepath" "strings" + "gopkg.in/yaml.v3" 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" @@ -124,7 +124,8 @@ func (r *fileParser) parse(ctx context.Context, info *FileInfo, validate bool) ( func (f *parsedFile) ToSaveBytes() ([]byte, error) { // TODO... should use the validated one? - obj := f.obj + obj := f.obj.Object + delete(obj, "metadata") switch filepath.Ext(f.info.Path) { // JSON pretty print @@ -133,13 +134,7 @@ func (f *parsedFile) ToSaveBytes() ([]byte, error) { // 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 + return yaml.Marshal(obj) default: return nil, fmt.Errorf("unexpected format") diff --git a/pkg/registry/apis/provisioning/utils.go b/pkg/registry/apis/provisioning/utils.go index a68f96d425e..a430c9fb5a2 100644 --- a/pkg/registry/apis/provisioning/utils.go +++ b/pkg/registry/apis/provisioning/utils.go @@ -53,7 +53,7 @@ func FallbackResourceLoader(data []byte) (*unstructured.Unstructured, *schema.Gr } } - return nil, nil, fmt.Errorf("unable to convert") + return nil, nil, ErrUnableToReadResourceBytes } func LoadYAMLOrJSON(input io.Reader) (*unstructured.Unstructured, *schema.GroupVersionKind, error) {