Skip to content

Commit

Permalink
Escape Terraform interpolation
Browse files Browse the repository at this point in the history
Closes #59
  • Loading branch information
julienduchesne committed Feb 12, 2024
1 parent 079d380 commit b0287a1
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 0 deletions.
1 change: 1 addition & 0 deletions config/grafana/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ func Configure(p *ujconfig.Provider) {
SelectorFieldName: "FolderSelector",
Extractor: SelfPackagePath + ".UIDExtractor()",
}
r.InitializerFns = append(r.InitializerFns, createDashboardConfigInitializer)
})
p.AddResourceConfigurator("grafana_dashboard_permission", func(r *ujconfig.Resource) {
delete(r.TerraformResource.Schema, "dashboard_id") // Deprecated
Expand Down
57 changes: 57 additions & 0 deletions config/grafana/dashboard_init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package grafana

import (
"context"
"encoding/json"
"fmt"
"strings"

"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
"github.com/crossplane/crossplane-runtime/pkg/resource"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func createDashboardConfigInitializer(client client.Client) managed.Initializer {
return &dashboardConfigInitializer{
kube: client,
}
}

// Based on the Tagger: https://github.com/crossplane/upjet/blob/v1.1.0/pkg/config/resource.go#L268
type dashboardConfigInitializer struct {
kube client.Client
}

// Replaces ${ with $${ to avoid TF interpolation
func (i *dashboardConfigInitializer) Initialize(ctx context.Context, mg resource.Managed) error {
paved, err := fieldpath.PaveObject(mg)
if err != nil {
return err
}
v, err := paved.GetString("spec.forProvider.configJSON")
if err != nil {
return fmt.Errorf("could not get configJSON: %w", err)
}
if err := paved.SetString("spec.forProvider.configJSON", replaceInterpolation(v)); err != nil {
return fmt.Errorf("could not set configJSON: %w", err)
}
pavedByte, err := paved.MarshalJSON()
if err != nil {
return fmt.Errorf("could not marshal modified dashboard spec into JSON: %w", err)
}
if err := json.Unmarshal(pavedByte, mg); err != nil {
return fmt.Errorf("could not unmarshal modified dashboard spec into managed resource interface: %w", err)
}
if err := i.kube.Update(ctx, mg); err != nil {
return fmt.Errorf("could not update managed resource: %w", err)
}
return nil
}

// Replaces ${ with $${ to avoid TF interpolation
// If a string is already escaped, it should not be escaped again
func replaceInterpolation(s string) string {
replaced := strings.ReplaceAll(s, "${", "$${")
return strings.ReplaceAll(replaced, "$$${", "$${") // Unescape already escaped strings
}
46 changes: 46 additions & 0 deletions config/grafana/dashboard_init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package grafana

import "testing"

func TestReplaceInterpolation(t *testing.T) {
cases := []struct {
name string
input string
expected string
}{
{
name: "no interpolation",
input: "no interpolation",
expected: "no interpolation",
},
{
name: "interpolation",
input: "interpolation ${}",
expected: "interpolation $${}",
},
{
name: "escaped interpolation",
input: "escaped interpolation $${}",
expected: "escaped interpolation $${}",
},
{
name: "multiple interpolations",
input: "multiple ${} interpolations ${}",
expected: "multiple $${} interpolations $${}",
},
{
name: "multiple escaped interpolations",
input: "interpolation $${} with escaped $${}",
expected: "interpolation $${} with escaped $${}",
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
actual := replaceInterpolation(tc.input)
if actual != tc.expected {
t.Errorf("expected %s, got %s", tc.expected, actual)
}
})
}
}

0 comments on commit b0287a1

Please sign in to comment.