From 27cd82065bdfc0243972db24df1315a6b09797c5 Mon Sep 17 00:00:00 2001 From: Ryan Richard Date: Mon, 27 Jul 2020 16:49:43 -0700 Subject: [PATCH] Add placeholder-name CLI - main and unit tests for main - client package to be done in a future commit Signed-off-by: Aram Price --- cmd/placeholder-name/main.go | 64 +++++++++++++++++ cmd/placeholder-name/main_test.go | 112 ++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + internal/constable/error.go | 14 ++++ pkg/client/client.go | 15 ++++ 6 files changed, 208 insertions(+) create mode 100644 cmd/placeholder-name/main.go create mode 100644 cmd/placeholder-name/main_test.go create mode 100644 internal/constable/error.go create mode 100644 pkg/client/client.go diff --git a/cmd/placeholder-name/main.go b/cmd/placeholder-name/main.go new file mode 100644 index 00000000..acd829fe --- /dev/null +++ b/cmd/placeholder-name/main.go @@ -0,0 +1,64 @@ +/* +Copyright 2020 VMware, Inc. +SPDX-License-Identifier: Apache-2.0 +*/ + +package main + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "k8s.io/client-go/pkg/apis/clientauthentication" + + "github.com/suzerain-io/placeholder-name/internal/constable" + "github.com/suzerain-io/placeholder-name/pkg/client" +) + +func main() { + err := run(os.LookupEnv, client.ExchangeToken, os.Stdout) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%s", err.Error()) + os.Exit(1) + } +} + +type envGetter func(string) (string, bool) +type tokenExchanger func(token, caBundle, apiEndpoint string) (*clientauthentication.ExecCredential, error) + +const EnvVarNotSetError = constable.Error("failed to login: environment variable not set") + +func run(envGetter envGetter, tokenExchanger tokenExchanger, outputWriter io.Writer) error { + token, varExists := envGetter("PLACEHOLDER_NAME_TOKEN") + if !varExists { + return envVarNotSetError("PLACEHOLDER_NAME_TOKEN") + } + + caBundle, varExists := envGetter("PLACEHOLDER_NAME_CA_BUNDLE") + if !varExists { + return envVarNotSetError("PLACEHOLDER_NAME_CA_BUNDLE") + } + + apiEndpoint, varExists := envGetter("PLACEHOLDER_NAME_K8S_API_ENDPOINT") + if !varExists { + return envVarNotSetError("PLACEHOLDER_NAME_K8S_API_ENDPOINT") + } + + execCredential, err := tokenExchanger(token, caBundle, apiEndpoint) + if err != nil { + return fmt.Errorf("failed to login: %w", err) + } + + err = json.NewEncoder(outputWriter).Encode(execCredential) + if err != nil { + return fmt.Errorf("failed to marshall response to stdout: %w", err) + } + + return nil +} + +func envVarNotSetError(varName string) error { + return fmt.Errorf("%w: %s", EnvVarNotSetError, varName) +} diff --git a/cmd/placeholder-name/main_test.go b/cmd/placeholder-name/main_test.go new file mode 100644 index 00000000..55320064 --- /dev/null +++ b/cmd/placeholder-name/main_test.go @@ -0,0 +1,112 @@ +/* +Copyright 2020 VMware, Inc. +SPDX-License-Identifier: Apache-2.0 +*/ + +package main + +import ( + "bytes" + "fmt" + "testing" + + "k8s.io/client-go/pkg/apis/clientauthentication" + + "github.com/stretchr/testify/require" + + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +func TestRun(t *testing.T) { + spec.Run(t, "Run", func(t *testing.T, when spec.G, it spec.S) { + var buffer *bytes.Buffer + var tokenExchanger tokenExchanger + var fakeEnv map[string]string + + var envGetter envGetter = func(envVarName string) (string, bool) { + value, present := fakeEnv[envVarName] + if !present { + return "", false + } + return value, true + } + + it.Before(func() { + buffer = new(bytes.Buffer) + fakeEnv = map[string]string{ + "PLACEHOLDER_NAME_TOKEN": "token from env", + "PLACEHOLDER_NAME_CA_BUNDLE": "ca bundle from env", + "PLACEHOLDER_NAME_K8S_API_ENDPOINT": "k8s api from env", + } + }) + + when("env vars are missing", func() { + it("returns an error when PLACEHOLDER_NAME_TOKEN is missing", func() { + fakeEnv = map[string]string{ + "PLACEHOLDER_NAME_K8S_API_ENDPOINT": "a", + "PLACEHOLDER_NAME_CA_BUNDLE": "b", + } + err := run(envGetter, tokenExchanger, buffer) + require.Error(t, err, "failed to login: environment variable not set: PLACEHOLDER_NAME_TOKEN") + }) + + it("returns an error when PLACEHOLDER_NAME_CA_BUNDLE is missing", func() { + fakeEnv = map[string]string{ + "PLACEHOLDER_NAME_K8S_API_ENDPOINT": "a", + "PLACEHOLDER_NAME_TOKEN": "b", + } + err := run(envGetter, tokenExchanger, buffer) + require.Error(t, err, "failed to login: environment variable not set: PLACEHOLDER_NAME_CA_BUNDLE") + }) + + it("returns an error when PLACEHOLDER_NAME_K8S_API_ENDPOINT is missing", func() { + fakeEnv = map[string]string{ + "PLACEHOLDER_NAME_TOKEN": "a", + "PLACEHOLDER_NAME_CA_BUNDLE": "b", + } + err := run(envGetter, tokenExchanger, buffer) + require.Error(t, err, "failed to login: environment variable not set: PLACEHOLDER_NAME_K8S_API_ENDPOINT") + }) + }, spec.Parallel()) + + when("the token exchange fails", func() { + it.Before(func() { + tokenExchanger = func(token, caBundle, apiEndpoint string) (*clientauthentication.ExecCredential, error) { + return nil, fmt.Errorf("some error") + } + }) + + it("returns an error", func() { + err := run(envGetter, tokenExchanger, buffer) + require.Error(t, err, "failed to login: some error") + }) + }, spec.Parallel()) + + when("the token exchange succeeds", func() { + var actualToken, actualCaBundle, actualAPIEndpoint string + + it.Before(func() { + tokenExchanger = func(token, caBundle, apiEndpoint string) (*clientauthentication.ExecCredential, error) { + actualToken, actualCaBundle, actualAPIEndpoint = token, caBundle, apiEndpoint + return &clientauthentication.ExecCredential{ + Status: &clientauthentication.ExecCredentialStatus{Token: "some token"}, + }, nil + } + }) + + it("writes the execCredential to the given writer", func() { + err := run(envGetter, tokenExchanger, buffer) + require.NoError(t, err) + require.Equal(t, fakeEnv["PLACEHOLDER_NAME_TOKEN"], actualToken) + require.Equal(t, fakeEnv["PLACEHOLDER_NAME_CA_BUNDLE"], actualCaBundle) + require.Equal(t, fakeEnv["PLACEHOLDER_NAME_K8S_API_ENDPOINT"], actualAPIEndpoint) + expected := `{ + "Spec": {"Interactive": false, "Response": null}, + "Status": {"ClientCertificateData": "", "ClientKeyData": "", "ExpirationTimestamp": null, "Token": "some token"} + }` + require.JSONEq(t, expected, buffer.String()) + }) + }, spec.Parallel()) + }, spec.Report(report.Terminal{})) +} diff --git a/go.mod b/go.mod index 0091acad..7764b378 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/golang/mock v1.4.3 github.com/golangci/golangci-lint v1.28.1 github.com/google/go-cmp v0.4.0 + github.com/sclevine/spec v1.4.0 github.com/spf13/cobra v1.0.0 github.com/stretchr/testify v1.6.1 github.com/suzerain-io/placeholder-name-api v0.0.0-20200724000517-dc602fd8d75e diff --git a/go.sum b/go.sum index 38f799c9..503ef603 100644 --- a/go.sum +++ b/go.sum @@ -467,6 +467,8 @@ github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUcc github.com/ryanrolds/sqlclosecheck v0.3.0 h1:AZx+Bixh8zdUBxUA1NxbxVAS78vTPq4rCb8OUZI9xFw= github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= +github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/securego/gosec/v2 v2.3.0 h1:y/9mCF2WPDbSDpL3QDWZD3HHGrSYw0QSHnCqTfs4JPE= github.com/securego/gosec/v2 v2.3.0/go.mod h1:UzeVyUXbxukhLeHKV3VVqo7HdoQR9MrRfFmZYotn8ME= diff --git a/internal/constable/error.go b/internal/constable/error.go new file mode 100644 index 00000000..462d26a4 --- /dev/null +++ b/internal/constable/error.go @@ -0,0 +1,14 @@ +/* +Copyright 2020 VMware, Inc. +SPDX-License-Identifier: Apache-2.0 +*/ + +package constable + +var _ error = Error("") + +type Error string + +func (e Error) Error() string { + return string(e) +} diff --git a/pkg/client/client.go b/pkg/client/client.go new file mode 100644 index 00000000..c878771a --- /dev/null +++ b/pkg/client/client.go @@ -0,0 +1,15 @@ +/* +Copyright 2020 VMware, Inc. +SPDX-License-Identifier: Apache-2.0 +*/ + +package client + +import "k8s.io/client-go/pkg/apis/clientauthentication" + +func ExchangeToken(token, caBundle, apiEndpoint string) (*clientauthentication.ExecCredential, error) { + _ = token + _ = caBundle + _ = apiEndpoint + return nil, nil +}