Skip to content

Commit

Permalink
add tests for manifest_encode
Browse files Browse the repository at this point in the history
  • Loading branch information
jrhouston committed Mar 7, 2024
1 parent 51ad89e commit 8d6534c
Show file tree
Hide file tree
Showing 2 changed files with 300 additions and 20 deletions.
106 changes: 86 additions & 20 deletions internal/framework/provider/functions/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package functions

import (
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
Expand All @@ -27,28 +28,76 @@ func encodeValue(v attr.Value) (any, error) {
return encodeObject(vv)
case basetypes.TupleValue:
return encodeTuple(vv)
// FIXME: we should support map, list here too
case basetypes.MapValue:
return encodeMap(vv)
case basetypes.ListValue:
return encodeList(vv)
case basetypes.SetValue:
return encodeSet(vv)
default:
return nil, fmt.Errorf("tried to encode unsupported type: %T: %v", v, vv)
}
}

func encodeTuple(t basetypes.TupleValue) ([]any, error) {
size := len(t.Elements())
func encodeSet(sv basetypes.SetValue) ([]any, error) {
elems := sv.Elements()
size := len(elems)
l := make([]any, size)
for i := 0; i < size; i++ {
var err error
l[i], err = encodeValue(t.Elements()[i])
l[i], err = encodeValue(elems[i])
if err != nil {
return nil, err
}
}
return l, nil
}

func encodeObject(o basetypes.ObjectValue) (map[string]any, error) {
m := map[string]any{}
for k, v := range o.Attributes() {
func encodeList(lv basetypes.ListValue) ([]any, error) {
elems := lv.Elements()
size := len(elems)
l := make([]any, size)
for i := 0; i < size; i++ {
var err error
l[i], err = encodeValue(elems[i])
if err != nil {
return nil, err
}
}
return l, nil
}

func encodeTuple(tv basetypes.TupleValue) ([]any, error) {
elems := tv.Elements()
size := len(elems)
l := make([]any, size)
for i := 0; i < size; i++ {
var err error
l[i], err = encodeValue(elems[i])
if err != nil {
return nil, err
}
}
return l, nil
}

func encodeObject(ov basetypes.ObjectValue) (map[string]any, error) {
attrs := ov.Attributes()
m := make(map[string]any, len(attrs))
for k, v := range attrs {
var err error
m[k], err = encodeValue(v)
if err != nil {
return nil, err
}
}
return m, nil
}

func encodeMap(mv basetypes.MapValue) (map[string]any, error) {
elems := mv.Elements()
m := make(map[string]any, len(elems))
for k, v := range elems {
var err error
m[k], err = encodeValue(v)
if err != nil {
Expand All @@ -58,27 +107,44 @@ func encodeObject(o basetypes.ObjectValue) (map[string]any, error) {
return m, nil
}

func encode(v attr.Value) (string, diag.Diagnostics) {
func marshal(m map[string]any) (encoded string, diags diag.Diagnostics) {
if err := validateKubernetesManifest(m); err != nil {
diags.Append(diag.NewErrorDiagnostic("Invalid Kubernetes manifest", err.Error()))
return
}
b, err := yaml.Marshal(m)
if err != nil {
diags.Append(diag.NewErrorDiagnostic("Error marshalling yaml", err.Error()))
return
}
return string(b), nil
}

func encode(v attr.Value) (encoded string, diags diag.Diagnostics) {
val, err := encodeValue(v)
if err != nil {
return "", diag.Diagnostics{diag.NewErrorDiagnostic("Error decoding manifest", err.Error())}
}
encoded := []byte{}
if l, ok := val.([]any); ok {

if m, ok := val.(map[string]any); ok {
return marshal(m)
} else if l, ok := val.([]any); ok {
for _, vv := range l {
e, err := yaml.Marshal(vv)
if err != nil {
return "", diag.Diagnostics{diag.NewErrorDiagnostic("Error marshalling yaml", err.Error())}
m, ok := vv.(map[string]any)
if !ok {
diags.Append(diag.NewErrorDiagnostic(
"List of manifests contained an invalid resource", fmt.Sprintf("value doesn't seem to be a manifest: %#v", vv)))
}
s, diags := marshal(m)
if diags.HasError() {
return "", diags
}
encoded = append(encoded, []byte("---\n")...)
encoded = append(encoded, e...)
encoded = strings.Join([]string{encoded, s}, "---\n")
}
return string(encoded), nil
}

encoded, err = yaml.Marshal(val)
if err != nil {
return "", diag.Diagnostics{diag.NewErrorDiagnostic("Error marshalling yaml", err.Error())}
}
return string(encoded), nil
diags.Append(diag.NewErrorDiagnostic(
"Invalid manifest", fmt.Sprintf("value doesn't seem to be a manifest: %#v", val)))
return
}
214 changes: 214 additions & 0 deletions internal/framework/provider/functions/manifest_encode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package functions_test

import (
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

func TestManifestEncode(t *testing.T) {
t.Parallel()

outputName := "test"

resource.UnitTest(t, resource.TestCase{
ProtoV5ProviderFactories: testAccProtoV5ProviderFactories,
Steps: []resource.TestStep{
{
Config: testManifestEncodeConfig(),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckOutput(outputName, `apiVersion: v1
data:
test: test
kind: ConfigMap
metadata:
name: test
`),
),
},
},
})
}

func TestManifestEncodeMulti(t *testing.T) {
t.Parallel()

outputName := "test"

resource.UnitTest(t, resource.TestCase{
ProtoV5ProviderFactories: testAccProtoV5ProviderFactories,
Steps: []resource.TestStep{
{
Config: testManifestEncodeMultiConfig(),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckOutput(outputName, `---
apiVersion: v1
data:
test: test
immutable: false
kind: ConfigMap
metadata:
name: test
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
k8s-app: fluentd-logging
name: fluentd-elasticsearch2
namespace: kube-system
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
template:
metadata:
labels:
name: fluentd-elasticsearch
something: helloworld
spec:
containers:
- image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
name: fluentd-elasticsearch
resources:
limits:
cpu: 1.5
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- mountPath: /var/log
name: varlog
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoSchedule
key: node-role.kubernetes.io/control-plane
operator: Exists
- effect: NoSchedule
key: node-role.kubernetes.io/master
operator: Exists
volumes:
- hostPath:
path: /var/log
name: varlog
`),
),
},
},
})
}

func testManifestEncodeConfig() string {
return `
locals {
single_manifest = {
apiVersion = "v1"
kind = "ConfigMap"
metadata = {
name = "test"
}
data = {
"test" = "test"
}
}
}
output "test" {
value = provider::kubernetes::manifest_encode(local.single_manifest)
}`
}

func testManifestEncodeMultiConfig() string {
return `
locals {
multi_manifest = [
{
apiVersion = "v1"
kind = "ConfigMap"
metadata = {
name = "test"
}
data = {
"test" = "test"
}
immutable = false
},
{
apiVersion = "apps/v1"
kind = "DaemonSet"
metadata = {
name = "fluentd-elasticsearch2"
namespace = "kube-system"
labels = {
"k8s-app" = "fluentd-logging"
}
}
spec = {
selector = {
matchLabels = {
name = "fluentd-elasticsearch"
}
}
template = {
metadata = {
labels = {
"something" = "helloworld"
"name" = "fluentd-elasticsearch"
}
}
spec = {
tolerations = [
{
key = "node-role.kubernetes.io/control-plane"
operator = "Exists"
effect = "NoSchedule"
},
{
key = "node-role.kubernetes.io/master"
operator = "Exists"
effect = "NoSchedule"
}
]
containers = [
{
name = "fluentd-elasticsearch"
image = "quay.io/fluentd_elasticsearch/fluentd:v2.5.2"
resources = {
limits = {
cpu = 1.5
memory = "200Mi"
}
requests = {
cpu = "100m"
memory = "200Mi"
}
}
volumeMounts = [
{
mountPath = "/var/log"
name = "varlog"
}
]
}
]
terminationGracePeriodSeconds = 30
volumes = [
{
name = "varlog"
hostPath = {
path = "/var/log"
}
}
]
}
}
}
}
]
}
output "test" {
value = provider::kubernetes::manifest_encode(local.multi_manifest)
}`
}

0 comments on commit 8d6534c

Please sign in to comment.