Skip to content

Commit

Permalink
[POA-1445] Add terraform container snippet in kube command (#22)
Browse files Browse the repository at this point in the history
_ps: This is an incremental PR into `mudit/POA-1443`_

In this PR, we are adding support for printing the Terraform resource
container snippet using the `kube` command.
Changes done:
* Create a new `tf-sidecar-snippet` sub-command under `kube`.
* Using Terraform's HCL Golang SDK to construct the resource definition
* A new function, `createTerraformContainer()` which creates a HCL
resource config ACC to our requirement and finally prints this config

@mgritter Is this approach okay, or do you suggest to use
`text/template`. Just like we are doing for k8s secret snippet?
  • Loading branch information
mudit-postman authored Jul 31, 2024
1 parent 2f0bf5e commit 0567bde
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 225 deletions.
10 changes: 1 addition & 9 deletions cmd/internal/kube/inject.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,15 +142,7 @@ var injectCmd = &cobra.Command{

return nil
},
PersistentPreRun: func(cmd *cobra.Command, args []string) {
// This function overrides the root command preRun so we need to duplicate the domain setup.
if rest.Domain == "" {
rest.Domain = rest.DefaultDomain()
}

// Initialize the telemetry client, but do not allow any logs to be printed
telemetry.Init(false)
},
PersistentPreRun: kubeCommandPreRun,
}

// A parsed representation of the `--secret` option.
Expand Down
149 changes: 149 additions & 0 deletions cmd/internal/kube/print_fragment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package kube

import (
"bytes"
"fmt"
"strings"

"github.com/postmanlabs/postman-insights-agent/cfg"
"github.com/postmanlabs/postman-insights-agent/cmd/internal/cmderr"
"github.com/postmanlabs/postman-insights-agent/rest"
v1 "k8s.io/api/core/v1"

"github.com/spf13/cobra"
"github.com/zclconf/go-cty/cty"
"sigs.k8s.io/yaml"

"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/hcl/v2/hclwrite"
)

var printHelmChartFragmentCmd = &cobra.Command{
Use: "helm-fragment",
Short: "Print a Helm chart container definition for adding the Postman Insights Agent to existing kubernetes deployment.",
Long: "Print a container definition that can be inserted into a Helm Chart template to add the Postman Insights Agent as a sidecar container.",
RunE: printHelmChartFragment,
PersistentPreRun: kubeCommandPreRun,
}

var printTerraformFragmentCmd = &cobra.Command{
Use: "tf-fragment",
Short: "Print an Terraform (HCL) code fragment for adding the Postman Insights Agent to an existing kubernetes deployment.",
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.",
RunE: printTerraformFragment,
PersistentPreRun: kubeCommandPreRun,
}

func printHelmChartFragment(_ *cobra.Command, _ []string) error {
err := cmderr.CheckAPIKeyAndInsightsProjectID(insightsProjectID)
if err != nil {
return err
}

// Create the Postman Insights Agent sidecar container
container := createPostmanSidecar(insightsProjectID, false)
// Store it in an array since the fragment will be added to a list of containers
containerArray := []v1.Container{container}

containerYamlBytes, err := yaml.Marshal(containerArray)
if err != nil {
return err
}
containerYaml := indentCodeFragment(containerYamlBytes, 4)

fmt.Printf("\n%s\n", containerYaml)
return nil
}

func printTerraformFragment(_ *cobra.Command, _ []string) error {
err := cmderr.CheckAPIKeyAndInsightsProjectID(insightsProjectID)
if err != nil {
return err
}

// Create the Postman Insights Agent sidecar container
hclBlockConfig := createTerraformContainer(insightsProjectID)
hclBlockConfigString := indentCodeFragment(hclBlockConfig.Bytes(), 4)

// Print the fragment
fmt.Printf("\n%s\n", hclBlockConfigString)
return nil
}

func createTerraformContainer(insightsProjectID string) *hclwrite.File {
hclConfig := hclwrite.NewEmptyFile()
rootBody := hclConfig.Body()

rootBody.AppendUnstructuredTokens(hclwrite.Tokens{
{
Type: hclsyntax.TokenComment,
Bytes: []byte("# Add this fragment to your 'kubernetes_deployment' resource under 'spec.template.spec'. \n"),
},
})

containerBlock := rootBody.AppendNewBlock("container", []string{})
containerBody := containerBlock.Body()

containerBody.SetAttributeValue("name", cty.StringVal("postman-insights-agent"))
containerBody.SetAttributeValue("image", cty.StringVal(akitaImage))

containerBody.AppendNewBlock("lifecycle", []string{}).
Body().AppendNewBlock("pre_stop", []string{}).
Body().AppendNewBlock("exec", []string{}).
Body().SetAttributeValue("command", cty.ListVal([]cty.Value{
cty.StringVal("/bin/sh"),
cty.StringVal("-c"),
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"),
}))

containerBody.AppendNewBlock("security_context", []string{}).
Body().AppendNewBlock("capabilities", []string{}).
Body().SetAttributeValue("add", cty.ListVal([]cty.Value{
cty.StringVal("NET_RAW"),
}))

// Add the args to the container
args := cty.ListVal([]cty.Value{
cty.StringVal("apidump"),
cty.StringVal("--project"),
cty.StringVal(insightsProjectID),
})
// If a nondefault --domain flag was used, specify it for the container as well.
if rest.Domain != rest.DefaultDomain() {
args.Add(cty.StringVal("--domain"))
args.Add(cty.StringVal(rest.Domain))
}
containerBody.SetAttributeValue("args", args)

// Add the environment variables to the container
pmKey, pmEnv := cfg.GetPostmanAPIKeyAndEnvironment()
APIKeyEnvBlockBody := containerBody.AppendNewBlock("env", []string{}).Body()
APIKeyEnvBlockBody.SetAttributeValue("name", cty.StringVal("POSTMAN_API_KEY"))
APIKeyEnvBlockBody.SetAttributeValue("value", cty.StringVal(pmKey))

if pmEnv != "" {
PostmanEnvBlockBody := containerBody.AppendNewBlock("env", []string{}).Body()
PostmanEnvBlockBody.SetAttributeValue("name", cty.StringVal("POSTMAN_ENV"))
PostmanEnvBlockBody.SetAttributeValue("value", cty.StringVal(pmEnv))
}

return hclConfig
}

