POC/Provisioning: Support write (after dryRun) (#96919)

now writing
This commit is contained in:
Ryan McKinley authored and GitHub committed 2024-11-22 14:48:49 +01:00
1 parent a07c5b0392
commit 5d4de8d8f3
4 files changed
+49 -32

No files matched your search

+3 -2
View File
@@ -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")
)
+41 -20
View File
@@ -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)
+4 -9
View File
@@ -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")
+1 -1
View File
@@ -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) {