Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
439: Add -f and -w flags as an alternative to stdin/out r=mkmik a=mkmik

Also closes bitnami-labs#405

This PR intentionally doesn't document the new flags in the README since
those changes would be visible before this PR gets released.

Co-authored-by: Marko Mikulicic <mkmik@vmware.com>
  • Loading branch information
bors[bot] and Marko Mikulicic authored Jul 29, 2020
2 parents 95a7c2c + 0cf5e35 commit 946a69e
Show file tree
Hide file tree
Showing 13 changed files with 679 additions and 12 deletions.
57 changes: 48 additions & 9 deletions cmd/kubeseal/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import (
"net/http"
"net/url"
"os"
"path/filepath"
"strings"

"github.com/bitnami-labs/sealed-secrets/pkg/buildinfo"
"github.com/bitnami-labs/sealed-secrets/pkg/crypto"
"github.com/google/renameio"
"github.com/mattn/go-isatty"
flag "github.com/spf13/pflag"
v1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -52,6 +54,8 @@ var (
controllerNs = flag.String("controller-namespace", metav1.NamespaceSystem, "Namespace of sealed-secrets controller.")
controllerName = flag.String("controller-name", "sealed-secrets-controller", "Name of sealed-secrets controller.")
outputFormat = flag.StringP("format", "o", "json", "Output format for sealed secret. Either json or yaml")
outputFileName = flag.StringP("sealed-secret-file", "w", "", "Sealed-secret (output) file")
inputFileName = flag.StringP("secret-file", "f", "", "Secret (input) file")
dumpCert = flag.Bool("fetch-cert", false, "Write certificate to stdout. Useful for later use with --cert")
allowEmptyData = flag.Bool("allow-empty-data", false, "Allow empty data in the secret object")
printVersion = flag.Bool("version", false, "Print version information and exit")
Expand Down Expand Up @@ -576,7 +580,7 @@ func unsealSealedSecret(w io.Writer, in io.Reader, codecs runtimeserializer.Code
return resourceOutput(w, codecs, v1.SchemeGroupVersion, sec)
}

func run(w io.Writer, secretName, controllerNs, controllerName, certURL string, printVersion, validateSecret, reEncrypt, dumpCert, raw, allowEmptyData bool, fromFile []string, mergeInto string, unseal bool, privKeys []string) error {
func run(w io.Writer, inputFileName, outputFileName, secretName, controllerNs, controllerName, certURL string, printVersion, validateSecret, reEncrypt, dumpCert, raw, allowEmptyData bool, fromFile []string, mergeInto string, unseal bool, privKeys []string) (err error) {
if len(fromFile) != 0 && !raw {
return fmt.Errorf("--from-file requires --raw")
}
Expand All @@ -586,25 +590,60 @@ func run(w io.Writer, secretName, controllerNs, controllerName, certURL string,
return nil
}

if !raw && !dumpCert {
var input io.Reader = os.Stdin
if inputFileName != "" {
f, err := os.Open(inputFileName)
if err != nil {
return nil
}
defer f.Close()

input = f
} else if !raw && !dumpCert {
if isatty.IsTerminal(os.Stdin.Fd()) {
fmt.Fprintf(os.Stderr, "(tty detected: expecting json/yaml k8s resource in stdin)\n")
}
}

// reEncrypt is the only "in-place" update subcommand. When the user only provides one file (the input file)
// we'll use the same file for output (see #405).
if reEncrypt && (outputFileName == "" && inputFileName != "") {
outputFileName = inputFileName
}
if outputFileName != "" {
// TODO(mkm): get rid of these horrible global variables
if ext := filepath.Ext(outputFileName); ext == ".yaml" || ext == ".yml" {
*outputFormat = "yaml"
}

var f *renameio.PendingFile
f, err = renameio.TempFile("", outputFileName)
if err != nil {
return err
}
// only write the output file if the run function exits without errors.
defer func() {
if err == nil {
f.CloseAtomicallyReplace()
}
}()

w = f
}

if unseal {
return unsealSealedSecret(w, os.Stdin, scheme.Codecs, privKeys)
return unsealSealedSecret(w, input, scheme.Codecs, privKeys)
}
if len(privKeys) != 0 && isatty.IsTerminal(os.Stderr.Fd()) {
fmt.Fprintf(os.Stderr, "warning: ignoring --recovery-private-key because unseal command not chosen with --recovery-unseal\n")
}

if validateSecret {
return validateSealedSecret(os.Stdin, controllerNs, controllerName)
return validateSealedSecret(input, controllerNs, controllerName)
}

if reEncrypt {
return reEncryptSealedSecret(os.Stdin, os.Stdout, scheme.Codecs, controllerNs, controllerName)
return reEncryptSealedSecret(input, w, scheme.Codecs, controllerNs, controllerName)
}

f, err := openCert(certURL)
Expand All @@ -614,7 +653,7 @@ func run(w io.Writer, secretName, controllerNs, controllerName, certURL string,
defer f.Close()

if dumpCert {
_, err := io.Copy(os.Stdout, f)
_, err := io.Copy(w, f)
return err
}

Expand All @@ -624,7 +663,7 @@ func run(w io.Writer, secretName, controllerNs, controllerName, certURL string,
}

if mergeInto != "" {
return sealMergingInto(os.Stdin, mergeInto, scheme.Codecs, pubKey, sealingScope, allowEmptyData)
return sealMergingInto(input, mergeInto, scheme.Codecs, pubKey, sealingScope, allowEmptyData)
}

if raw {
Expand Down Expand Up @@ -662,14 +701,14 @@ func run(w io.Writer, secretName, controllerNs, controllerName, certURL string,
return encryptSecretItem(w, secretName, ns, data, sealingScope, pubKey)
}

return seal(os.Stdin, os.Stdout, scheme.Codecs, pubKey, sealingScope, allowEmptyData, secretName, "")
return seal(input, w, scheme.Codecs, pubKey, sealingScope, allowEmptyData, secretName, "")
}

func main() {
flag.Parse()
goflag.CommandLine.Parse([]string{})

if err := run(os.Stdout, *secretName, *controllerNs, *controllerName, *certURL, *printVersion, *validateSecret, reEncrypt, *dumpCert, *raw, *allowEmptyData, *fromFile, *mergeInto, *unseal, *privKeys); err != nil {
if err := run(os.Stdout, *inputFileName, *outputFileName, *secretName, *controllerNs, *controllerName, *certURL, *printVersion, *validateSecret, reEncrypt, *dumpCert, *raw, *allowEmptyData, *fromFile, *mergeInto, *unseal, *privKeys); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
Expand Down
97 changes: 94 additions & 3 deletions cmd/kubeseal/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ func TestMergeInto(t *testing.T) {

func TestVersion(t *testing.T) {
var buf strings.Builder
err := run(&buf, "", "", "", "", true, false, false, false, false, false, nil, "", false, nil)
err := run(&buf, "", "", "", "", "", "", true, false, false, false, false, false, nil, "", false, nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -656,7 +656,7 @@ func TestVersion(t *testing.T) {

func TestMainError(t *testing.T) {
const badFileName = "/?this/file/cannot/possibly/exist/can/it?"
err := run(ioutil.Discard, "", "", "", badFileName, false, false, false, false, false, false, nil, "", false, nil)
err := run(ioutil.Discard, "", "", "", "", "", badFileName, false, false, false, false, false, false, nil, "", false, nil)

if err == nil || !os.IsNotExist(err) {
t.Fatalf("expecting not exist error, got: %v", err)
Expand Down Expand Up @@ -818,13 +818,104 @@ func sealTestItem(certFilename, secretNS, secretName, secretValue string, scope
fromFile := []string{dataFile}

var buf bytes.Buffer
if err := run(&buf, secretName, "", "", certFilename, false, false, false, false, true, false, fromFile, "", false, nil); err != nil {
if err := run(&buf, "", "", secretName, "", "", certFilename, false, false, false, false, true, false, fromFile, "", false, nil); err != nil {
return "", err
}

return buf.String(), nil
}

func TestWriteToFile(t *testing.T) {
certFilename, _, cleanup := testingKeypairFiles(t)
defer cleanup()

in, err := ioutil.TempFile("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(in.Name())
fmt.Fprintf(in, `apiVersion: v1
kind: Secret
metadata:
name: foo
namespace: bar
data:
super: c2VjcmV0
`)
in.Close()

out, err := ioutil.TempFile("", "*.yaml")
if err != nil {
t.Fatal(err)
}
out.Close()
defer os.RemoveAll(out.Name())

var buf bytes.Buffer
if err := run(&buf, in.Name(), out.Name(), "", "", "", certFilename, false, false, false, false, false, false, nil, "", false, nil); err != nil {
t.Fatal(err)
}

if got, want := buf.Len(), 0; got != want {
t.Errorf("got: %d, want: %d", got, want)
}

b, err := ioutil.ReadFile(out.Name())
if err != nil {
t.Fatal(err)
}
if sub := "kind: SealedSecret"; !bytes.Contains(b, []byte(sub)) {
t.Errorf("expecting to find %q in %q", sub, b)
}
}

func TestFailToWriteToFile(t *testing.T) {
certFilename, _, cleanup := testingKeypairFiles(t)
defer cleanup()

in, err := ioutil.TempFile("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(in.Name())
fmt.Fprintf(in, `apiVersion: v1
kind: BadInput
metadata:
name: foo
namespace: bar
`)
in.Close()

out, err := ioutil.TempFile("", "")
if err != nil {
t.Fatal(err)
}

// if sealing error happens, the old content of the output file shouldn't be truncated.
const testOldContent = "previous content"

fmt.Fprint(out, testOldContent)
out.Close()
defer os.RemoveAll(out.Name())

var buf bytes.Buffer
if err := run(&buf, in.Name(), out.Name(), "", "", "", certFilename, false, false, false, false, false, false, nil, "", false, nil); err == nil {
t.Errorf("expecting error")
}

if got, want := buf.Len(), 0; got != want {
t.Errorf("got: %d, want: %d", got, want)
}

b, err := ioutil.ReadFile(out.Name())
if err != nil {
t.Fatal(err)
}
if got, want := string(b), testOldContent; got != want {
t.Errorf("got: %q, want: %q", got, want)
}
}

// writeTempFile creates a temporary file, writes data into it and closes it.
func writeTempFile(b []byte) (string, error) {
tmp, err := ioutil.TempFile("", "")
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/google/go-cmp v0.3.0
github.com/google/gofuzz v1.0.0
github.com/google/renameio v0.1.0
github.com/googleapis/gnostic v0.0.0-20171211024024-933c109c13ce // indirect
github.com/mattn/go-isatty v0.0.10
github.com/mkmik/multierror v0.3.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
Expand Down
16 changes: 16 additions & 0 deletions vendor/github.com/google/renameio/.travis.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions vendor/github.com/google/renameio/CONTRIBUTING.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 946a69e

Please sign in to comment.