From 2c421566c600817db2098cc8ea5c166a4d6cc0db Mon Sep 17 00:00:00 2001 From: Aditya Thebe Date: Wed, 18 Oct 2023 16:48:46 +0545 Subject: [PATCH] feat: env prep --- models/connections.go | 104 +++++++++++++++++++++++++++++++------ models/connections_test.go | 10 ++-- 2 files changed, 93 insertions(+), 21 deletions(-) diff --git a/models/connections.go b/models/connections.go index c52103053..20a79ce9f 100644 --- a/models/connections.go +++ b/models/connections.go @@ -1,10 +1,13 @@ package models import ( + "bytes" + "context" "fmt" "net/url" + "os" + "os/exec" "regexp" - "strings" "time" "github.com/flanksource/duty/types" @@ -153,33 +156,102 @@ func (c Connection) AsGoGetterURL() (string, error) { } // AsEnv generates environment variables and a configuration file content based on the connection type. -func (c Connection) AsEnv() ([]string, string) { - var ( - envs []string - file strings.Builder - ) +func (c Connection) AsEnv() EnvPrep { + envPrep := EnvPrep{ + Conn: c, + } switch c.Type { case ConnectionTypeAWS: - envs = append(envs, fmt.Sprintf("AWS_ACCESS_KEY_ID=%s", c.Username)) - envs = append(envs, fmt.Sprintf("AWS_SECRET_ACCESS_KEY=%s", c.Password)) + envPrep.Env = append(envPrep.Env, fmt.Sprintf("AWS_ACCESS_KEY_ID=%s", c.Username)) + envPrep.Env = append(envPrep.Env, fmt.Sprintf("AWS_SECRET_ACCESS_KEY=%s", c.Password)) - file.WriteString("[default]\n") - file.WriteString(fmt.Sprintf("aws_access_key_id = %s\n", c.Username)) - file.WriteString(fmt.Sprintf("aws_secret_access_key = %s\n", c.Password)) + envPrep.File.WriteString("[default]\n") + envPrep.File.WriteString(fmt.Sprintf("aws_access_key_id = %s\n", c.Username)) + envPrep.File.WriteString(fmt.Sprintf("aws_secret_access_key = %s\n", c.Password)) if v, ok := c.Properties["profile"]; ok { - envs = append(envs, fmt.Sprintf("AWS_DEFAULT_PROFILE=%s", v)) + envPrep.Env = append(envPrep.Env, fmt.Sprintf("AWS_DEFAULT_PROFILE=%s", v)) } if v, ok := c.Properties["region"]; ok { - envs = append(envs, fmt.Sprintf("AWS_DEFAULT_REGION=%s", v)) - file.WriteString(fmt.Sprintf("region = %s\n", v)) + envPrep.Env = append(envPrep.Env, fmt.Sprintf("AWS_DEFAULT_REGION=%s", v)) + envPrep.File.WriteString(fmt.Sprintf("region = %s\n", v)) + } + + case ConnectionTypeAzure: + // Do nothing + + case ConnectionTypeGCP: + envPrep.File.WriteString(c.Certificate) + } + + return envPrep +} + +type EnvPrep struct { + Conn Connection + + // Env is the connection credentials in environment variables + Env []string + + // File contains the content of the configuration file based on the connection + File bytes.Buffer +} + +func (c *EnvPrep) Apply(ctx context.Context, cmd *exec.Cmd, configAbsPath string) error { + switch c.Conn.Type { + case ConnectionTypeAWS: + if err := saveConfig(c.File.Bytes(), configAbsPath); err != nil { + return err + } + + cmd.Env = append(cmd.Env, "AWS_EC2_METADATA_DISABLED=true") // https://github.com/aws/aws-cli/issues/5262#issuecomment-705832151 + cmd.Env = append(cmd.Env, fmt.Sprintf("AWS_SHARED_CREDENTIALS_FILE=%s", configAbsPath)) + if v, ok := c.Conn.Properties["region"]; ok { + cmd.Env = append(cmd.Env, fmt.Sprintf("AWS_DEFAULT_REGION=%s", v)) } case ConnectionTypeGCP: - file.WriteString(c.Certificate) + if err := saveConfig(c.File.Bytes(), configAbsPath); err != nil { + return err + } + + // to configure gcloud CLI to use the service account specified in GOOGLE_APPLICATION_CREDENTIALS, + // we need to explicitly activate it + runCmd := exec.Command("gcloud", "auth", "activate-service-account", "--key-file", configAbsPath) + if err := runCmd.Run(); err != nil { + return fmt.Errorf("failed to activate GCP service account: %w", err) + } + + cmd.Env = append(cmd.Env, fmt.Sprintf("GOOGLE_APPLICATION_CREDENTIALS=%s", configAbsPath)) + + case ConnectionTypeAzure: + args := []string{"login", "--service-principal", "--username", c.Conn.Username, "--password", c.Conn.Password} + if v, ok := c.Conn.Properties["tenant"]; ok { + args = append(args, "--tenant") + args = append(args, v) + } + + // login with service principal + runCmd := exec.CommandContext(ctx, "az", args...) + if err := runCmd.Run(); err != nil { + return err + } + + cmd.Env = append(cmd.Env, fmt.Sprintf("GOOGLE_APPLICATION_CREDENTIALS=%s", configAbsPath)) + } + + return nil +} + +func saveConfig(content []byte, absPath string) error { + file, err := os.Create(absPath) + if err != nil { + return err } + defer file.Close() - return envs, file.String() + _, err = file.Write(content) + return err } diff --git a/models/connections_test.go b/models/connections_test.go index 2d813c671..40b5b975d 100644 --- a/models/connections_test.go +++ b/models/connections_test.go @@ -87,16 +87,16 @@ func Test_Connection_AsEnv(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - env, file := tc.connection.AsEnv() + envPrep := tc.connection.AsEnv() for i, expected := range tc.expectedEnv { - if env[i] != expected { - t.Errorf("Expected environment variable: %s, but got: %s", expected, env[i]) + if envPrep.Env[i] != expected { + t.Errorf("Expected environment variable: %s, but got: %s", expected, envPrep.Env[i]) } } - if file != tc.expectedFile { - t.Errorf("Expected file content:\n%s\nBut got:\n%s", tc.expectedFile, file) + if envPrep.File.String() != tc.expectedFile { + t.Errorf("Expected file content:\n%s\nBut got:\n%s", tc.expectedFile, envPrep.File.String()) } }) }