This repository was archived by the owner on Jul 10, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 38
Add command to generate Kubernetes Secrets #202
Merged
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
746248e
Add base kube command
versilis ad244de
Refactor API credential check into a function
versilis 3789778
Add template to generate secrets
versilis e5b4a88
Add kube secret command
versilis 10ad02d
Add to root command
versilis 0caeed3
Print success message when secret config is generated
versilis e651574
Fix issues with output generation
versilis 1c7ae51
Mark namespace flag as required
versilis 0fd9974
Add comment
versilis a7a2c07
Update unit test
versilis ed0dfc8
Apply suggestions from code review
versilis 8d66125
Remove old credential check in addAgentToECS
versilis 6596d5e
Use the default namespace when none is provided
versilis 69857a4
Rename flag variables to fit standard conventions
versilis 5acfe06
Print output on successful secret generation
versilis df81990
Add function to initialize telemetry
versilis 3dbe526
Return an error if directory does not exist
versilis 935b4bb
Add aliases for kube command
versilis 562adb3
Tweak test names
versilis 24a8bc3
Fix test
versilis edb4246
Update buffer initialization to use builtin constructor
versilis 4490c52
Only print when no file is specified
versilis ce8ba09
Fix doc comment
versilis 058e72c
Use null analytics client by default
versilis 44ba56b
Add contextual information on successful file generation
versilis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| 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: "Install Akita in your Kubernetes cluster", | ||
| Aliases: []string{ | ||
| "k8s", | ||
| "kubernetes", | ||
| }, | ||
| RunE: func(_ *cobra.Command, _ []string) error { | ||
| return cmderr.AkitaErr{Err: errors.New("no subcommand specified")} | ||
| }, | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| package kube | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "encoding/base64" | ||
| "os" | ||
| "path/filepath" | ||
| "text/template" | ||
|
|
||
| "github.com/akitasoftware/akita-cli/telemetry" | ||
|
|
||
| "github.com/akitasoftware/akita-cli/cmd/internal/cmderr" | ||
| "github.com/akitasoftware/akita-cli/printer" | ||
| "github.com/pkg/errors" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| var ( | ||
| secretFilePathFlag string | ||
| namespaceFlag string | ||
| // Store a parsed representation of /template/akita-secret.tmpl | ||
| secretTemplate *template.Template | ||
| ) | ||
|
|
||
| var secretCmd = &cobra.Command{ | ||
| Use: "secret", | ||
| Short: "Generate a Kubernetes secret containing the Akita credentials", | ||
| 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 | ||
| } | ||
|
|
||
| output, err := handleSecretGeneration(namespaceFlag, key, secret) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| // If the secret file path flag hasn't been set, print the generated secret to stdout | ||
| if secretFilePathFlag == "" { | ||
| printer.RawOutput(string(output)) | ||
| return nil | ||
| } | ||
|
|
||
| // Otherwise, write the generated secret to the given file path | ||
| err = writeSecretFile(output, secretFilePathFlag) | ||
| if err != nil { | ||
| return cmderr.AkitaErr{Err: errors.Wrapf(err, "Failed to write generated secret to %s", output)} | ||
| } | ||
|
|
||
| printer.Infof("Successfully generated a Kubernetes Secret file for Akita at %s\n", secretFilePathFlag) | ||
| printer.Infof("To apply, run: kubectl apply -f %s\n", secretFilePathFlag) | ||
| return nil | ||
| }, | ||
| // Override the parent command's PersistentPreRun to prevent any logs from being printed. | ||
| // This is necessary because the secret command is intended to be used in a pipeline | ||
| PersistentPreRun: func(cmd *cobra.Command, args []string) { | ||
| // Initialize the telemetry client, but do not allow any logs to be printed | ||
| telemetry.Init(false) | ||
| }, | ||
| } | ||
|
|
||
| // Represents the input used by secretTemplate | ||
| type secretTemplateInput struct { | ||
| Namespace string | ||
| APIKey string | ||
| APISecret string | ||
| } | ||
|
|
||
| func initSecretTemplate() error { | ||
| var err error | ||
|
|
||
| secretTemplate, err = template.ParseFS(templateFS, "template/akita-secret.tmpl") | ||
| if err != nil { | ||
| return cmderr.AkitaErr{Err: errors.Wrap(err, "failed to parse secret template")} | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // Generates a Kubernetes secret config file for Akita | ||
| // On success, the generated output is returned as a string. | ||
| func handleSecretGeneration(namespace, key, secret string) ([]byte, error) { | ||
| err := initSecretTemplate() | ||
| if err != nil { | ||
| return nil, cmderr.AkitaErr{Err: errors.Wrap(err, "failed to initialize secret template")} | ||
| } | ||
|
|
||
| input := secretTemplateInput{ | ||
| Namespace: namespace, | ||
| APIKey: base64.StdEncoding.EncodeToString([]byte(key)), | ||
| APISecret: base64.StdEncoding.EncodeToString([]byte(secret)), | ||
| } | ||
|
|
||
| buf := bytes.NewBuffer([]byte{}) | ||
|
|
||
| err = secretTemplate.Execute(buf, input) | ||
| if err != nil { | ||
| return nil, cmderr.AkitaErr{Err: errors.Wrap(err, "failed to generate template")} | ||
| } | ||
|
|
||
| return buf.Bytes(), nil | ||
| } | ||
|
|
||
| // Writes the generated secret to the given file path | ||
| func writeSecretFile(data []byte, filePath string) error { | ||
| secretFile, err := createSecretFile(filePath) | ||
| if err != nil { | ||
| return cmderr.AkitaErr{ | ||
| Err: cmderr.AkitaErr{ | ||
| Err: errors.Wrapf( | ||
| err, | ||
| "failed to create secret file %s", | ||
| filePath, | ||
| ), | ||
| }, | ||
| } | ||
| } | ||
| defer secretFile.Close() | ||
|
|
||
| _, err = secretFile.Write(data) | ||
| if err != nil { | ||
| return cmderr.AkitaErr{Err: errors.Wrap(err, "failed to write generated secret file")} | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // Creates a file at the given path to be used for storing of the generated Secret configuration | ||
| // If the directory provided does not exist, an error will be returned and the file will not be created | ||
| func createSecretFile(path string) (*os.File, error) { | ||
| // Split the output 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 that the output directory exists | ||
| if _, statErr := os.Stat(absOutputDir); os.IsNotExist(statErr) { | ||
| return nil, errors.Errorf("output directory %s does not exist", absOutputDir) | ||
| } | ||
|
|
||
| // 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 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() { | ||
| secretCmd.Flags().StringVarP( | ||
| &namespaceFlag, | ||
| "namespace", | ||
| "n", | ||
| "default", | ||
| "The Kubernetes namespace the secret should be applied to", | ||
| ) | ||
|
|
||
| secretCmd.Flags().StringVarP( | ||
| &secretFilePathFlag, | ||
| "file", | ||
| "f", | ||
| "", | ||
| "File to output the generated secret. If not set, the secret will be printed to stdout.", | ||
| ) | ||
|
|
||
| Cmd.AddCommand(secretCmd) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package kube | ||
|
|
||
| import ( | ||
| _ "embed" | ||
| "github.com/stretchr/testify/assert" | ||
| "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" | ||
| ) | ||
|
|
||
| // WHEN | ||
| output, err := handleSecretGeneration(namespace, key, secret) | ||
| if err != nil { | ||
| t.Errorf("Unexpected error: %s", err) | ||
| } | ||
|
|
||
| // THEN | ||
| assert.Equal(t, testAkitaSecretYAML, output) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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}} | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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== |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.