Skip to content

Commit 4b9c05a

Browse files
[POA-1443] Add helm container snippet in kube command (#21)
In this PR, we are adding support for printing the Helm Chart snippet using the `kube` command. Changes done: * Create a new `helm-sidecar-support` sub-command under `kube`. * Moved the `init()` function from `inject.go` to `kube.go` and also moved the `--project` flag to the `kube` command level * Refactored the `createPostmanSidecar()` function to handle how the API Key should be shown in the snippet * Also moved this function to `kube/util.go` * Created a new `print_snippet.go` file, which stores the function to print the snippet. @mgritter Have two questions: 1. Do we need to add quotes `'` around the image value since it contains a colon `:`? 2. By default, the preStop command is broken into two lines. Do we still need to keep it on a single line?
1 parent b02595a commit 4b9c05a

File tree

7 files changed

+275
-241
lines changed

7 files changed

+275
-241
lines changed

cmd/internal/kube/inject.go

Lines changed: 4 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"github.com/akitasoftware/akita-libs/akid"
88
"github.com/akitasoftware/go-utils/optionals"
99
"github.com/pkg/errors"
10-
"github.com/postmanlabs/postman-insights-agent/cfg"
1110
"github.com/postmanlabs/postman-insights-agent/cmd/internal/cmderr"
1211
"github.com/postmanlabs/postman-insights-agent/cmd/internal/kube/injector"
1312
"github.com/postmanlabs/postman-insights-agent/printer"
@@ -19,7 +18,7 @@ import (
1918
)
2019

2120
var (
22-
// The target Yaml faile to be injected
21+
// The target Yaml file to be injected
2322
// This is required for execution of injectCmd
2423
injectFileNameFlag string
2524
// The output file to write the injected Yaml to
@@ -120,8 +119,7 @@ var injectCmd = &cobra.Command{
120119
var container v1.Container
121120

122121
// Inject the sidecar into the input file
123-
_, env := cfg.GetPostmanAPIKeyAndEnvironment()
124-
container = createPostmanSidecar(insightsProjectID, env)
122+
container = createPostmanSidecar(insightsProjectID, true)
125123

126124
rawInjected, err := injector.ToRawYAML(injectr, container)
127125
if err != nil {
@@ -144,15 +142,7 @@ var injectCmd = &cobra.Command{
144142

145143
return nil
146144
},
147-
PersistentPreRun: func(cmd *cobra.Command, args []string) {
148-
// This function overrides the root command preRun so we need to duplicate the domain setup.
149-
if rest.Domain == "" {
150-
rest.Domain = rest.DefaultDomain()
151-
}
152-
153-
// Initialize the telemetry client, but do not allow any logs to be printed
154-
telemetry.Init(false)
155-
},
145+
PersistentPreRun: kubeCommandPreRun,
156146
}
157147

158148
// A parsed representation of the `--secret` option.
@@ -163,62 +153,6 @@ type secretGenerationOptions struct {
163153
Filepath optionals.Optional[string]
164154
}
165155

166-
// The image to use for the Postman Insights Agent sidecar
167-
const akitaImage = "docker.postman.com/postman-insights-agent:latest"
168-
169-
func createPostmanSidecar(insightsProjectID string, postmanEnvironment string) v1.Container {
170-
args := []string{"apidump", "--project", insightsProjectID}
171-
172-
// If a nondefault --domain flag was used, specify it for the container as well.
173-
if rest.Domain != rest.DefaultDomain() {
174-
args = append(args, "--domain", rest.Domain)
175-
}
176-
177-
envs := []v1.EnvVar{
178-
{
179-
Name: "POSTMAN_API_KEY",
180-
ValueFrom: &v1.EnvVarSource{
181-
SecretKeyRef: &v1.SecretKeySelector{
182-
LocalObjectReference: v1.LocalObjectReference{
183-
Name: "postman-agent-secrets",
184-
},
185-
Key: "postman-api-key",
186-
},
187-
},
188-
},
189-
}
190-
191-
if postmanEnvironment != "" {
192-
envs = append(envs, v1.EnvVar{
193-
Name: "POSTMAN_ENV",
194-
Value: postmanEnvironment,
195-
})
196-
}
197-
198-
sidecar := v1.Container{
199-
Name: "postman-insights-agent",
200-
Image: akitaImage,
201-
Env: envs,
202-
Lifecycle: &v1.Lifecycle{
203-
PreStop: &v1.LifecycleHandler{
204-
Exec: &v1.ExecAction{
205-
Command: []string{
206-
"/bin/sh",
207-
"-c",
208-
"POSTMAN_INSIGHTS_AGENT_PID=$(pgrep postman-insights-agent) && kill -2 $POSTMAN_INSIGHTS_AGENT_PID && tail -f /proc/$POSTMAN_INSIGHTS_AGENT_PID/fd/1",
209-
},
210-
},
211-
},
212-
},
213-
Args: args,
214-
SecurityContext: &v1.SecurityContext{
215-
Capabilities: &v1.Capabilities{Add: []v1.Capability{"NET_RAW"}},
216-
},
217-
}
218-
219-
return sidecar
220-
}
221-
222156
// Parses the given value for the `--secret` option.
223157
func resolveSecretGenerationOptions(flagValue string) secretGenerationOptions {
224158
if flagValue == "" || flagValue == "false" {
@@ -257,6 +191,7 @@ func lookupService(insightsProjectID string) error {
257191
}
258192

259193
func init() {
194+
// `kube inject` command level flags
260195
injectCmd.Flags().StringVarP(
261196
&injectFileNameFlag,
262197
"file",
@@ -284,11 +219,5 @@ func init() {
284219
// Default value is "true" when the flag is given without an argument.
285220
injectCmd.Flags().Lookup("secret").NoOptDefVal = "true"
286221

287-
injectCmd.Flags().StringVar(
288-
&insightsProjectID,
289-
"project",
290-
"",
291-
"Your Postman Insights project ID.")
292-
293222
Cmd.AddCommand(injectCmd)
294223
}

cmd/internal/kube/kube.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,13 @@ var Cmd = &cobra.Command{
1212
"kubernetes",
1313
},
1414
}
15+
16+
func init() {
17+
// `kube` command level flags
18+
Cmd.PersistentFlags().StringVar(
19+
&insightsProjectID,
20+
"project",
21+
"",
22+
"Your Postman Insights project ID.")
23+
_ = Cmd.MarkFlagRequired("project")
24+
}

cmd/internal/kube/print_fragment.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package kube
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/postmanlabs/postman-insights-agent/cfg"
9+
"github.com/postmanlabs/postman-insights-agent/cmd/internal/cmderr"
10+
"github.com/postmanlabs/postman-insights-agent/rest"
11+
v1 "k8s.io/api/core/v1"
12+
13+
"github.com/spf13/cobra"
14+
"github.com/zclconf/go-cty/cty"
15+
"sigs.k8s.io/yaml"
16+
17+
"github.com/hashicorp/hcl/v2/hclsyntax"
18+
"github.com/hashicorp/hcl/v2/hclwrite"
19+
)
20+
21+
var printHelmChartFragmentCmd = &cobra.Command{
22+
Use: "helm-fragment",
23+
Short: "Print a Helm chart container definition for adding the Postman Insights Agent to existing kubernetes deployment.",
24+
Long: "Print a container definition that can be inserted into a Helm Chart template to add the Postman Insights Agent as a sidecar container.",
25+
RunE: printHelmChartFragment,
26+
PersistentPreRun: kubeCommandPreRun,
27+
}
28+
29+
var printTerraformFragmentCmd = &cobra.Command{
30+
Use: "tf-fragment",
31+
Short: "Print an Terraform (HCL) code fragment for adding the Postman Insights Agent to an existing kubernetes deployment.",
32+
Long: "Print a Terraform (HCL) code fragment that can be inserted into a Terraform kubernetes_deployment resource spec to add the Postman Insights Agent as a sidecar container.",
33+
RunE: printTerraformFragment,
34+
PersistentPreRun: kubeCommandPreRun,
35+
}
36+
37+
func printHelmChartFragment(_ *cobra.Command, _ []string) error {
38+
err := cmderr.CheckAPIKeyAndInsightsProjectID(insightsProjectID)
39+
if err != nil {
40+
return err
41+
}
42+
43+
// Create the Postman Insights Agent sidecar container
44+
container := createPostmanSidecar(insightsProjectID, false)
45+
// Store it in an array since the fragment will be added to a list of containers
46+
containerArray := []v1.Container{container}
47+
48+
containerYamlBytes, err := yaml.Marshal(containerArray)
49+
if err != nil {
50+
return err
51+
}
52+
containerYaml := indentCodeFragment(containerYamlBytes, 4)
53+
54+
fmt.Printf("\n%s\n", containerYaml)
55+
return nil
56+
}
57+
58+
func printTerraformFragment(_ *cobra.Command, _ []string) error {
59+
err := cmderr.CheckAPIKeyAndInsightsProjectID(insightsProjectID)
60+
if err != nil {
61+
return err
62+
}
63+
64+
// Create the Postman Insights Agent sidecar container
65+
hclBlockConfig := createTerraformContainer(insightsProjectID)
66+
hclBlockConfigString := indentCodeFragment(hclBlockConfig.Bytes(), 4)
67+
68+
// Print the fragment
69+
fmt.Printf("\n%s\n", hclBlockConfigString)
70+
return nil
71+
}
72+
73+
func createTerraformContainer(insightsProjectID string) *hclwrite.File {
74+
hclConfig := hclwrite.NewEmptyFile()
75+
rootBody := hclConfig.Body()
76+
77+
rootBody.AppendUnstructuredTokens(hclwrite.Tokens{
78+
{
79+
Type: hclsyntax.TokenComment,
80+
Bytes: []byte("# Add this fragment to your 'kubernetes_deployment' resource under 'spec.template.spec'. \n"),
81+
},
82+
})
83+
84+
containerBlock := rootBody.AppendNewBlock("container", []string{})
85+
containerBody := containerBlock.Body()
86+
87+
containerBody.SetAttributeValue("name", cty.StringVal("postman-insights-agent"))
88+
containerBody.SetAttributeValue("image", cty.StringVal(akitaImage))
89+
90+
containerBody.AppendNewBlock("lifecycle", []string{}).
91+
Body().AppendNewBlock("pre_stop", []string{}).
92+
Body().AppendNewBlock("exec", []string{}).
93+
Body().SetAttributeValue("command", cty.ListVal([]cty.Value{
94+
cty.StringVal("/bin/sh"),
95+
cty.StringVal("-c"),
96+
cty.StringVal("POSTMAN_INSIGHTS_AGENT_PID=$(pgrep postman-insights-agent) && kill -2 $POSTMAN_INSIGHTS_AGENT_PID && tail -f /proc/$POSTMAN_INSIGHTS_AGENT_PID/fd/1"),
97+
}))
98+
99+
containerBody.AppendNewBlock("security_context", []string{}).
100+
Body().AppendNewBlock("capabilities", []string{}).
101+
Body().SetAttributeValue("add", cty.ListVal([]cty.Value{
102+
cty.StringVal("NET_RAW"),
103+
}))
104+
105+
// Add the args to the container
106+
args := cty.ListVal([]cty.Value{
107+
cty.StringVal("apidump"),
108+
cty.StringVal("--project"),
109+
cty.StringVal(insightsProjectID),
110+
})
111+
// If a non default --domain flag was used, specify it for the container as well.
112+
if rest.Domain != rest.DefaultDomain() {
113+
args.Add(cty.StringVal("--domain"))
114+
args.Add(cty.StringVal(rest.Domain))
115+
}
116+
containerBody.SetAttributeValue("args", args)
117+
118+
// Add the environment variables to the container
119+
pmKey, pmEnv := cfg.GetPostmanAPIKeyAndEnvironment()
120+
APIKeyEnvBlockBody := containerBody.AppendNewBlock("env", []string{}).Body()
121+
APIKeyEnvBlockBody.SetAttributeValue("name", cty.StringVal("POSTMAN_API_KEY"))
122+
APIKeyEnvBlockBody.SetAttributeValue("value", cty.StringVal(pmKey))
123+
124+
if pmEnv != "" {
125+
PostmanEnvBlockBody := containerBody.AppendNewBlock("env", []string{}).Body()
126+
PostmanEnvBlockBody.SetAttributeValue("name", cty.StringVal("POSTMAN_ENV"))
127+
PostmanEnvBlockBody.SetAttributeValue("value", cty.StringVal(pmEnv))
128+
}
129+
130+
return hclConfig
131+
}
132+
133+
func indentCodeFragment(codeFragmentInBytes []byte, indentLevel int) string {
134+
// Trim off any extraneous newlines.
135+
codeFragmentInBytes = bytes.Trim(codeFragmentInBytes, "\n")
136+
137+
// Indent level prefix
138+
indentPrefix := strings.Repeat(" ", indentLevel)
139+
140+
indentedCodeFragment := indentPrefix + strings.ReplaceAll(
141+
string(codeFragmentInBytes), "\n", "\n"+indentPrefix)
142+
143+
return indentedCodeFragment
144+
}
145+
146+
func init() {
147+
Cmd.AddCommand(printHelmChartFragmentCmd)
148+
Cmd.AddCommand(printTerraformFragmentCmd)
149+
}

cmd/internal/kube/secret.go

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import (
88
"github.com/pkg/errors"
99
"github.com/postmanlabs/postman-insights-agent/cmd/internal/cmderr"
1010
"github.com/postmanlabs/postman-insights-agent/printer"
11-
"github.com/postmanlabs/postman-insights-agent/rest"
12-
"github.com/postmanlabs/postman-insights-agent/telemetry"
1311
"github.com/spf13/cobra"
1412
)
1513

@@ -54,15 +52,7 @@ var secretCmd = &cobra.Command{
5452
},
5553
// Override the parent command's PersistentPreRun to prevent any logs from being printed.
5654
// This is necessary because the secret command is intended to be used in a pipeline
57-
PersistentPreRun: func(cmd *cobra.Command, args []string) {
58-
// This function overrides the root command preRun so we need to duplicate the domain setup.
59-
if rest.Domain == "" {
60-
rest.Domain = rest.DefaultDomain()
61-
}
62-
63-
// Initialize the telemetry client, but do not allow any logs to be printed
64-
telemetry.Init(false)
65-
},
55+
PersistentPreRun: kubeCommandPreRun,
6656
}
6757

6858
// Represents the input used by secretTemplate

0 commit comments

Comments
 (0)