Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: test
concurrency:
group: test-${{ github.ref }}
on:
pull_request:

jobs:
unit:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout Source Code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup go
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
with:
go-version: '^1.24'
- run: make install-tools
- run: make test-all
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ lint-all: ## lint all packages of the repository
.PHONY: lint-fix-all
lint-fix-all:
find . -name go.sum -exec dirname {} \; | xargs -I{} /bin/sh -c "cd {} && golangci-lint run --fix ./..."

.PHONY: test-all
test-all: ## lint all packages of the repository
find . -name go.sum -exec dirname {} \; | xargs -I{} /bin/sh -c "cd {} && go test ./... -v"
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ func allNestedAttributesAreNull(obj basetypes.ObjectValue) bool {
// we conclude that not all attributes are null.
return false
}

}

// If we finish iterating without finding a non-null attribute,
Expand Down
121 changes: 121 additions & 0 deletions tfbuilder/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package tfbuilder

import (
"bytes"
"fmt"
"strings"
"text/template"
)

type ProviderType string

const (
KongMesh ProviderType = "kong-mesh"
Konnect ProviderType = "konnect"
KonnectBeta ProviderType = "konnect-beta"
)

type Builder struct {
provider ProviderType
providerProperty ProviderType // optional
scheme string
host string
port int
controlPlanes map[string]*ControlPlane
meshes map[string]*MeshBuilder
policies map[string]*PolicyBuilder
}

func NewBuilder(provider ProviderType, scheme, host string, port int) *Builder {
return &Builder{
provider: provider,
scheme: scheme,
host: host,
port: port,
controlPlanes: make(map[string]*ControlPlane),
meshes: make(map[string]*MeshBuilder),
policies: make(map[string]*PolicyBuilder),
}
}

func (b *Builder) AddControlPlane(cp *ControlPlane) *Builder {
b.controlPlanes[cp.ResourceName] = cp
return b
}

func (b *Builder) AddMesh(mesh *MeshBuilder) *Builder {
b.meshes[mesh.ResourceName] = mesh
return b
}

func (b *Builder) RemoveMesh(name string) *Builder {
delete(b.meshes, name)
return b
}

func (b *Builder) AddPolicy(p *PolicyBuilder) *Builder {
b.policies[p.ResourceName] = p
return b
}

func (b *Builder) RemovePolicy(name string) *Builder {
delete(b.policies, name)
return b
}

func (b *Builder) WithProviderProperty(providerProperty ProviderType) *Builder {
b.providerProperty = providerProperty
return b
}

func (b *Builder) Build() string {
var sb strings.Builder

sb.WriteString(b.renderTemplate("provider.tmpl", map[string]interface{}{
"Provider": b.provider,
"ProviderProperty": b.providerProperty,
"Scheme": b.scheme,
"Host": b.host,
"Port": b.port,
}))
sb.WriteString("\n")

for _, cp := range b.controlPlanes {
sb.WriteString(cp.Render(b))
sb.WriteString("\n")
}

for _, mesh := range b.meshes {
sb.WriteString(mesh.Render(b))
sb.WriteString("\n")
}

for _, policy := range b.policies {
sb.WriteString(policy.Render(b))
sb.WriteString("\n")
}

return sb.String()
}

func (b *Builder) ResourceAddress(resourceType, resourceName string) string {
return fmt.Sprintf("%s_%s.%s", b.provider, resourceType, resourceName)
}

func (b *Builder) renderTemplate(file string, data interface{}) string {
tmplBytes, err := templatesFS.ReadFile("templates/" + file)
if err != nil {
panic(fmt.Errorf("failed to read template %s: %w", file, err))
}

tmpl, err := template.New(file).Parse(string(tmplBytes))
if err != nil {
panic(fmt.Errorf("failed to parse template: %w", err))
}

var out bytes.Buffer
if err := tmpl.Execute(&out, data); err != nil {
panic(fmt.Errorf("failed to render template: %w", err))
}
return out.String()
}
55 changes: 55 additions & 0 deletions tfbuilder/builder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package tfbuilder_test

import (
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"

"github.com/Kong/shared-speakeasy/tfbuilder"
)

