-
Notifications
You must be signed in to change notification settings - Fork 38
Add command to generate Kubernetes Secrets #202
Changes from 10 commits
746248e
ad244de
3789778
e5b4a88
10ad02d
0caeed3
e651574
1c7ae51
0fd9974
a7a2c07
ed0dfc8
8d66125
6596d5e
69857a4
5acfe06
df81990
3dbe526
935b4bb
562adb3
24a8bc3
edb4246
4490c52
ce8ba09
058e72c
44ba56b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package cmderr | ||
|
|
||
| import ( | ||
| "errors" | ||
| "github.com/akitasoftware/akita-cli/cfg" | ||
| "github.com/akitasoftware/akita-cli/env" | ||
| "github.com/akitasoftware/akita-cli/printer" | ||
| ) | ||
|
|
||
| // Checks that a user has configured their API key and secret and returned them. | ||
| // If the user has not configured their API key, a user-friendly error message is printed and an error is returned. | ||
| func RequireAPICredentials(explanation string) (string, string, error) { | ||
| key, secret := cfg.GetAPIKeyAndSecret() | ||
| if key == "" || secret == "" { | ||
| printer.Errorf("No Akita API key configured. %s\n", explanation) | ||
| if env.InDocker() { | ||
| printer.Infof("Please set the AKITA_API_KEY_ID and AKITA_API_KEY_SECRET environment variables on the Docker command line.\n") | ||
| } else { | ||
| printer.Infof("Use the AKITA_API_KEY_ID and AKITA_API_KEY_SECRET environment variables, or run 'akita login'.\n") | ||
| } | ||
|
|
||
| return "", "", AkitaErr{Err: errors.New("could not find an Akita API key to use")} | ||
| } | ||
|
|
||
| return key, secret, nil | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package kube | ||
|
|
||
| import ( | ||
| "github.com/akitasoftware/akita-cli/cmd/internal/cmderr" | ||
| "github.com/pkg/errors" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| var Cmd = &cobra.Command{ | ||
| Use: "kube", | ||
| Short: "Gateway to Kubernetes related utilities", | ||
versilis marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| RunE: func(_ *cobra.Command, _ []string) error { | ||
| return cmderr.AkitaErr{Err: errors.New("no subcommand specified")} | ||
| }, | ||
versilis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| package kube | ||
|
|
||
| import ( | ||
| "encoding/base64" | ||
| "log" | ||
| "os" | ||
| "path/filepath" | ||
| "text/template" | ||
|
|
||
| "github.com/akitasoftware/akita-cli/printer" | ||
|
|
||
| "github.com/akitasoftware/akita-cli/cmd/internal/cmderr" | ||
| "github.com/pkg/errors" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| var ( | ||
| output string | ||
| namespace string | ||
versilis marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Store a parsed representation of /template/akita-secret.tmpl | ||
| secretTemplate *template.Template | ||
| ) | ||
|
|
||
| var secretCmd = &cobra.Command{ | ||
| Use: "secret", | ||
| Short: "Generate a Kubernetes secret config for Akita", | ||
versilis marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| key, secret, err := cmderr.RequireAPICredentials("Akita API key is required for Kubernetes Secret generation") | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| err = handleSecretGeneration(namespace, key, secret, output) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| printer.Infoln("Generated Kubernetes secret config to ", output) | ||
| return nil | ||
| }, | ||
| } | ||
|
|
||
| // Represents the input used by secretTemplate | ||
| type secretTemplateInput struct { | ||
| Namespace string | ||
| APIKey string | ||
| APISecret string | ||
| } | ||
|
|
||
| // Generates a Kubernetes secret config file for Akita | ||
| func handleSecretGeneration(namespace, key, secret, output string) error { | ||
|
|
||
| input := secretTemplateInput{ | ||
| Namespace: namespace, | ||
| APIKey: base64.StdEncoding.EncodeToString([]byte(key)), | ||
| APISecret: base64.StdEncoding.EncodeToString([]byte(secret)), | ||
| } | ||
|
|
||
| secretFile, err := createSecretFile(output) | ||
| if err != nil { | ||
| return cmderr.AkitaErr{Err: errors.Wrap(err, "failed to create output file")} | ||
| } | ||
|
|
||
| defer secretFile.Close() | ||
|
|
||
| err = secretTemplate.Execute(secretFile, input) | ||
| if err != nil { | ||
| return cmderr.AkitaErr{Err: errors.Wrap(err, "failed to generate template")} | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // Creates a file at the give path to be used for storing of the generated Secret config | ||
| // If any child dicrectories do not exist, it will be created. | ||
|
||
| func createSecretFile(path string) (*os.File, error) { | ||
| // Split the outut flag value into directory and filename | ||
| outputDir, outputName := filepath.Split(path) | ||
|
|
||
| // Get the absolute path of the output directory | ||
| absOutputDir, err := filepath.Abs(outputDir) | ||
| if err != nil { | ||
| return nil, errors.Wrapf(err, "failed to resolve the absolute path of the output directory") | ||
| } | ||
|
|
||
| // Check if the output file already exists | ||
| outputFilePath := filepath.Join(absOutputDir, outputName) | ||
| if _, statErr := os.Stat(outputFilePath); statErr == nil { | ||
| return nil, errors.Errorf("output file %s already exists", outputFilePath) | ||
| } | ||
|
|
||
| // Create the output directory if it doesn't exist | ||
| err = os.MkdirAll(absOutputDir, 0755) | ||
| if err != nil { | ||
| return nil, errors.Wrapf(err, "failed to create the output directory") | ||
| } | ||
versilis marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Create the output file in the output directory | ||
| outputFile, err := os.Create(outputFilePath) | ||
| if err != nil { | ||
| return nil, errors.Wrap(err, "failed to create the output file") | ||
| } | ||
|
|
||
| return outputFile, nil | ||
| } | ||
|
|
||
| func init() { | ||
| var err error | ||
|
|
||
| secretTemplate, err = template.ParseFS(templateFS, "template/akita-secret.tmpl") | ||
|
||
| if err != nil { | ||
| log.Fatalf("unable to parse kube secret template: %v", err) | ||
| } | ||
versilis marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| secretCmd.Flags().StringVarP( | ||
| &namespace, | ||
| "namespace", | ||
| "n", | ||
| "", | ||
| "The Kuberenetes namespace the secret should be applied to", | ||
versilis marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ) | ||
| _ = secretCmd.MarkFlagRequired("namespace") | ||
versilis marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| secretCmd.Flags().StringVarP(&output, "output", "o", "akita-secret.yml", "File to output the generated secret.") | ||
|
||
|
|
||
| Cmd.AddCommand(secretCmd) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package kube | ||
|
|
||
| import ( | ||
| _ "embed" | ||
| "github.com/stretchr/testify/assert" | ||
| "os" | ||
| "path/filepath" | ||
| "testing" | ||
| ) | ||
|
|
||
| //go:embed test_resource/akita-secret.yml | ||
| var testAkitaSecretYAML []byte | ||
|
|
||
| func Test_secretGeneration(t *testing.T) { | ||
| // GIVEN | ||
| const ( | ||
| namespace = "default" | ||
| key = "api-key" | ||
| secret = "api-secret" | ||
| ) | ||
|
|
||
| dir := t.TempDir() | ||
| actualOutput := filepath.Join(dir, "configurations", "akita-secret.yml") | ||
|
|
||
| // WHEN | ||
| err := handleSecretGeneration(namespace, key, secret, actualOutput) | ||
| if err != nil { | ||
| t.Errorf("Unexpected error: %s", err) | ||
| } | ||
|
|
||
| // THEN | ||
| actualFile, err := os.ReadFile(actualOutput) | ||
| if err != nil { | ||
| t.Errorf("Failed to read generated file: %v", err) | ||
| } | ||
|
|
||
| assert.Equal(t, string(testAkitaSecretYAML), string(actualFile)) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package kube | ||
|
|
||
| import "embed" | ||
|
|
||
| //go:embed template | ||
| var templateFS embed.FS |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| apiVersion: v1 | ||
| kind: Secret | ||
| metadata: | ||
| name: akita-secrets | ||
| namespace: {{.Namespace}} | ||
versilis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| type: Opaque | ||
| data: | ||
| akita-api-key: {{.APIKey}} | ||
| akita-api-secret: {{.APISecret}} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| apiVersion: v1 | ||
| kind: Secret | ||
| metadata: | ||
| name: akita-secrets | ||
| namespace: default | ||
| type: Opaque | ||
| data: | ||
| akita-api-key: YXBpLWtleQ== | ||
| akita-api-secret: YXBpLXNlY3JldA== |
Uh oh!
There was an error while loading. Please reload this page.