From 9e0538b0cc6641d12f90d17e1275b56108173b2e Mon Sep 17 00:00:00 2001 From: Yoofi Quansah Date: Mon, 2 Oct 2023 11:08:15 -0500 Subject: [PATCH] feat: Add transformers defintion for transforming a definition into a materialized type --- pkg/api/config/config.go | 15 +++++++++++--- pkg/api/core/resource.go | 9 +++++++++ pkg/api/server.go | 42 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/pkg/api/config/config.go b/pkg/api/config/config.go index f1f0534..f025e39 100644 --- a/pkg/api/config/config.go +++ b/pkg/api/config/config.go @@ -22,9 +22,10 @@ import ( func New(ctx context.Context, cfg *config.Config) (*api.Configuration, error) { c := &api.Configuration{ - Definitions: containers.MapStore[string, *core.ResourceDefinition]{}, - Controllers: containers.MapStore[string, api.Controller]{}, - Bindings: containers.MapStore[string, *core.Binding]{}, + Definitions: containers.MapStore[string, *core.ResourceDefinition]{}, + Controllers: containers.MapStore[string, api.Controller]{}, + Bindings: containers.MapStore[string, *core.Binding]{}, + Transformers: containers.MapStore[string, *core.Transformer]{}, } dir := os.DirFS(cfg.API.Resources) @@ -109,6 +110,14 @@ func New(ctx context.Context, cfg *config.Config) (*api.Configuration, error) { } c.Bindings[binding.Metadata.Name] = &binding + case core.TransformerKind: + var transformer core.Transformer + + if err := json.NewDecoder(buf).Decode(&transformer); err != nil { + return fmt.Errorf("parsing transformer: %w", err) + } + + c.Transformers[transformer.Spec.Kind] = &transformer } return nil diff --git a/pkg/api/core/resource.go b/pkg/api/core/resource.go index 5938140..f614f78 100644 --- a/pkg/api/core/resource.go +++ b/pkg/api/core/resource.go @@ -7,6 +7,7 @@ import ( const ( ResourceDefinitionKind = "ResourceDefinition" BindingKind = "Binding" + TransformerKind = "Transformer" ) // Resource is the core API resource definition used to communicate @@ -39,3 +40,11 @@ type BindingSpec struct { Resources []string Controller string } + +type Transformer Object[TransformerSpec] + +type TransformerSpec struct { + Kind string + Controller string + Template string // template to transform a ResourceDefinition into +} diff --git a/pkg/api/server.go b/pkg/api/server.go index c6fe064..3a16bdc 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -1,9 +1,11 @@ package api import ( + "bytes" "context" "encoding/json" "fmt" + "html/template" "io" "io/fs" "log/slog" @@ -67,6 +69,7 @@ type Configuration struct { Definitions containers.MapStore[string, *core.ResourceDefinition] Controllers containers.MapStore[string, Controller] Bindings containers.MapStore[string, *core.Binding] + Transformers containers.MapStore[string, *core.Transformer] TailscaleClient tailscale.Client } @@ -233,6 +236,45 @@ func (s *Server) register(cntl Controller, version string, def *core.ResourceDef resource.Metadata.Namespace, resource.Metadata.Name, ) + transformer, err := s.cfg.Transformers.Get(resource.Kind) + if err == nil && transformer != nil { + m := map[string]interface{}{} + + err := json.Unmarshal(resource.Spec, &m) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + t := template.Must(template.New("").Parse(transformer.Spec.Template)) + bb := &bytes.Buffer{} + err = t.Execute(bb, m) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // For transformed values store the transformed payload within the spec, along with the + // definition, that the user has specified. + // Also add a metadata label to mark this resource as transformed. So the clients that + // retrieve the definition are informed. + resource.Metadata.Labels = map[string]string{ + "transformed": "yes", + } + a := map[string]json.RawMessage{} + + a["definition"] = resource.Spec + a["transformed"] = bb.Bytes() + + spec, err := json.Marshal(a) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + resource.Spec = spec + } + result, err := s.fs.Update(r.Context(), s.rev, message, func(f controllers.FSConfig) error { return cntl.Put(r.Context(), &controllers.PutRequest{ Request: controllers.Request{