Skip to content

Commit

Permalink
feat: support kustomize krm functions style
Browse files Browse the repository at this point in the history
  • Loading branch information
aabouzaid committed May 30, 2022
1 parent 8caa34d commit 512053f
Show file tree
Hide file tree
Showing 5 changed files with 343 additions and 17 deletions.
97 changes: 86 additions & 11 deletions SopsSecretGenerator.go
Expand Up @@ -17,6 +17,7 @@ import (
"unicode"
"unicode/utf8"

"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
"github.com/pkg/errors"
"go.mozilla.org/sops/v3"
"go.mozilla.org/sops/v3/cmd/sops/formats"
Expand Down Expand Up @@ -65,13 +66,82 @@ type Secret struct {
Type string `json:"type,omitempty" yaml:"type,omitempty"`
}

func usage() {
usage := `
SopsSecretGenerator is a Kustomize generator plugin that generates Secrets from sops-encrypted files.
This plugin supports legacy Kustomize plugin style as well KRM Functions style.
Note:
The usage examples here are for standalone execution. If the plugin is used via Kustomize,
then Kustomize will handle passing data to the plugin.
Usage:
- Legacy: SopsSecretGenerator SopsSecretGenerator.yaml
- KRM: cat ResourceList.yaml | SopsSecretGenerator
`

fmt.Fprintf(os.Stderr, "%s", strings.ReplaceAll(usage, " ", ""))
os.Exit(1)
}

func main() {
if len(os.Args) != 2 {
_, _ = fmt.Fprintln(os.Stderr, "usage: SopsSecretGenerator FILE")
os.Exit(1)
argsLen := len(os.Args)

// Kustomize KRM Function style.
if argsLen == 1 {
stdinStat, _ := os.Stdin.Stat()

// Check the StdIn content.
if (stdinStat.Mode() & os.ModeCharDevice) != 0 {
usage()
}

err := fn.AsMain(fn.ResourceListProcessorFunc(generateKRMManifest))
if err != nil {
fmt.Println(err)
usage()
}

// Kustomize legacy plugin style.
} else {
if argsLen != 2 {
usage()
}

sopsSecretGeneratorManifest, err := readFile(os.Args[1])
if err != nil {
fmt.Println(err)
usage()
}

secretManifest := generateSecretManifest(sopsSecretGeneratorManifest)
fmt.Print(secretManifest)
}
}

// generateKRMManifest reads ResourceList with SopsSecretGenerator items
// and returns ResourceList with Secret items.
func generateKRMManifest(rl *fn.ResourceList) (bool, error) {
var generatedSecrets []*fn.KubeObject

for _, sopsSecretGeneratorManifest := range rl.Items {
secretManifest := generateSecretManifest([]byte(sopsSecretGeneratorManifest.String()))

secretKubeObject, err := fn.ParseKubeObject([]byte(secretManifest))
if err != nil {
return false, err
}

generatedSecrets = append(generatedSecrets, secretKubeObject)
}

output, err := processSopsSecretGenerator(os.Args[1])
rl.Items = generatedSecrets

return true, nil
}

func generateSecretManifest(manifestContent []byte) string {
output, err := processSopsSecretGenerator(manifestContent)
if err != nil {
if sopsErr, ok := errors.Cause(err).(sops.UserError); ok {
_, _ = fmt.Fprintf(os.Stderr, "Error: %v\n%s\n", err, sopsErr.UserError())
Expand All @@ -80,11 +150,11 @@ func main() {
}
os.Exit(2)
}
fmt.Print(output)
return output
}

func processSopsSecretGenerator(fn string) (string, error) {
input, err := readInput(fn)
func processSopsSecretGenerator(manifestContent []byte) (string, error) {
input, err := readInput(manifestContent)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -133,19 +203,24 @@ func generateSecret(sopsSecret SopsSecretGenerator) (Secret, error) {
return secret, nil
}

func readInput(fn string) (SopsSecretGenerator, error) {
content, err := ioutil.ReadFile(fn)
func readFile(fileName string) ([]byte, error) {
content, err := ioutil.ReadFile(fileName)
if err != nil {
return SopsSecretGenerator{}, err
return []byte{}, err
}

return content, nil
}

func readInput(manifestContent []byte) (SopsSecretGenerator, error) {
input := SopsSecretGenerator{
TypeMeta: TypeMeta{},
ObjectMeta: ObjectMeta{
Annotations: make(kvMap),
},
}
err = yaml.Unmarshal(content, &input)

err := yaml.Unmarshal(manifestContent, &input)
if err != nil {
return SopsSecretGenerator{}, err
}
Expand Down
90 changes: 86 additions & 4 deletions SopsSecretGenerator_test.go
Expand Up @@ -6,11 +6,13 @@ package main
import (
"encoding/base64"
"fmt"
"io/ioutil"
"os"
"reflect"
"strings"
"testing"

"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
"github.com/lithammer/dedent"
"github.com/pkg/errors"
"go.mozilla.org/sops/v3/pgp"
Expand Down Expand Up @@ -59,6 +61,63 @@ func setupGnuPG() error {

// Tests

func Test_GenerateKRMManifest(t *testing.T) {
type args struct {
rlFile string
itemIndex int
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
"SecretFromEnv",
args{"testdata/krm-function-input.yaml", 0},
strings.TrimLeft(dedent.Dedent(`
apiVersion: v1
kind: Secret
metadata:
name: secret-from-env
data:
VAR_ENV: dmFsX2Vudg==
`), "\n"),
false,
},
{
"SecretFromFile",
args{"testdata/krm-function-input.yaml", 1},
strings.TrimLeft(dedent.Dedent(`
apiVersion: v1
kind: Secret
metadata:
name: secret-from-file
data:
file.txt: c2VjcmV0Cg==
`), "\n"),
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
in, _ := ioutil.ReadFile(tt.args.rlFile)
out, err := fn.Run(fn.ResourceListProcessorFunc(generateKRMManifest), in)
if (err != nil) != tt.wantErr {
t.Errorf("generateKRMManifest() error = %v, wantErr %v", err, tt.wantErr)
return
}
rl, _ := fn.ParseResourceList(out)
got := fmt.Sprint(rl.Items[tt.args.itemIndex])
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("generateKRMManifest() got = %v, want %v", got, tt.want)
}
})
})
}
}

func Test_ProcessSopsSecretGenerator(t *testing.T) {
type args struct {
fn string
Expand All @@ -83,11 +142,11 @@ func Test_ProcessSopsSecretGenerator(t *testing.T) {
false,
},
{"InvalidEnvs", args{"testdata/generator-invalidenv.yaml"}, "", true},
{"MissingFile", args{"testdata/missing.yaml"}, "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := processSopsSecretGenerator(tt.args.fn)
sopsSecretGeneratorManifest, _ := ioutil.ReadFile(tt.args.fn)
got, err := processSopsSecretGenerator(sopsSecretGeneratorManifest)
if (err != nil) != tt.wantErr {
t.Errorf("processSopsSecretGenerator() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down Expand Up @@ -183,6 +242,29 @@ func Test_generateSecret(t *testing.T) {
}
}

func Test_readFile(t *testing.T) {
type args struct {
fn string
}
tests := []struct {
name string
args args
wantErr bool
}{
{"ExistingFile", args{"testdata/generator.yaml"}, false},
{"MissingFile", args{"testdata/missing.yaml"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := readFile(tt.args.fn)
if (err != nil) != tt.wantErr {
t.Errorf("readFile() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}

func Test_readInput(t *testing.T) {
type args struct {
fn string
Expand All @@ -195,15 +277,15 @@ func Test_readInput(t *testing.T) {
}{
{"SopsSecretGenerator", args{"testdata/generator.yaml"}, ssg(nil, []string{"testdata/file.txt"}), false},
{"SopsSecret", args{"testdata/generator-oldkind.yaml"}, ssg(nil, []string{"testdata/file.txt"}), false},
{"Missing", args{"testdata/missing.yaml"}, SopsSecretGenerator{}, true},
{"NotYaml", args{"testdata/notyaml.txt"}, SopsSecretGenerator{}, true},
{"WrongVersion", args{"testdata/generator-wrongversion.yaml"}, SopsSecretGenerator{}, true},
{"WrongKind", args{"testdata/generator-wrongkind.yaml"}, SopsSecretGenerator{}, true},
{"NoName", args{"testdata/generator-noname.yaml"}, SopsSecretGenerator{}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := readInput(tt.args.fn)
sopsSecretGeneratorManifest, _ := ioutil.ReadFile(tt.args.fn)
got, err := readInput(sopsSecretGeneratorManifest)
if (err != nil) != tt.wantErr {
t.Errorf("readInput() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down
17 changes: 17 additions & 0 deletions go.mod
Expand Up @@ -3,6 +3,7 @@ module github.com/goabout/kustomize-sopssecretgenerator
go 1.17

require (
github.com/GoogleContainerTools/kpt-functions-sdk/go/fn v0.0.0-20220520201813-68c3f914c65f
github.com/lithammer/dedent v1.1.0
github.com/pkg/errors v0.9.1
go.mozilla.org/sops/v3 v3.7.1
Expand All @@ -23,13 +24,21 @@ require (
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/armon/go-metrics v0.3.10 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/aws/aws-sdk-go v1.42.46 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-logr/logr v1.2.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.3 // indirect
github.com/go-openapi/swag v0.19.5 // indirect
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
Expand Down Expand Up @@ -58,6 +67,7 @@ require (
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/lib/pq v1.10.4 // indirect
github.com/mailru/easyjson v0.7.0 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
Expand All @@ -66,10 +76,14 @@ require (
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/stretchr/testify v1.7.0 // indirect
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
Expand All @@ -89,4 +103,7 @@ require (
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/urfave/cli.v1 v1.20.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
k8s.io/klog/v2 v2.60.1 // indirect
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.6 // indirect
)

0 comments on commit 512053f

Please sign in to comment.