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
Simplify Kubernetes Injection #205
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
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,247 @@ | ||
| package kube | ||
|
|
||
| import ( | ||
| "bytes" | ||
|
|
||
| "github.com/akitasoftware/akita-cli/cmd/internal/cmderr" | ||
| "github.com/akitasoftware/akita-cli/cmd/internal/kube/injector" | ||
| "github.com/akitasoftware/akita-cli/printer" | ||
| "github.com/akitasoftware/akita-cli/telemetry" | ||
| "github.com/akitasoftware/go-utils/optionals" | ||
| "github.com/pkg/errors" | ||
| "github.com/spf13/cobra" | ||
| v1 "k8s.io/api/core/v1" | ||
| ) | ||
|
|
||
| var ( | ||
| // The target Yaml faile to be injected | ||
| // This is required for execution of injectCmd | ||
| injectFileNameFlag string | ||
| // The output file to write the injected Yaml to | ||
| // If not set, injectCmd will default to printing the output to stdout | ||
| injectOutputFlag string | ||
| // The name of the project that the injected deployments should be associated with | ||
| // This will be used by the agent to determine which Akita service to report traffic to | ||
| projectNameFlag string | ||
| // Represents the options for generating a secret | ||
| // When set to "false" or left empty, injectCmd will not generate a secret | ||
| // When set to "true", injectCmd will prepend a secret to each injectable namespace found in the file to inject (injectFileNameFlag) | ||
| // Otherwise, injectCmd will treat secretInjectFlag as the file path all secrets should be generated to | ||
| secretInjectFlag string | ||
| ) | ||
|
|
||
| var injectCmd = &cobra.Command{ | ||
| Use: "inject", | ||
| Short: "Inject Akita into a Kubernetes deployment", | ||
| Long: "Inject Akita into a Kubernetes deployment or set of deployments, and output the result to stdout or a file", | ||
| RunE: func(_ *cobra.Command, args []string) error { | ||
| secretOpts := resolveSecretGenerationOptions(secretInjectFlag) | ||
|
|
||
| // To avoid users unintentionally attempting to apply injected Deployments via pipeline without | ||
| // their dependent Secrets, require that the user explicitly specify an output file. | ||
| if secretOpts.ShouldInject && secretOpts.Filepath.IsSome() && injectOutputFlag == "" { | ||
| printer.Errorln("Cannot specify a Secret file path without an output file (using --output or -o)") | ||
| printer.Infoln("To generate a Secret file on its own, use `akita kube secret`") | ||
| return cmderr.AkitaErr{ | ||
| Err: errors.New("invalid flag usage"), | ||
| } | ||
| } | ||
|
|
||
| // Create the injector which reads from the Kubernetes YAML file specified by the user | ||
| injectr, err := injector.FromYAML(injectFileNameFlag) | ||
| if err != nil { | ||
| return cmderr.AkitaErr{ | ||
| Err: errors.Wrapf( | ||
| err, | ||
| "Failed to read injection file %s", | ||
| injectFileNameFlag, | ||
| ), | ||
| } | ||
| } | ||
|
|
||
| // Generate a secret for each namespace in the deployment if the user specified secret generation | ||
| secretBuf := new(bytes.Buffer) | ||
| if secretOpts.ShouldInject { | ||
| key, secret, err := cmderr.RequireAPICredentials("API credentials are required to generate secret.") | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| namespaces, err := injectr.InjectableNamespaces() | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| for _, namespace := range namespaces { | ||
| r, err := handleSecretGeneration(namespace, key, secret) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| secretBuf.WriteString("---\n") | ||
| secretBuf.Write(r) | ||
| } | ||
| } | ||
|
|
||
| // Create the output buffer | ||
| out := new(bytes.Buffer) | ||
|
|
||
| // Either write the secret to a file or prepend it to the output | ||
| if secretFilePath, exists := secretOpts.Filepath.Get(); exists { | ||
| err = writeFile(secretBuf.Bytes(), secretFilePath) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| printer.Infof("Kubernetes Secret generated to %s\n", secretFilePath) | ||
| } else { | ||
| // Assign the secret to the output buffer | ||
| // We do this so that the secret is written before any injected Deployment resources that depend on it | ||
| out = secretBuf | ||
| } | ||
|
|
||
| // Inject the sidecar into the input file | ||
| rawInjected, err := injector.ToRawYAML(injectr, createSidecar(projectNameFlag)) | ||
| if err != nil { | ||
| return cmderr.AkitaErr{Err: errors.Wrap(err, "Failed to inject sidecars")} | ||
| } | ||
| // Append the injected YAML to the output | ||
| out.Write(rawInjected) | ||
|
|
||
| // If the user did not specify an output file, print the output to stdout | ||
| if injectOutputFlag == "" { | ||
| printer.Stdout.RawOutput(out.String()) | ||
| return nil | ||
| } | ||
|
|
||
| // Write the output to the specified file | ||
| if err := writeFile(out.Bytes(), injectOutputFlag); err != nil { | ||
| return err | ||
| } | ||
| printer.Infof("Injected YAML written to %s\n", injectOutputFlag) | ||
|
|
||
| return nil | ||
| }, | ||
| PersistentPreRun: func(cmd *cobra.Command, args []string) { | ||
| // Initialize the telemetry client, but do not allow any logs to be printed | ||
| telemetry.Init(false) | ||
| }, | ||
| } | ||
|
|
||
| // A parsed representation of the `--secret` option. | ||
| type secretGenerationOptions struct { | ||
| // Whether to inject a secret | ||
| ShouldInject bool | ||
| // The path to the secret file | ||
| Filepath optionals.Optional[string] | ||
| } | ||
|
|
||
| func createSidecar(projectName string) v1.Container { | ||
| sidecar := v1.Container{ | ||
| Name: "akita", | ||
| Image: "akitasoftware/cli:latest", | ||
| Env: []v1.EnvVar{ | ||
| { | ||
| Name: "AKITA_API_KEY_ID", | ||
| ValueFrom: &v1.EnvVarSource{ | ||
| SecretKeyRef: &v1.SecretKeySelector{ | ||
| LocalObjectReference: v1.LocalObjectReference{ | ||
| Name: "akita-secrets", | ||
| }, | ||
| Key: "akita-api-key", | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| Name: "AKITA_API_KEY_SECRET", | ||
| ValueFrom: &v1.EnvVarSource{ | ||
| SecretKeyRef: &v1.SecretKeySelector{ | ||
| LocalObjectReference: v1.LocalObjectReference{ | ||
| Name: "akita-secrets", | ||
| }, | ||
| Key: "akita-api-secret", | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| Lifecycle: &v1.Lifecycle{ | ||
| PreStop: &v1.LifecycleHandler{ | ||
| Exec: &v1.ExecAction{ | ||
| Command: []string{ | ||
| "/bin/sh", | ||
| "-c", | ||
| "AKITA_PID=$(pgrep akita) && kill -2 $AKITA_PID && tail -f /proc/$AKITA_PID/fd/1", | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| Args: []string{"apidump", "--project", projectName}, | ||
| SecurityContext: &v1.SecurityContext{ | ||
| Capabilities: &v1.Capabilities{Add: []v1.Capability{"NET_RAW"}}, | ||
| }, | ||
| } | ||
|
|
||
| return sidecar | ||
| } | ||
|
|
||
| // Parses the given value for the `--secret` option. | ||
| func resolveSecretGenerationOptions(flagValue string) secretGenerationOptions { | ||
| if flagValue == "" || flagValue == "false" { | ||
| return secretGenerationOptions{ | ||
| ShouldInject: false, | ||
| Filepath: optionals.None[string](), | ||
| } | ||
| } | ||
|
|
||
| if flagValue == "true" { | ||
| return secretGenerationOptions{ | ||
| ShouldInject: true, | ||
| Filepath: optionals.None[string](), | ||
| } | ||
| } | ||
|
|
||
| return secretGenerationOptions{ | ||
| ShouldInject: true, | ||
| Filepath: optionals.Some(flagValue), | ||
| } | ||
| } | ||
|
|
||
| func init() { | ||
| injectCmd.Flags().StringVarP( | ||
| &injectFileNameFlag, | ||
| "file", | ||
| "f", | ||
| "", | ||
| "Path to the Kubernetes YAML file to be injected. This should contain a Deployment object.", | ||
| ) | ||
| _ = injectCmd.MarkFlagRequired("file") | ||
|
|
||
| injectCmd.Flags().StringVarP( | ||
| &injectOutputFlag, | ||
| "output", | ||
| "o", | ||
| "", | ||
| "Path to the output file. If not specified, the output will be printed to stdout.", | ||
| ) | ||
|
|
||
| injectCmd.Flags().StringVarP( | ||
| &projectNameFlag, | ||
| "project", | ||
| "p", | ||
| "", | ||
| "Name of the Akita project to which the traffic will be uploaded.", | ||
| ) | ||
| _ = injectCmd.MarkFlagRequired("project") | ||
|
|
||
| injectCmd.Flags().StringVarP( | ||
| &secretInjectFlag, | ||
| "secret", | ||
| "s", | ||
| "false", | ||
| `Whether to generate a Kubernetes Secret. If set to "true", the secret will be added to the modified Kubernetes YAML file. Specify a path to write the secret to a separate file; if this is done, an output file must also be specified with --output.`, | ||
| ) | ||
| // Default value is "true" when the flag is given without an argument. | ||
| injectCmd.Flags().Lookup("secret").NoOptDefVal = "true" | ||
|
|
||
| Cmd.AddCommand(injectCmd) | ||
| } | ||
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 injector | ||
|
|
||
| import ( | ||
| "bytes" | ||
| v1 "k8s.io/api/core/v1" | ||
| kyaml "sigs.k8s.io/yaml" | ||
| ) | ||
|
|
||
| // Calls the given injector's Inject method and returns the result as a YAML bytes. | ||
| func ToRawYAML(injector Injector, sidecar v1.Container) ([]byte, error) { | ||
| injectedObjects, err := injector.Inject(sidecar) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| out := new(bytes.Buffer) | ||
| for _, obj := range injectedObjects { | ||
| raw, err := kyaml.Marshal(obj) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| out.WriteString("---\n") | ||
| out.Write(raw) | ||
| } | ||
|
|
||
| return out.Bytes(), nil | ||
| } |
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this work even if the command line is something like
akita kube inject --secret --project myproject
or does it only work in certain positions? I am not sure what logic cobra uses.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my own testing, the
--secretflag works in any position. Here's more info on how Cobra handles no option default values for flags: https://github.com/spf13/pflag#setting-no-option-default-values-for-flags