POC/Provisioning: Import: Create and set the appropriate folder (#97097)

This commit is contained in:
Mariell Hoversholm authored and GitHub committed 2024-11-27 14:40:40 +01:00
1 parent 09ea6b572a
commit b5b43112d1
2 files changed
+67 -15

No files matched your search

+65 -11
View File
@@ -6,12 +6,14 @@ import (
"fmt"
"log/slog"
"net/http"
"path/filepath"
"path"
"strings"
apiutils "github.com/grafana/grafana/pkg/apimachinery/utils"
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/registry/rest"
@@ -78,11 +80,20 @@ func (c *importConnector) Connect(
kinds := newKindsLookup(client)
fileParser := newFileParser(ns, repo, client, kinds)
ref := r.URL.Query().Get("ref")
if ref == "" {
responder.Error(apierrors.NewBadRequest("no ref value was given"))
folderGVR, ok := kinds.Resource(schema.GroupVersionKind{
Group: "folder.grafana.app",
Version: "v0alpha1",
Kind: "Folder",
})
if !ok {
logger.DebugContext(ctx, "no folder GVR was returned")
responder.Error(apierrors.NewInternalError(fmt.Errorf("failed to find folder GVR")))
return
}
folderIface := client.Resource(folderGVR).Namespace(ns)
ref := r.URL.Query().Get("ref")
logger := logger.With("ref", ref)
tree, err := repo.ReadTree(r.Context(), logger, ref)
if err != nil {
@@ -96,7 +107,7 @@ func (c *importConnector) Connect(
continue
}
name := filepath.Base(entry.Path)
name := path.Base(entry.Path)
if strings.ContainsRune(name, '.') {
name = name[:strings.LastIndex(name, ".")]
}
@@ -109,9 +120,9 @@ func (c *importConnector) Connect(
return
}
// NOTE: We don't need validation because we're about to apply the data for real.
// If the data is invalid, we'll know then!
file, err := fileParser.parse(r.Context(), logger, info, false)
// NOTE: We're validating here to make sure we want the folders to be created.
// If the file isn't valid, its folders aren't relevant, either.
file, err := fileParser.parse(r.Context(), logger, info, true)
if err != nil {
logger.DebugContext(ctx, "error on parsing the entry's data", "error", err)
if errors.Is(err, ErrUnableToReadResourceBytes) {
@@ -123,8 +134,50 @@ func (c *importConnector) Connect(
return
}
// TODO: Don't create folders for resources that aren't folder-able. Maybe we can check the annotations for this?
dir := path.Dir(entry.Path)
if dir != "." && dir != "/" {
logger := logger.With("dir", dir)
logger.DebugContext(ctx, "creating directories")
for _, folder := range strings.Split(dir, "/") {
logger := logger.With("folder", folder)
obj, err := folderIface.Get(r.Context(), folder, metav1.GetOptions{})
// FIXME: Check for IsNotFound properly
if obj != nil || err == nil {
logger.DebugContext(ctx, "folder already existed")
continue
}
_, err = folderIface.Create(r.Context(), &unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]any{
"name": folder,
"namespace": ns,
},
"spec": map[string]any{
"title": folder, // TODO: how do we want to get this?
"description": "Repository-managed folder.",
},
},
}, metav1.CreateOptions{})
if err != nil {
logger.DebugContext(ctx, "failed to create folder", "error", err)
responder.Error(apierrors.NewInternalError(fmt.Errorf("got error when creating folder %s: %w", folder, err)))
return
}
logger.DebugContext(ctx, "folder created")
// TODO: folder parents
// TODO: the top-most folder's parent must be the repo folder.
}
}
wrapper := file.AsResourceWrapper()
resource := wrapper.Resource.File
resource.SetNestedField(name, "metadata", "name")
resource.SetNestedField(ns, "metadata", "namespace")
resource.SetNestedField(path.Base(dir), "metadata", "annotations", apiutils.AnnoKeyFolder)
gv, err := schema.ParseGroupVersion(resource.GetNestedString("apiVersion"))
if err != nil {
logger.DebugContext(ctx, "invalid GroupVersion was given as the apiVersion of the parsed object?", "error", err)
@@ -146,10 +199,12 @@ func (c *importConnector) Connect(
responder.Error(apierrors.NewInternalError(fmt.Errorf("failed to check if object already exists: %w", err)))
return
}
kubeObj := resource.ToKubernetesObject()
logger.DebugContext(ctx, "upserting kube object", "name", kubeObj.GetName())
if err != nil { // IsNotFound
_, err = iface.Create(r.Context(), resource.ToKubernetesObject(), metav1.CreateOptions{})
_, err = iface.Create(r.Context(), kubeObj, metav1.CreateOptions{})
} else { // already exists
_, err = iface.Update(ctx, resource.ToKubernetesObject(), metav1.UpdateOptions{})
_, err = iface.Update(r.Context(), kubeObj, metav1.UpdateOptions{})
}
if err != nil {
logger.DebugContext(ctx, "failed to upsert object", "error", err)
@@ -157,7 +212,6 @@ func (c *importConnector) Connect(
return
}
// TODO: Folders
logger.DebugContext(ctx, "successfully upserted the object")
}
+2 -4
View File
@@ -161,7 +161,7 @@ func (b *ProvisioningAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserv
importConnector := &importConnector{
repoGetter: b,
client: b.client,
logger: b.logger.With("connector", "export"),
logger: b.logger.With("connector", "import"),
}
storage := map[string]rest.Storage{}
@@ -518,9 +518,7 @@ func (b *ProvisioningAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.
sub = oas.Paths.Paths[repoprefix+"/import"]
if sub != nil && sub.Post != nil {
ref := *ref
ref.ParameterProps.Required = true
sub.Post.Parameters = []*spec3.Parameter{&ref}
sub.Post.Parameters = []*spec3.Parameter{ref}
}
// hide the version with no path