func indentCodeFragment(codeFragmentInBytes []byte, indentLevel int) string {
// Trim off any extraneous newlines.
codeFragmentInBytes = bytes.Trim(codeFragmentInBytes, "\n")

// Indent level prefix
indentPrefix := strings.Repeat(" ", indentLevel)

indentedCodeFragment := indentPrefix + strings.ReplaceAll(
string(codeFragmentInBytes), "\n", "\n"+indentPrefix)

return indentedCodeFragment
}

func init() {
Cmd.AddCommand(printHelmChartFragmentCmd)
Cmd.AddCommand(printTerraformFragmentCmd)
}
50 changes: 0 additions & 50 deletions cmd/internal/kube/print_snippet.go

This file was deleted.

12 changes: 1 addition & 11 deletions cmd/internal/kube/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (
"github.com/pkg/errors"
"github.com/postmanlabs/postman-insights-agent/cmd/internal/cmderr"
"github.com/postmanlabs/postman-insights-agent/printer"
"github.com/postmanlabs/postman-insights-agent/rest"
"github.com/postmanlabs/postman-insights-agent/telemetry"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -54,15 +52,7 @@ var secretCmd = &cobra.Command{
},
// 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) {
// This function overrides the root command preRun so we need to duplicate the domain setup.
if rest.Domain == "" {
rest.Domain = rest.DefaultDomain()
}

// Initialize the telemetry client, but do not allow any logs to be printed
telemetry.Init(false)
},
PersistentPreRun: kubeCommandPreRun,
}

// Represents the input used by secretTemplate
Expand Down
14 changes: 14 additions & 0 deletions cmd/internal/kube/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/postmanlabs/postman-insights-agent/cfg"
"github.com/postmanlabs/postman-insights-agent/cmd/internal/cmderr"
"github.com/postmanlabs/postman-insights-agent/rest"
"github.com/postmanlabs/postman-insights-agent/telemetry"
"github.com/spf13/cobra"
v1 "k8s.io/api/core/v1"
)

Expand Down Expand Up @@ -130,3 +132,15 @@ func createPostmanSidecar(insightsProjectID string, addAPIKeyAsSecret bool) v1.C

return sidecar
}

// This function overrrides the default preRun function in the root command.
// It supresses the INFO logs printed on start i.e. agent version and telemetry logs
func kubeCommandPreRun(cmd *cobra.Command, args []string) {
// This function overrides the root command preRun so we need to duplicate the domain setup.
if rest.Domain == "" {
rest.Domain = rest.DefaultDomain()
}

// Initialize the telemetry client, but do not allow any logs to be printed
telemetry.Init(false)
}
11 changes: 9 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ require (
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f
github.com/golang/mock v1.4.4
github.com/golang/protobuf v1.5.2
github.com/google/go-cmp v0.5.9
github.com/google/go-cmp v0.6.0
github.com/google/gopacket v1.1.19
github.com/google/martian/v3 v3.0.1
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/hashicorp/go-retryablehttp v0.6.8
github.com/hashicorp/go-version v1.2.1
github.com/hashicorp/hcl/v2 v2.21.0
github.com/jpillora/backoff v1.0.0
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/mitchellh/go-homedir v1.1.0
Expand All @@ -36,18 +37,21 @@ require (
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.7.1
github.com/stretchr/testify v1.8.1
github.com/zclconf/go-cty v1.15.0
golang.org/x/exp v0.0.0-20220428152302-39d4317da171
golang.org/x/term v0.5.0
golang.org/x/text v0.7.0
golang.org/x/text v0.11.0
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.26.3
k8s.io/apimachinery v0.26.3
sigs.k8s.io/yaml v1.3.0
)

require (
github.com/agext/levenshtein v1.2.1 // indirect
github.com/akitasoftware/objecthash-proto v0.0.0-20211020162104-173a34b1afb0 // indirect
github.com/amplitude/analytics-go v1.0.1 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.12.23 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.25 // indirect
Expand All @@ -74,6 +78,7 @@ require (
github.com/mattn/go-colorable v0.1.6 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
Expand All @@ -83,8 +88,10 @@ require (
github.com/spf13/cast v1.3.0 // indirect
github.com/spf13/jwalterweatherman v1.0.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/tools v0.6.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.51.0 // indirect
Expand Down
Loading

0 comments on commit 0567bde

Please sign in to comment.