From da7c981f14abb5586714201cc005e9b58a8a2c02 Mon Sep 17 00:00:00 2001 From: Ryan Richard Date: Fri, 11 Sep 2020 17:56:05 -0700 Subject: [PATCH] Organize Pinniped CLI into subcommands; Add get-kubeconfig subcommand - Add flag parsing and help messages for root command, `exchange-credential` subcommand, and new `get-kubeconfig` subcommand - The new `get-kubeconfig` subcommand is a work in progress in this commit - Also add here.Doc() and here.Docf() to enable nice heredocs in our code --- cmd/pinniped/cmd/exchange_credential.go | 102 +++++++++++++ .../exchange_credential_test.go} | 20 +-- cmd/pinniped/cmd/get_kubeconfig.go | 136 ++++++++++++++++++ cmd/pinniped/cmd/get_kubeconfig_test.go | 76 ++++++++++ cmd/pinniped/cmd/root.go | 30 ++++ cmd/pinniped/main.go | 61 +------- go.mod | 2 + go.sum | 38 +---- internal/here/doc.go | 25 ++++ internal/here/doc_test.go | 104 ++++++++++++++ 10 files changed, 490 insertions(+), 104 deletions(-) create mode 100644 cmd/pinniped/cmd/exchange_credential.go rename cmd/pinniped/{main_test.go => cmd/exchange_credential_test.go} (86%) create mode 100644 cmd/pinniped/cmd/get_kubeconfig.go create mode 100644 cmd/pinniped/cmd/get_kubeconfig_test.go create mode 100644 cmd/pinniped/cmd/root.go create mode 100644 internal/here/doc.go create mode 100644 internal/here/doc_test.go diff --git a/cmd/pinniped/cmd/exchange_credential.go b/cmd/pinniped/cmd/exchange_credential.go new file mode 100644 index 00000000..a9f3566f --- /dev/null +++ b/cmd/pinniped/cmd/exchange_credential.go @@ -0,0 +1,102 @@ +/* +Copyright 2020 VMware, Inc. +SPDX-License-Identifier: Apache-2.0 +*/ + +package cmd + +import ( + "context" + "encoding/json" + "fmt" + "io" + "os" + "time" + + "github.com/spf13/cobra" + clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" + + "github.com/suzerain-io/pinniped/internal/client" + "github.com/suzerain-io/pinniped/internal/constable" + "github.com/suzerain-io/pinniped/internal/here" +) + +//nolint: gochecknoinits +func init() { + exchangeCredentialCmd := &cobra.Command{ + Run: runExchangeCredential, + Args: cobra.NoArgs, // do not accept positional arguments for this command + Use: "exchange-credential", + Short: "Exchange a credential for a cluster-specific access credential", + Long: here.Doc(` + Exchange a credential which proves your identity for a time-limited, + cluster-specific access credential. + + Designed to be conveniently used as an credential plugin for kubectl. + See the help message for 'pinniped get-kubeconfig' for more + information about setting up a kubeconfig file using Pinniped. + + Requires all of the following environment variables, which are + typically set in the kubeconfig: + - PINNIPED_TOKEN: the token to send to Pinniped for exchange + - PINNIPED_CA_BUNDLE: the CA bundle to trust when calling + Pinniped's HTTPS endpoint + - PINNIPED_K8S_API_ENDPOINT: the URL for the Pinniped credential + exchange API + + For more information about credential plugins in general, see + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins + `), + } + + rootCmd.AddCommand(exchangeCredentialCmd) +} + +type envGetter func(string) (string, bool) +type tokenExchanger func(ctx context.Context, token, caBundle, apiEndpoint string) (*clientauthenticationv1beta1.ExecCredential, error) + +const ErrMissingEnvVar = constable.Error("failed to get credential: environment variable not set") + +func runExchangeCredential(_ *cobra.Command, _ []string) { + err := exchangeCredential(os.LookupEnv, client.ExchangeToken, os.Stdout, 30*time.Second) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%s\n", err.Error()) + os.Exit(1) + } +} + +func exchangeCredential(envGetter envGetter, tokenExchanger tokenExchanger, outputWriter io.Writer, timeout time.Duration) error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + token, varExists := envGetter("PINNIPED_TOKEN") + if !varExists { + return envVarNotSetError("PINNIPED_TOKEN") + } + + caBundle, varExists := envGetter("PINNIPED_CA_BUNDLE") + if !varExists { + return envVarNotSetError("PINNIPED_CA_BUNDLE") + } + + apiEndpoint, varExists := envGetter("PINNIPED_K8S_API_ENDPOINT") + if !varExists { + return envVarNotSetError("PINNIPED_K8S_API_ENDPOINT") + } + + cred, err := tokenExchanger(ctx, token, caBundle, apiEndpoint) + if err != nil { + return fmt.Errorf("failed to get credential: %w", err) + } + + err = json.NewEncoder(outputWriter).Encode(cred) + if err != nil { + return fmt.Errorf("failed to marshal response to stdout: %w", err) + } + + return nil +} + +func envVarNotSetError(varName string) error { + return fmt.Errorf("%w: %s", ErrMissingEnvVar, varName) +} diff --git a/cmd/pinniped/main_test.go b/cmd/pinniped/cmd/exchange_credential_test.go similarity index 86% rename from cmd/pinniped/main_test.go rename to cmd/pinniped/cmd/exchange_credential_test.go index 261fa50b..db834020 100644 --- a/cmd/pinniped/main_test.go +++ b/cmd/pinniped/cmd/exchange_credential_test.go @@ -3,7 +3,7 @@ Copyright 2020 VMware, Inc. SPDX-License-Identifier: Apache-2.0 */ -package main +package cmd import ( "bytes" @@ -21,8 +21,8 @@ import ( "github.com/suzerain-io/pinniped/internal/testutil" ) -func TestRun(t *testing.T) { - spec.Run(t, "main.run", func(t *testing.T, when spec.G, it spec.S) { +func TestExchangeCredential(t *testing.T) { + spec.Run(t, "cmd.exchangeCredential", func(t *testing.T, when spec.G, it spec.S) { var r *require.Assertions var buffer *bytes.Buffer var tokenExchanger tokenExchanger @@ -49,19 +49,19 @@ func TestRun(t *testing.T) { when("env vars are missing", func() { it("returns an error when PINNIPED_TOKEN is missing", func() { delete(fakeEnv, "PINNIPED_TOKEN") - err := run(envGetter, tokenExchanger, buffer, 30*time.Second) + err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second) r.EqualError(err, "failed to get credential: environment variable not set: PINNIPED_TOKEN") }) it("returns an error when PINNIPED_CA_BUNDLE is missing", func() { delete(fakeEnv, "PINNIPED_CA_BUNDLE") - err := run(envGetter, tokenExchanger, buffer, 30*time.Second) + err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second) r.EqualError(err, "failed to get credential: environment variable not set: PINNIPED_CA_BUNDLE") }) it("returns an error when PINNIPED_K8S_API_ENDPOINT is missing", func() { delete(fakeEnv, "PINNIPED_K8S_API_ENDPOINT") - err := run(envGetter, tokenExchanger, buffer, 30*time.Second) + err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second) r.EqualError(err, "failed to get credential: environment variable not set: PINNIPED_K8S_API_ENDPOINT") }) }) @@ -74,7 +74,7 @@ func TestRun(t *testing.T) { }) it("returns an error", func() { - err := run(envGetter, tokenExchanger, buffer, 30*time.Second) + err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second) r.EqualError(err, "failed to get credential: some error") }) }) @@ -91,7 +91,7 @@ func TestRun(t *testing.T) { }) it("returns an error", func() { - err := run(envGetter, tokenExchanger, &testutil.ErrorWriter{ReturnError: fmt.Errorf("some IO error")}, 30*time.Second) + err := exchangeCredential(envGetter, tokenExchanger, &testutil.ErrorWriter{ReturnError: fmt.Errorf("some IO error")}, 30*time.Second) r.EqualError(err, "failed to marshal response to stdout: some IO error") }) }) @@ -113,7 +113,7 @@ func TestRun(t *testing.T) { }) it("returns an error", func() { - err := run(envGetter, tokenExchanger, buffer, 1*time.Millisecond) + err := exchangeCredential(envGetter, tokenExchanger, buffer, 1*time.Millisecond) r.EqualError(err, "failed to get credential: context deadline exceeded") }) }) @@ -141,7 +141,7 @@ func TestRun(t *testing.T) { }) it("writes the execCredential to the given writer", func() { - err := run(envGetter, tokenExchanger, buffer, 30*time.Second) + err := exchangeCredential(envGetter, tokenExchanger, buffer, 30*time.Second) r.NoError(err) r.Equal(fakeEnv["PINNIPED_TOKEN"], actualToken) r.Equal(fakeEnv["PINNIPED_CA_BUNDLE"], actualCaBundle) diff --git a/cmd/pinniped/cmd/get_kubeconfig.go b/cmd/pinniped/cmd/get_kubeconfig.go new file mode 100644 index 00000000..7bceadc1 --- /dev/null +++ b/cmd/pinniped/cmd/get_kubeconfig.go @@ -0,0 +1,136 @@ +/* +Copyright 2020 VMware, Inc. +SPDX-License-Identifier: Apache-2.0 +*/ + +package cmd + +import ( + "fmt" + "io" + "os" + + "github.com/ghodss/yaml" + "github.com/spf13/cobra" + clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" + v1 "k8s.io/client-go/tools/clientcmd/api/v1" + + "github.com/suzerain-io/pinniped/internal/here" +) + +const ( + getKubeConfigCmdTokenFlagName = "token" +) + +//nolint: gochecknoinits +func init() { + getKubeConfigCmd := &cobra.Command{ + Run: runGetKubeConfig, + Args: cobra.NoArgs, // do not accept positional arguments for this command + Use: "get-kubeconfig", + Short: "Print a kubeconfig for authenticating into a cluster via Pinniped", + Long: here.Doc(` + Print a kubeconfig for authenticating into a cluster via Pinniped. + + Assumes that you have admin-like access to the cluster using your + current kubeconfig context, in order to access Pinniped's metadata. + + Prints a kubeconfig which is suitable to access the cluster using + Pinniped as the authentication mechanism. This kubeconfig output + can be saved to a file and used with future kubectl commands, e.g.: + pinniped get-kubeconfig --token $MY_TOKEN > $HOME/mycluster-kubeconfig + kubectl --kubeconfig $HOME/mycluster-kubeconfig get pods + `), + } + + rootCmd.AddCommand(getKubeConfigCmd) + + getKubeConfigCmd.Flags().StringP( + getKubeConfigCmdTokenFlagName, + "t", + "", + "The credential to include in the resulting kubeconfig output (Required)", + ) + err := getKubeConfigCmd.MarkFlagRequired(getKubeConfigCmdTokenFlagName) + if err != nil { + panic(err) + } +} + +func runGetKubeConfig(cmd *cobra.Command, _ []string) { + token := cmd.Flag(getKubeConfigCmdTokenFlagName).Value.String() + + err := getKubeConfig(os.Stdout, token) + + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) + os.Exit(1) + } +} + +func getKubeConfig(outputWriter io.Writer, token string) error { + clusterName := "pinniped-cluster" + userName := "pinniped-user" + + fullPathToSelf, err := os.Executable() + if err != nil { + return fmt.Errorf("could not find path to self: %w", err) + } + + config := v1.Config{ + Kind: "Config", + APIVersion: v1.SchemeGroupVersion.Version, + Preferences: v1.Preferences{ + Colors: false, // TODO what does this setting do? + Extensions: nil, + }, + Clusters: []v1.NamedCluster{ + { + Name: clusterName, + Cluster: v1.Cluster{}, // TODO fill in server and cert authority and such + }, + }, + AuthInfos: []v1.NamedAuthInfo{ + { + Name: userName, + AuthInfo: v1.AuthInfo{ + Exec: &v1.ExecConfig{ + Command: fullPathToSelf, + Args: []string{"exchange-credential"}, + Env: []v1.ExecEnvVar{ + {Name: "PINNIPED_K8S_API_ENDPOINT", Value: ""}, // TODO fill in value + {Name: "PINNIPED_CA_BUNDLE", Value: ""}, // TODO fill in value + {Name: "PINNIPED_TOKEN", Value: token}, + }, + APIVersion: clientauthenticationv1beta1.SchemeGroupVersion.String(), + InstallHint: "The Pinniped CLI is required to authenticate to the current cluster.\n" + + "For more information, please visit https://pinniped.dev", + }, + }, + }, + }, + Contexts: []v1.NamedContext{ + { + Name: clusterName, + Context: v1.Context{ + Cluster: clusterName, + AuthInfo: userName, + }, + }, + }, + CurrentContext: clusterName, + Extensions: nil, + } + + output, err := yaml.Marshal(&config) + if err != nil { + return fmt.Errorf("YAML serialization error: %w", err) + } + + _, err = fmt.Fprint(outputWriter, string(output)) + if err != nil { + return fmt.Errorf("output write error: %w", err) + } + + return nil +} diff --git a/cmd/pinniped/cmd/get_kubeconfig_test.go b/cmd/pinniped/cmd/get_kubeconfig_test.go new file mode 100644 index 00000000..feccb96b --- /dev/null +++ b/cmd/pinniped/cmd/get_kubeconfig_test.go @@ -0,0 +1,76 @@ +/* +Copyright 2020 VMware, Inc. +SPDX-License-Identifier: Apache-2.0 +*/ + +package cmd + +import ( + "bytes" + "os" + "testing" + + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + "github.com/stretchr/testify/require" + + "github.com/suzerain-io/pinniped/internal/here" +) + +func TestGetKubeConfig(t *testing.T) { + spec.Run(t, "cmd.getKubeConfig", func(t *testing.T, when spec.G, it spec.S) { + var r *require.Assertions + var buffer *bytes.Buffer + var fullPathToSelf string + + it.Before(func() { + r = require.New(t) + buffer = new(bytes.Buffer) + + var err error + fullPathToSelf, err = os.Executable() + r.NoError(err) + }) + + it("writes the kubeconfig to the given writer", func() { + err := getKubeConfig(buffer, "some-token") + r.NoError(err) + + expectedYAML := here.Docf(` + apiVersion: v1 + clusters: + - cluster: + server: "" + name: pinniped-cluster + contexts: + - context: + cluster: pinniped-cluster + user: pinniped-user + name: pinniped-cluster + current-context: pinniped-cluster + kind: Config + preferences: {} + users: + - name: pinniped-user + user: + exec: + apiVersion: client.authentication.k8s.io/v1beta1 + args: + - exchange-credential + command: %s + env: + - name: PINNIPED_K8S_API_ENDPOINT + value: "" + - name: PINNIPED_CA_BUNDLE + value: "" + - name: PINNIPED_TOKEN + value: some-token + installHint: |- + The Pinniped CLI is required to authenticate to the current cluster. + For more information, please visit https://pinniped.dev + `, fullPathToSelf) + + r.Equal(expectedYAML, buffer.String()) + }) + }, spec.Parallel(), spec.Report(report.Terminal{})) +} diff --git a/cmd/pinniped/cmd/root.go b/cmd/pinniped/cmd/root.go new file mode 100644 index 00000000..3ad05292 --- /dev/null +++ b/cmd/pinniped/cmd/root.go @@ -0,0 +1,30 @@ +/* +Copyright 2020 VMware, Inc. +SPDX-License-Identifier: Apache-2.0 +*/ + +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +//nolint: gochecknoglobals +var rootCmd = &cobra.Command{ + Use: "pinniped", + Short: "pinniped", + Long: "pinniped is the client-side binary for use with Pinniped-enabled Kubernetes clusters.", + SilenceUsage: true, // do not print usage message when commands fail +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/cmd/pinniped/main.go b/cmd/pinniped/main.go index b232d400..314019af 100644 --- a/cmd/pinniped/main.go +++ b/cmd/pinniped/main.go @@ -5,65 +5,8 @@ SPDX-License-Identifier: Apache-2.0 package main -import ( - "context" - "encoding/json" - "fmt" - "io" - "os" - "time" - - clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" - - "github.com/suzerain-io/pinniped/internal/client" - "github.com/suzerain-io/pinniped/internal/constable" -) +import "github.com/suzerain-io/pinniped/cmd/pinniped/cmd" func main() { - err := run(os.LookupEnv, client.ExchangeToken, os.Stdout, 30*time.Second) - if err != nil { - _, _ = fmt.Fprintf(os.Stderr, "%s\n", err.Error()) - os.Exit(1) - } -} - -type envGetter func(string) (string, bool) -type tokenExchanger func(ctx context.Context, token, caBundle, apiEndpoint string) (*clientauthenticationv1beta1.ExecCredential, error) - -const ErrMissingEnvVar = constable.Error("failed to get credential: environment variable not set") - -func run(envGetter envGetter, tokenExchanger tokenExchanger, outputWriter io.Writer, timeout time.Duration) error { - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - token, varExists := envGetter("PINNIPED_TOKEN") - if !varExists { - return envVarNotSetError("PINNIPED_TOKEN") - } - - caBundle, varExists := envGetter("PINNIPED_CA_BUNDLE") - if !varExists { - return envVarNotSetError("PINNIPED_CA_BUNDLE") - } - - apiEndpoint, varExists := envGetter("PINNIPED_K8S_API_ENDPOINT") - if !varExists { - return envVarNotSetError("PINNIPED_K8S_API_ENDPOINT") - } - - cred, err := tokenExchanger(ctx, token, caBundle, apiEndpoint) - if err != nil { - return fmt.Errorf("failed to get credential: %w", err) - } - - err = json.NewEncoder(outputWriter).Encode(cred) - if err != nil { - return fmt.Errorf("failed to marshal response to stdout: %w", err) - } - - return nil -} - -func envVarNotSetError(varName string) error { - return fmt.Errorf("%w: %s", ErrMissingEnvVar, varName) + cmd.Execute() } diff --git a/go.mod b/go.mod index 246020d6..65cdc141 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,9 @@ module github.com/suzerain-io/pinniped go 1.14 require ( + github.com/MakeNowJust/heredoc/v2 v2.0.1 github.com/davecgh/go-spew v1.1.1 + github.com/ghodss/yaml v1.0.0 github.com/go-logr/logr v0.2.1 github.com/golang/mock v1.4.4 github.com/golangci/golangci-lint v1.31.0 diff --git a/go.sum b/go.sum index fb22470c..3fb7c565 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5 h1:XTrzB+F8+SpRmbhAH8HLxhiiG6nYNwaBZjrFps1oWEk= github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A= +github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46 h1:lsxEuwrXEAokXB9qhlbKWPpo3KMLZQ5WB5WLQRW1uq0= @@ -98,8 +100,6 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/daixiang0/gci v0.0.0-20200727065011-66f1df783cb2 h1:3Lhhps85OdA8ezsEKu+IA1hE+DBTjt/fjd7xNCrHbVA= -github.com/daixiang0/gci v0.0.0-20200727065011-66f1df783cb2/go.mod h1:+AV8KmHTGxxwp/pY84TLQfFKp2vuKXXJVzF3kD/hfR4= github.com/daixiang0/gci v0.2.4 h1:BUCKk5nlK2m+kRIsoj+wb/5hazHvHeZieBKWd9Afa8Q= github.com/daixiang0/gci v0.2.4/go.mod h1:+AV8KmHTGxxwp/pY84TLQfFKp2vuKXXJVzF3kD/hfR4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -132,16 +132,14 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-critic/go-critic v0.5.0 h1:Ic2p5UCl5fX/2WX2w8nroPpPhxRNsNTMlJzsu/uqwnM= -github.com/go-critic/go-critic v0.5.0/go.mod h1:4jeRh3ZAVnRYhuWdOEvwzVqLUpxMSoAT0xZ74JsTPlo= github.com/go-critic/go-critic v0.5.2 h1:3RJdgf6u4NZUumoP8nzbqiiNT8e1tC2Oc7jlgqre/IA= github.com/go-critic/go-critic v0.5.2/go.mod h1:cc0+HvdE3lFpqLecgqMaJcvWWH77sLdBp+wLGPM1Yyo= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= @@ -175,17 +173,13 @@ github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8= github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= -github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= github.com/go-toolsmith/astequal v1.0.0 h1:4zxD8j3JRFNyLN46lodQuqz3xdKSrur7U/sr0SDS/gQ= github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= -github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k= github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= -github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg= github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= -github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= github.com/go-toolsmith/pkgload v1.0.0 h1:4DFWWMXVfbcN5So1sBNW9+yeiMqLFGl1wFLTL5R0Tgg= github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4= @@ -197,8 +191,6 @@ github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4 github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gofrs/flock v0.7.1 h1:DP+LD/t0njgoPBvT5MJLeliUIVQR03hiKR6vezdwHlc= -github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY= github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -244,8 +236,6 @@ github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d h1:pXTK/gkVNs7Zyy github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks= github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= -github.com/golangci/golangci-lint v1.30.0 h1:UhdK5WbO0GBd7W+k2lOD7BEJH4Wsa7zKfw8m3/aEJGQ= -github.com/golangci/golangci-lint v1.30.0/go.mod h1:5t0i3wHlqQc9deBBvZsP+a/4xz7cfjV+zhp5U0Mzp14= github.com/golangci/golangci-lint v1.31.0 h1:+m9I3LEmxXLpymkXRPkDQGzOVBmBYm16UtDiXqZxWek= github.com/golangci/golangci-lint v1.31.0/go.mod h1:aMQuNCA+NDU5+4jLL5pEuFHoue0IznKE2+/GsFvvs8A= github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI= @@ -358,8 +348,6 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.10.5/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -436,8 +424,6 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1 github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nishanths/exhaustive v0.0.0-20200708172631-8866003e3856 h1:W3KBC2LFyfgd+wNudlfgCCsTo4q97MeNWrfz8/wSdSc= -github.com/nishanths/exhaustive v0.0.0-20200708172631-8866003e3856/go.mod h1:wBEpHwM2OdmeNpdCvRPUlkEbBuaFmcK4Wv8Q7FuGW3c= github.com/nishanths/exhaustive v0.0.0-20200811152831-6cf413ae40e0 h1:eMV1t2NQRc3r1k3guWiv/zEeqZZP6kPvpUfy6byfL1g= github.com/nishanths/exhaustive v0.0.0-20200811152831-6cf413ae40e0/go.mod h1:wBEpHwM2OdmeNpdCvRPUlkEbBuaFmcK4Wv8Q7FuGW3c= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= @@ -492,8 +478,6 @@ github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFB github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= -github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8 h1:DvnesvLtRPQOvaUbfXfh0tpMHg29by0H7F2U+QIkSu8= -github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8/go.mod h1:CGFX09Ci3pq9QZdj86B+VGIdNj4VyCo2iPOGS9esB/k= github.com/quasilyte/go-ruleguard v0.2.0 h1:UOVMyH2EKkxIfzrULvA9n/tO+HtEhqD9mrLSWMr5FwU= github.com/quasilyte/go-ruleguard v0.2.0/go.mod h1:2RT/tf0Ce0UDj5y243iWKosQogJd8+1G3Rs2fxmlYnw= github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95 h1:L8QM9bvf68pVdQ3bCFZMDmnt9yqcMBro1pC7F+IPYMY= @@ -534,8 +518,6 @@ github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sonatard/noctx v0.0.1 h1:VC1Qhl6Oxx9vvWo3UDgrGXYCeKCe3Wbw7qAWL6FrmTY= github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI= -github.com/sourcegraph/go-diff v0.5.3 h1:lhIKJ2nXLZZ+AfbHpYxTn0pXpNTTui0DX7DO3xeb1Zs= -github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak= github.com/sourcegraph/go-diff v0.6.0 h1:WbN9e/jD8ujU+o0vd9IFN5AEwtfB0rn/zM/AANaClqQ= github.com/sourcegraph/go-diff v0.6.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -555,12 +537,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/ssgreg/nlreturn/v2 v2.0.1 h1:+lm6xFjVuNw/9t/Fh5sIwfNWefiD5bddzc6vwJ1TvRI= -github.com/ssgreg/nlreturn/v2 v2.0.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/ssgreg/nlreturn/v2 v2.1.0 h1:6/s4Rc49L6Uo6RLjhWZGBpWWjfzk2yrf1nIW8m4wgVA= github.com/ssgreg/nlreturn/v2 v2.1.0/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -587,8 +565,6 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa h1:RC4maTWLKKwb7p1cnoygsbKIgNlJqSYBeAFON3Ar8As= github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ultraware/funlen v0.0.2 h1:Av96YVBwwNSe4MLR7iI/BIa3VyI7/djnto/pK3Uxbdo= -github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA= github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFODYDsg= @@ -597,9 +573,7 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb github.com/uudashr/gocognit v1.0.1 h1:MoG2fZ0b/Eo7NXoIwCVFLG5JED3qgQz5/NEE+rOsjPs= github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.12.0/go.mod h1:229t1eWu9UXTPmoUkbpN/fctKPBY4IJoFXQnxHGXy6E= github.com/valyala/fasthttp v1.15.1/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= -github.com/valyala/quicktemplate v1.5.1/go.mod h1:v7yYWpBEiutDyNfVaph6oC/yKwejzVyTX/2cwwHxyok= github.com/valyala/quicktemplate v1.6.2/go.mod h1:mtEJpQtUiBV0SHhMX6RtiJtqxncgrfmjcUy5T68X8TM= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= @@ -749,7 +723,6 @@ golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -762,7 +735,6 @@ golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -881,8 +853,6 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.5 h1:nI5egYTGJakVyOryqLs1cQO5dO0ksin5XXs2pspk75k= honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.19.0 h1:XyrFIJqTYZJ2DU7FBE/bSPz7b1HvbVBuBf07oeo6eTc= @@ -925,5 +895,3 @@ sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4 h1:JPJh2pk3+X4lXAkZIk2RuE/7/FoK9maXw+TNPJhVS/c= -sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/internal/here/doc.go b/internal/here/doc.go new file mode 100644 index 00000000..5c486c91 --- /dev/null +++ b/internal/here/doc.go @@ -0,0 +1,25 @@ +/* +Copyright 2020 VMware, Inc. +SPDX-License-Identifier: Apache-2.0 +*/ + +package here + +import ( + "strings" + + "github.com/MakeNowJust/heredoc/v2" +) + +const ( + tab = "\t" + fourSpaces = " " +) + +func Doc(s string) string { + return strings.ReplaceAll(heredoc.Doc(s), tab, fourSpaces) +} + +func Docf(raw string, args ...interface{}) string { + return strings.ReplaceAll(heredoc.Docf(raw, args...), tab, fourSpaces) +} diff --git a/internal/here/doc_test.go b/internal/here/doc_test.go new file mode 100644 index 00000000..611049d8 --- /dev/null +++ b/internal/here/doc_test.go @@ -0,0 +1,104 @@ +/* +Copyright 2020 VMware, Inc. +SPDX-License-Identifier: Apache-2.0 +*/ + +package here + +import ( + "testing" + + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + "github.com/stretchr/testify/require" +) + +func TestDoc(t *testing.T) { + spec.Run(t, "here.Doc", func(t *testing.T, when spec.G, it spec.S) { + var r *require.Assertions + + it.Before(func() { + r = require.New(t) + }) + + it("returns single-line strings unchanged", func() { + r.Equal("the quick brown fox", Doc("the quick brown fox")) + r.Equal(" the quick brown fox", Doc(" the quick brown fox")) + }) + + it("returns multi-line strings with indentation removed", func() { + r.Equal( + "the quick brown fox\njumped over the\nlazy dog", + Doc(`the quick brown fox + jumped over the + lazy dog`), + ) + }) + + it("ignores the first empty line and the whitespace in the last line", func() { + r.Equal( + "the quick brown fox\njumped over the\nlazy dog\n", + Doc(` + the quick brown fox + jumped over the + lazy dog + `), + ) + }) + + it("turns all tabs into 4 spaces", func() { + r.Equal( + "the quick brown fox\n jumped over the\n lazy dog\n", + Doc(` + the quick brown fox + jumped over the + lazy dog + `), + ) + }) + }, spec.Parallel(), spec.Report(report.Terminal{})) + + spec.Run(t, "here.Docf", func(t *testing.T, when spec.G, it spec.S) { + var r *require.Assertions + + it.Before(func() { + r = require.New(t) + }) + + it("returns single-line strings unchanged", func() { + r.Equal("the quick brown fox", Docf("the quick brown %s", "fox")) + r.Equal(" the quick brown fox", Docf(" the %s brown %s", "quick", "fox")) + }) + + it("returns multi-line strings with indentation removed", func() { + r.Equal( + "the quick brown fox\njumped over the\nlazy dog", + Docf(`the quick brown %s + jumped over the + lazy %s`, "fox", "dog"), + ) + }) + + it("ignores the first empty line and the whitespace in the last line", func() { + r.Equal( + "the quick brown fox\njumped over the\nlazy dog\n", + Docf(` + the quick brown %s + jumped over the + lazy %s + `, "fox", "dog"), + ) + }) + + it("turns all tabs into 4 spaces", func() { + r.Equal( + "the quick brown fox\n jumped over the\n lazy dog\n", + Docf(` + the quick brown %s + jumped over the + lazy %s + `, "fox", "dog"), + ) + }) + }, spec.Parallel(), spec.Report(report.Terminal{})) +}