
1. Extending templater with kyaml functions and creating combined catalogue to be able to request/update the existing resources. This is based on 'everything is transformer' concept introduced in kustomize 4.x That includes gathering all secrets into 1 variable catalogue and special mechanism to regenerate/merge with manual secrets. 2. Implementing 'catalogue per cluster' approach for secrets. 3. Rearranging secrets so it's possible to use: pgp (each person may have his own key), age, Hachicorp Vault and etc and the list of people who can decrypt documents is set in a special file. Since in some cases there should be a separate list of people who can decrypt data - this list is set for each cluster (ephemeral and target) separatelly. Closes: #586 Change-Id: I038f84dd138d5ad4a35f4862c61ff2124c2fd530
635 lines
12 KiB
Go
635 lines
12 KiB
Go
/*
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package templater
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
|
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
|
"sigs.k8s.io/yaml"
|
|
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/base64"
|
|
"encoding/pem"
|
|
)
|
|
|
|
func TestTemplater(t *testing.T) {
|
|
testCases := []struct {
|
|
in string
|
|
cfg string
|
|
expectedOut string
|
|
expectedErr string
|
|
}{
|
|
{
|
|
cfg: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: notImportantHere
|
|
values:
|
|
hosts:
|
|
- macAddress: 00:aa:bb:cc:dd
|
|
name: node-1
|
|
- macAddress: 00:aa:bb:cc:ee
|
|
name: node-2
|
|
template: |
|
|
{{ range .hosts -}}
|
|
---
|
|
apiVersion: metal3.io/v1alpha1
|
|
kind: BareMetalHost
|
|
metadata:
|
|
name: {{ .name }}
|
|
spec:
|
|
bootMACAddress: {{ .macAddress }}
|
|
{{ end -}}`,
|
|
expectedOut: `apiVersion: metal3.io/v1alpha1
|
|
kind: BareMetalHost
|
|
metadata:
|
|
name: node-1
|
|
spec:
|
|
bootMACAddress: 00:aa:bb:cc:dd
|
|
---
|
|
apiVersion: metal3.io/v1alpha1
|
|
kind: BareMetalHost
|
|
metadata:
|
|
name: node-2
|
|
spec:
|
|
bootMACAddress: 00:aa:bb:cc:ee
|
|
`,
|
|
},
|
|
{
|
|
cfg: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: notImportantHere
|
|
values:
|
|
test:
|
|
someKey:
|
|
anotherKey: value
|
|
of:
|
|
- toYaml
|
|
template: |
|
|
{{ toYaml . -}}
|
|
`,
|
|
expectedOut: `test:
|
|
of:
|
|
- toYaml
|
|
someKey:
|
|
anotherKey: value
|
|
`,
|
|
},
|
|
{
|
|
cfg: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: notImportantHere
|
|
values:
|
|
test:
|
|
someKey:
|
|
anotherKey: value
|
|
of:
|
|
- toYaml
|
|
template: |
|
|
{{- $_ := setItems getItems -}}
|
|
{{ toYaml . -}}
|
|
`,
|
|
expectedOut: `test:
|
|
of:
|
|
- toYaml
|
|
someKey:
|
|
anotherKey: value
|
|
`,
|
|
},
|
|
{
|
|
cfg: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: notImportantHere
|
|
values:
|
|
test:
|
|
of:
|
|
- badToYamlInput
|
|
template: |
|
|
{{ toYaml ignorethisbadinput -}}
|
|
`,
|
|
expectedErr: `template: notImportantHere:1: function "ignorethisbadinput" not defined`,
|
|
},
|
|
{
|
|
cfg: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: notImportantHere
|
|
template: |
|
|
{{ end }`,
|
|
expectedErr: "template: notImportantHere:1: unexpected \"}\" in end",
|
|
},
|
|
{
|
|
cfg: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: notImportantHere
|
|
values:
|
|
template: |
|
|
touint32: {{ toUint32 10 -}}
|
|
`,
|
|
expectedOut: `touint32: 10
|
|
`,
|
|
},
|
|
{
|
|
cfg: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: notImportantHere
|
|
values:
|
|
regex: "^[a-z]{5,10}$"
|
|
nomatchregex: "^[a-z]{0,4}$"
|
|
limit: 10
|
|
template: |
|
|
truepassword: {{ regexMatch .regex (regexGen .regex (.limit|int)) }}
|
|
falsepassword: {{ regexMatch .nomatchregex (regexGen .regex (.limit|int)) }}
|
|
`,
|
|
expectedOut: `truepassword: true
|
|
falsepassword: false
|
|
`,
|
|
}, {
|
|
cfg: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: notImportantHere
|
|
values:
|
|
name: test
|
|
regex: "^[a-z]{5,10}$"
|
|
limit: 0
|
|
template: |
|
|
password: {{ regexGen .regex (.limit|int) }}
|
|
`,
|
|
expectedErr: "template: notImportantHere:1:13: executing \"notImportantHere\" at " +
|
|
"<regexGen .regex (.limit | int)>: error calling regexGen: " +
|
|
"Limit cannot be less than or equal to 0",
|
|
},
|
|
{
|
|
cfg: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: notImportantHere
|
|
values:
|
|
name: test
|
|
regex: "^[a-z"
|
|
limit: 10
|
|
template: |
|
|
password: {{ regexGen .regex (.limit|int) }}
|
|
`,
|
|
expectedErr: "template: notImportantHere:1:13: executing \"notImportantHere\" " +
|
|
"at <regexGen .regex (.limit | int)>: error calling " +
|
|
"regexGen: error parsing regexp: missing closing ]: `[a-z`",
|
|
},
|
|
// transformer tests
|
|
{
|
|
in: `
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: map1
|
|
`,
|
|
cfg: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: notImportantHere
|
|
values:
|
|
annotationTransf: |
|
|
kind: AnnotationSetter
|
|
key: test-annotation
|
|
value: %s
|
|
template: |
|
|
{{- $_ := KPipe getItems (list (KYFilter (list (YFilter (printf .annotationTransf "testenvvalue"))))) -}}
|
|
`,
|
|
expectedOut: `apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: map1
|
|
annotations:
|
|
test-annotation: 'testenvvalue'
|
|
`,
|
|
},
|
|
{
|
|
in: `
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: map1
|
|
data:
|
|
value: value1
|
|
---
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: map2
|
|
data:
|
|
value: value2
|
|
`,
|
|
cfg: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: notImportantHere
|
|
values:
|
|
map1grep: |
|
|
kind: GrepFilter
|
|
path:
|
|
- metadata
|
|
- name
|
|
value: ^map1$
|
|
pathGet1: |
|
|
kind: PathGetter
|
|
path:
|
|
- data
|
|
- value
|
|
map2grep: |
|
|
kind: GrepFilter
|
|
path:
|
|
- metadata
|
|
- name
|
|
value: ^map2$
|
|
map2PathGet: |
|
|
kind: PathGetter
|
|
path:
|
|
- data
|
|
fieldSet: |
|
|
kind: FieldSetter
|
|
name: value
|
|
stringValue: %s
|
|
template: |
|
|
{{- $map1 := KPipe getItems (list (KFilter .map1grep)) -}}
|
|
{{- $map1value := YValue (YPipe (index $map1 0) (list (YFilter .pathGet1))) -}}
|
|
{{- $kyflt := KYFilter (list (YFilter .map2PathGet) (YFilter (printf .fieldSet $map1value))) -}}
|
|
{{- $_ := KPipe getItems (list (KFilter .map2grep) $kyflt) -}}
|
|
`,
|
|
expectedOut: `apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: map1
|
|
data:
|
|
value: value1
|
|
---
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: map2
|
|
data:
|
|
value: value1
|
|
`,
|
|
},
|
|
{
|
|
in: `
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: map1
|
|
annotations:
|
|
test-annotation: x
|
|
data:
|
|
value: value1
|
|
---
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: map2
|
|
data:
|
|
value: value2
|
|
`,
|
|
cfg: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: notImportantHere
|
|
values:
|
|
grep: |
|
|
kind: GrepFilter
|
|
path:
|
|
- metadata
|
|
- annotations
|
|
- test-annotation
|
|
value: ^x$
|
|
invertMatch: true
|
|
template: |
|
|
{{- $_ := setItems (KPipe getItems (list (KFilter .grep))) -}}
|
|
`,
|
|
expectedOut: `apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: map2
|
|
data:
|
|
value: value2
|
|
`,
|
|
},
|
|
{
|
|
in: `
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: map1
|
|
annotations:
|
|
test-annotation: x
|
|
data:
|
|
value: value1
|
|
---
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: map2
|
|
data:
|
|
value: value2
|
|
`,
|
|
cfg: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: notImportantHere
|
|
values:
|
|
grep: |
|
|
kind: GrepFilter
|
|
path:
|
|
- metadata
|
|
- annotations
|
|
- test-annotation
|
|
value: ^x$
|
|
invertMatch: true
|
|
template: |
|
|
{{- $_ := setItems (KOneFilter getItems .grep) -}}
|
|
`,
|
|
expectedOut: `apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: map2
|
|
data:
|
|
value: value2
|
|
`,
|
|
},
|
|
{
|
|
in: ``,
|
|
cfg: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: notImportantHere
|
|
template: |
|
|
{{ define "tmplx" }}
|
|
{{- $name:= . -}}
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: {{ $name }}
|
|
{{ end }}
|
|
{{ include "tmplx" "cfg1" }}
|
|
---
|
|
{{ include "tmplx" "cfg2" }}
|
|
`,
|
|
expectedOut: `apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: cfg1
|
|
---
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: cfg2
|
|
`,
|
|
},
|
|
{
|
|
in: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: libModule
|
|
template: |
|
|
{{/* grepTpl returns yaml that can be used to built KFilter that will
|
|
filter with grep */}}
|
|
{{- define "grepTpl" -}}
|
|
kind: GrepFilter
|
|
path: {{ index . 0 }}
|
|
value: {{ index . 1 }}
|
|
{{ if gt (len .) 2}}
|
|
invertMatch: {{ index . 2 }}
|
|
{{ end }}
|
|
{{- end -}}
|
|
{{/* test function */}}
|
|
{{ define "fnFromModule" }}
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: {{ index . 0 }}
|
|
{{ end }}
|
|
`,
|
|
cfg: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: notImportantHere
|
|
template: |
|
|
{{/* remove all modules (they already imported) */}}
|
|
{{- $_ := setItems (KOneFilter getItems (include "grepTpl" (list "[\"kind\"]" "^Templater$" "true"))) -}}
|
|
{{/* call fn from imported module */}}
|
|
{{ include "fnFromModule" (list "cfg1") }}
|
|
`,
|
|
expectedOut: `apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: cfg1
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
cfg := make(map[string]interface{})
|
|
err := yaml.Unmarshal([]byte(tc.cfg), &cfg)
|
|
require.NoError(t, err)
|
|
plugin, err := New(cfg)
|
|
require.NoError(t, err)
|
|
|
|
nodesIn, err := (&kio.ByteReader{Reader: bytes.NewBufferString(tc.in)}).Read()
|
|
require.NoError(t, err)
|
|
|
|
buf := &bytes.Buffer{}
|
|
nodes, err := plugin.Filter(nodesIn)
|
|
if tc.expectedErr != "" {
|
|
assert.EqualError(t, err, tc.expectedErr)
|
|
continue
|
|
}
|
|
require.NoError(t, err)
|
|
err = kio.ByteWriter{Writer: buf}.Write(nodes)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tc.expectedOut, buf.String())
|
|
}
|
|
}
|
|
|
|
func TestGenSignedCertEx(t *testing.T) {
|
|
testCases := []struct {
|
|
cfg string
|
|
expectedSubject pkix.Name
|
|
}{
|
|
{
|
|
cfg: `
|
|
apiVersion: airshipit.org/v1alpha1
|
|
kind: Templater
|
|
metadata:
|
|
name: notImportantHere
|
|
values:
|
|
name: test
|
|
regex: "^[a-z]{5,10}$"
|
|
limit: 0
|
|
template: |
|
|
{{- $targetClusterCa:=genCAEx "Kubernetes API" 3650 }}
|
|
{{- $targetKubeconfigCert:= genSignedCertEx "/CN=admin/O=system:masters" nil nil 365 $targetClusterCa }}
|
|
cert: {{ $targetKubeconfigCert.Cert|b64enc|quote }}
|
|
`,
|
|
expectedSubject: pkix.Name{
|
|
CommonName: `admin`,
|
|
Organization: []string{
|
|
`system:masters`,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
cfg := make(map[string]interface{})
|
|
err := yaml.Unmarshal([]byte(tc.cfg), &cfg)
|
|
require.NoError(t, err)
|
|
plugin, err := New(cfg)
|
|
require.NoError(t, err)
|
|
buf := &bytes.Buffer{}
|
|
nodes, err := plugin.Filter(nil)
|
|
require.NoError(t, err)
|
|
err = kio.ByteWriter{Writer: buf}.Write(nodes)
|
|
require.NoError(t, err)
|
|
|
|
res := make(map[string]string)
|
|
err = yaml.Unmarshal(buf.Bytes(), &res)
|
|
require.NoError(t, err)
|
|
|
|
key, err := base64.StdEncoding.DecodeString(res["cert"])
|
|
require.NoError(t, err)
|
|
|
|
der, _ := pem.Decode(key)
|
|
if der == nil {
|
|
t.Errorf("failed to find PEM block")
|
|
return
|
|
}
|
|
|
|
cert, err := x509.ParseCertificate(der.Bytes)
|
|
if err != nil {
|
|
t.Errorf("failed to parse: %s", err)
|
|
return
|
|
}
|
|
cert.Subject.Names = nil
|
|
assert.Equal(t, tc.expectedSubject, cert.Subject)
|
|
}
|
|
}
|
|
|
|
func TestGetRNodes(t *testing.T) {
|
|
//Prepare test data A, B, C,
|
|
//var x []*yaml.RNode
|
|
rnode1, err := kyaml.Parse(`x: y`)
|
|
require.NoError(t, err)
|
|
rnode2, err := kyaml.Parse(`z: "a"`)
|
|
require.NoError(t, err)
|
|
|
|
testA := []*kyaml.RNode{
|
|
rnode1,
|
|
rnode2,
|
|
}
|
|
|
|
testB := []interface{}{
|
|
rnode1,
|
|
rnode2,
|
|
}
|
|
|
|
testCases := []struct {
|
|
rnodesarr interface{}
|
|
expectedOut string
|
|
expectedErr bool
|
|
}{
|
|
{
|
|
rnodesarr: nil,
|
|
expectedErr: true,
|
|
},
|
|
{
|
|
rnodesarr: testA,
|
|
expectedOut: `
|
|
x: y
|
|
---
|
|
z: "a"
|
|
`,
|
|
},
|
|
{
|
|
rnodesarr: testB,
|
|
expectedOut: `
|
|
x: y
|
|
---
|
|
z: "a"
|
|
`,
|
|
},
|
|
}
|
|
|
|
for i, tc := range testCases {
|
|
nodes, err := getRNodes(tc.rnodesarr)
|
|
if tc.expectedErr && err != nil {
|
|
continue
|
|
}
|
|
if tc.expectedErr {
|
|
t.Errorf("expected error, but hasn't got it for the case %d", i)
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Errorf("got unexpected error: %v", err)
|
|
continue
|
|
}
|
|
|
|
// convert to string and compare with expected
|
|
out := &bytes.Buffer{}
|
|
err = kio.ByteWriter{Writer: out}.Write(nodes)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tc.expectedOut[1:], out.String())
|
|
}
|
|
}
|
|
|
|
func TestDebug(t *testing.T) {
|
|
i := 0
|
|
|
|
os.Setenv("DEBUG_TEMPLATER", "false")
|
|
debug(func() { i = 1 })
|
|
assert.Equal(t, 0, i)
|
|
|
|
os.Setenv("DEBUG_TEMPLATER", "true")
|
|
debug(func() { i = 1 })
|
|
assert.Equal(t, 1, i)
|
|
}
|