func TestBuilder_KongMeshWithPolicy(t *testing.T) {
builder := tfbuilder.NewBuilder(tfbuilder.KongMesh, "http", "localhost", 5681)

// Add mesh
builder.AddMesh(
tfbuilder.NewMeshBuilder("default", "mesh-1").
WithSpec(`skip_creating_initial_policies = [ "*" ]`),
)

// Add policy
builder.AddPolicy(
tfbuilder.NewPolicyBuilder("mesh_traffic_permission", "allow_all", "allow-all", "MeshTrafficPermission").
WithMeshRef("kong-mesh_mesh.default.name").
WithDependsOn("kong-mesh_mesh.default").
WithLabels(map[string]string{
"kuma.io/mesh": "kong-mesh_mesh.default.name",
}).
WithSpecHCL(tfbuilder.AllowAllTrafficPermissionSpec),
)

actual := builder.Build()

goldenFile := filepath.Join("testdata", "expected_kong_mesh_with_policy.tf")

if updateGoldenFiles(t, goldenFile, actual) {
return
}

expected, err := os.ReadFile(goldenFile)
require.NoError(t, err, "reading golden file")

require.Equal(t, string(expected), actual)
}

func updateGoldenFiles(t *testing.T, goldenFile string, actual string) bool {
if os.Getenv("UPDATE_GOLDEN_FILES") == "1" {
err := os.WriteFile(goldenFile, []byte(actual), 0o600)
require.NoError(t, err, "updating golden file")
t.Logf("updated golden file: %s", goldenFile)
return true
}
return false
}
48 changes: 48 additions & 0 deletions tfbuilder/control_plane.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package tfbuilder

import (
"bytes"
"fmt"
"text/template"
)

type ControlPlane struct {
ResourceName string
Name string
Description string
}

func NewControlPlane(resourceName, name, description string) *ControlPlane {
return &ControlPlane{
ResourceName: resourceName,
Name: name,
Description: description,
}
}

func (cp *ControlPlane) Render(provider *Builder) string {
data := map[string]interface{}{
"Provider": provider.provider,
"ProviderProperty": provider.providerProperty,
"ResourceName": cp.ResourceName,
"Name": cp.Name,
"Description": cp.Description,
}

tmplBytes, err := templatesFS.ReadFile("templates/control_plane.tmpl")
if err != nil {
panic(fmt.Errorf("failed to read control plane template: %w", err))
}

tmpl, err := template.New("control_plane").Parse(string(tmplBytes))
if err != nil {
panic(err)
}

var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
panic(err)
}

return buf.String()
}
11 changes: 11 additions & 0 deletions tfbuilder/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/Kong/shared-speakeasy/tfbuilder

go 1.24.1

require github.com/stretchr/testify v1.10.0

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 10 additions & 0 deletions tfbuilder/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
66 changes: 66 additions & 0 deletions tfbuilder/mesh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package tfbuilder

import (
"bytes"
"fmt"
"text/template"
)

type MeshBuilder struct {
ResourceName string
MeshName string
DependsOn []string
Spec string
CPID string // Optional
}

func NewMeshBuilder(resourceName, meshName string) *MeshBuilder {
return &MeshBuilder{
ResourceName: resourceName,
MeshName: meshName,
}
}

func (m *MeshBuilder) WithSpec(spec string) *MeshBuilder {
m.Spec = spec
return m
}

func (m *MeshBuilder) WithCPID(cpID string) *MeshBuilder {
m.CPID = cpID
return m
}

func (m *MeshBuilder) WithDependsOn(deps ...string) *MeshBuilder {
m.DependsOn = append(m.DependsOn, deps...)
return m
}

func (m *MeshBuilder) Render(provider *Builder) string {
data := map[string]interface{}{
"Provider": provider.provider,
"ProviderProperty": provider.providerProperty,
"ResourceName": m.ResourceName,
"MeshName": m.MeshName,
"DependsOn": m.DependsOn,
"Spec": m.Spec,
"CPID": m.CPID,
}

tmplBytes, err := templatesFS.ReadFile("templates/mesh.tmpl")
if err != nil {
panic(fmt.Errorf("failed to read mesh template: %w", err))
}

tmpl, err := template.New("mesh").Parse(string(tmplBytes))
if err != nil {
panic(err)
}

var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
panic(err)
}

return buf.String()
}
Loading
Loading