Skip to content
This repository has been archived by the owner on Dec 26, 2023. It is now read-only.

introduce set-env command #94

Merged
merged 1 commit into from
Sep 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions cmd/cli/setenv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package cli

import (
"context"
"fmt"
"os"

"github.com/hashicorp/go-hclog"
"github.com/pkg/errors"
"github.com/spf13/cobra"

"github.com/ergomake/layerform/internal/lfconfig"
"github.com/ergomake/layerform/pkg/data"
)

func init() {
rootCmd.AddCommand(setEnvCmd)
}

var setEnvCmd = &cobra.Command{
Use: "set-env <VAR_NAME> <value>",
Short: "set an environment variable to be used when spawning a layer",
Long: `The set-env command sets an environment variable to be used when spawning layers.

These are often used for configuring the providers, for instance, you can use this command to set AWS credentials.
Environment variables can also be used to set values for the variables in your layers. The environment variables must be in the format TF_VAR_name.`,
Example: `# Set value for a variable in your layer
layerform set-env TF_VAR_foo bar`,
Args: cobra.MinimumNArgs(2),
Run: func(_ *cobra.Command, args []string) {
logger := hclog.Default()
logLevel := hclog.LevelFromString(os.Getenv("LF_LOG"))
if logLevel != hclog.NoLevel {
logger.SetLevel(logLevel)
}
ctx := hclog.WithContext(context.Background(), logger)

cfg, err := lfconfig.Load("")
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", errors.Wrap(err, "fail to load config"))
os.Exit(1)
return
}

varName := args[0]
varValue := args[1]

envvarsBackend, err := cfg.GetEnvVarsBackend(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", errors.Wrap(err, "fail to get environment variables backend"))
os.Exit(1)
}

err = envvarsBackend.SaveVariable(ctx, &data.EnvVar{Name: varName, Value: varValue})
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", errors.Wrap(err, "fail to save environment variable"))
os.Exit(1)
}
},
}
50 changes: 47 additions & 3 deletions internal/lfconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/ergomake/layerform/pkg/command/kill"
"github.com/ergomake/layerform/pkg/command/refresh"
"github.com/ergomake/layerform/pkg/command/spawn"
"github.com/ergomake/layerform/pkg/envvars"
"github.com/ergomake/layerform/pkg/layerdefinitions"
"github.com/ergomake/layerform/pkg/layerinstances"
)
Expand Down Expand Up @@ -256,7 +257,12 @@ func (c *config) GetSpawnCommand(ctx context.Context) (spawn.Spawn, error) {
return nil, errors.Wrap(err, "fail to get instance backend")
}

return spawn.NewLocal(layersBackend, instancesBackend), nil
envVarsBackend, err := c.GetEnvVarsBackend(ctx)
if err != nil {
return nil, errors.Wrap(err, "fail to get env vars backend")
}

return spawn.NewLocal(layersBackend, instancesBackend, envVarsBackend), nil
}

return nil, errors.Errorf("fail to get spawn command unexpected context type %s", current.Type)
Expand Down Expand Up @@ -286,7 +292,12 @@ func (c *config) GetKillCommand(ctx context.Context) (kill.Kill, error) {
return nil, errors.Wrap(err, "fail to get instance backend")
}

return kill.NewLocal(layersBackend, instancesBackend), nil
envVarsBackend, err := c.GetEnvVarsBackend(ctx)
if err != nil {
return nil, errors.Wrap(err, "fail to get env vars backend")
}

return kill.NewLocal(layersBackend, instancesBackend, envVarsBackend), nil
}

return nil, errors.Errorf("fail to get kill command unexpected context type %s", current.Type)
Expand Down Expand Up @@ -316,8 +327,41 @@ func (c *config) GetRefreshCommand(ctx context.Context) (refresh.Refresh, error)
return nil, errors.Wrap(err, "fail to get instance backend")
}

return refresh.NewLocal(layersBackend, instancesBackend), nil
envVarsBackend, err := c.GetEnvVarsBackend(ctx)
if err != nil {
return nil, errors.Wrap(err, "fail to get env vars backend")
}

return refresh.NewLocal(layersBackend, instancesBackend, envVarsBackend), nil
}

return nil, errors.Errorf("fail to get spawn command unexpected context type %s", current.Type)
}

const envVarsFileName = "layerform.env"

func (c *config) GetEnvVarsBackend(ctx context.Context) (envvars.Backend, error) {
current := c.GetCurrent()

switch current.Type {
case "cloud":
cloudClient, err := c.GetCloudClient(ctx)
if err != nil {
return nil, errors.Wrap(err, "fail to get cloud client")
}

return envvars.NewCloud(cloudClient), nil
case "s3":
s3, err := storage.NewS3Backend(current.Bucket, envVarsFileName, current.Region)
if err != nil {
return nil, errors.Wrap(err, "fail to initialize s3 backend")
}

return envvars.NewFileLikeBackend(ctx, s3)
case "local":
fileStorage := storage.NewFileStorage(path.Join(c.getDir(), envVarsFileName))
return envvars.NewFileLikeBackend(ctx, fileStorage)
}

return nil, errors.Errorf("fail to get set-env command unexpected context type %s", current.Type)
}
26 changes: 24 additions & 2 deletions pkg/command/kill/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,25 @@ import (
"github.com/ergomake/layerform/internal/tfclient"
"github.com/ergomake/layerform/pkg/command"
"github.com/ergomake/layerform/pkg/data"
"github.com/ergomake/layerform/pkg/envvars"
"github.com/ergomake/layerform/pkg/layerdefinitions"
"github.com/ergomake/layerform/pkg/layerinstances"
)

type localKillCommand struct {
definitionsBackend layerdefinitions.Backend
instancesBackend layerinstances.Backend
envVarsBackend envvars.Backend
}

var _ Kill = &localKillCommand{}

func NewLocal(definitionsBackend layerdefinitions.Backend, instancesBackend layerinstances.Backend) *localKillCommand {
return &localKillCommand{definitionsBackend, instancesBackend}
func NewLocal(
definitionsBackend layerdefinitions.Backend,
instancesBackend layerinstances.Backend,
envVarsBackend envvars.Backend,
) *localKillCommand {
return &localKillCommand{definitionsBackend, instancesBackend, envVarsBackend}
}

func (c *localKillCommand) Run(
Expand Down Expand Up @@ -95,6 +101,22 @@ func (c *localKillCommand) Run(
return errors.New("can't kill this layer because other layers depend on it")
}

envVars, err := c.envVarsBackend.ListVariables(ctx)
if err != nil {
s.Error()
sm.Stop()
return errors.Wrap(err, "fail to list environment variables")
}

for _, envVar := range envVars {
err := os.Setenv(envVar.Name, envVar.Value)
if err != nil {
s.Error()
sm.Stop()
return errors.Wrapf(err, "fail to set %s environment variable", envVar.Name)
}
}

tfpath, err := terraform.GetTFPath(ctx)
if err != nil {
s.Error()
Expand Down
21 changes: 20 additions & 1 deletion pkg/command/refresh/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,25 @@ import (
"github.com/ergomake/layerform/internal/tfclient"
"github.com/ergomake/layerform/pkg/command"
"github.com/ergomake/layerform/pkg/data"
"github.com/ergomake/layerform/pkg/envvars"
"github.com/ergomake/layerform/pkg/layerdefinitions"
"github.com/ergomake/layerform/pkg/layerinstances"
)

type localRefreshCommand struct {
definitionsBackend layerdefinitions.Backend
instancesBackend layerinstances.Backend
envVarsBackend envvars.Backend
}

var _ Refresh = &localRefreshCommand{}

func NewLocal(
definitionsBackend layerdefinitions.Backend,
instancesBackend layerinstances.Backend,
envVarsBackend envvars.Backend,
) *localRefreshCommand {
return &localRefreshCommand{definitionsBackend, instancesBackend}
return &localRefreshCommand{definitionsBackend, instancesBackend, envVarsBackend}
}

func (c *localRefreshCommand) Run(
Expand Down Expand Up @@ -78,6 +81,22 @@ func (c *localRefreshCommand) Run(
return errors.Wrap(err, "fail to get layer instance")
}

envVars, err := c.envVarsBackend.ListVariables(ctx)
if err != nil {
s.Error()
sm.Stop()
return errors.Wrap(err, "fail to list environment variables")
}

for _, envVar := range envVars {
err := os.Setenv(envVar.Name, envVar.Value)
if err != nil {
s.Error()
sm.Stop()
return errors.Wrapf(err, "fail to set %s environment variable", envVar.Name)
}
}

tfpath, err := terraform.GetTFPath(ctx)
if err != nil {
s.Error()
Expand Down
20 changes: 20 additions & 0 deletions pkg/command/setenv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package command

import (
"context"

"github.com/ergomake/layerform/pkg/data"
"github.com/ergomake/layerform/pkg/envvars"
)

type setenvCommand struct {
backend envvars.Backend
}

func NewSetEnv(backend envvars.Backend) *setenvCommand {
return &setenvCommand{backend}
}

func (c *setenvCommand) Run(ctx context.Context, variable *data.EnvVar) error {
return c.backend.SaveVariable(ctx, variable)
}
22 changes: 20 additions & 2 deletions pkg/command/spawn/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,25 @@ import (
"github.com/ergomake/layerform/internal/tfclient"
"github.com/ergomake/layerform/pkg/command"
"github.com/ergomake/layerform/pkg/data"
"github.com/ergomake/layerform/pkg/envvars"
"github.com/ergomake/layerform/pkg/layerdefinitions"
"github.com/ergomake/layerform/pkg/layerinstances"
)

type localSpawnCommand struct {
definitionsBackend layerdefinitions.Backend
instancesBackend layerinstances.Backend
envVarsBackend envvars.Backend
}

var _ Spawn = &localSpawnCommand{}

func NewLocal(definitionsBackend layerdefinitions.Backend, instancesBackend layerinstances.Backend) *localSpawnCommand {
return &localSpawnCommand{definitionsBackend, instancesBackend}
func NewLocal(
definitionsBackend layerdefinitions.Backend,
instancesBackend layerinstances.Backend,
envVarsBackend envvars.Backend,
) *localSpawnCommand {
return &localSpawnCommand{definitionsBackend, instancesBackend, envVarsBackend}
}

func (c *localSpawnCommand) Run(
Expand Down Expand Up @@ -64,6 +70,18 @@ func (c *localSpawnCommand) Run(
return errors.Wrap(err, "fail to get instance")
}

envVars, err := c.envVarsBackend.ListVariables(ctx)
if err != nil {
return errors.Wrap(err, "fail to list environment variables")
}

for _, envVar := range envVars {
err := os.Setenv(envVar.Name, envVar.Value)
if err != nil {
return errors.Wrapf(err, "fail to set %s environment variable", envVar.Name)
}
}

err = c.spawnLayer(ctx, layerName, instanceName, workdir, tfpath, dependenciesInstance, vars)
if err != nil {
return errors.Wrap(err, "fail to spawn layer")
Expand Down
6 changes: 6 additions & 0 deletions pkg/data/envvar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package data

type EnvVar struct {
Name string `json:"name"`
Value string `json:"value"`
}
76 changes: 76 additions & 0 deletions pkg/envvars/cloud.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package envvars

import (
"bytes"
"context"
"encoding/json"
"net/http"

"github.com/pkg/errors"

"github.com/ergomake/layerform/internal/cloud"
"github.com/ergomake/layerform/pkg/data"
)

type cloudBackend struct {
client *cloud.HTTPClient
}

var _ Backend = &cloudBackend{}

func NewCloud(client *cloud.HTTPClient) *cloudBackend {
return &cloudBackend{client}
}

func (cb *cloudBackend) ListVariables(ctx context.Context) ([]*data.EnvVar, error) {
url := "/v1/env-vars"
req, err := cb.client.NewRequest(ctx, "GET", url, nil)
if err != nil {
return nil, errors.Wrap(err, "fail to create http request to cloud backend")
}

req.SetHeader("Content-Type", "application/json")
resp, err := cb.client.Do(req)
if err != nil {
return nil, errors.Wrap(err, "fail to perform http request to cloud backend")
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, errors.Errorf("HTTP request to %s failed with status code %d", url, resp.StatusCode)
}

var variables []*data.EnvVar
err = json.NewDecoder(resp.Body).Decode(&variables)
if err != nil {
return nil, errors.Wrap(err, "fail to decode variables JSON response")
}

return variables, nil
}

func (cb *cloudBackend) SaveVariable(ctx context.Context, variable *data.EnvVar) error {
url := "/v1/env-vars"
dataBytes, err := json.Marshal(variable)
if err != nil {
return errors.Wrap(err, "fail to marshal env var to json")
}

req, err := cb.client.NewRequest(ctx, "POST", url, bytes.NewBuffer(dataBytes))
if err != nil {
return errors.Wrap(err, "fail to create http request to cloud backend")
}

req.SetHeader("Content-Type", "application/json")
resp, err := cb.client.Do(req)
if err != nil {
return errors.Wrap(err, "fail to perform http request to cloud backend")
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return errors.Errorf("HTTP request to %s failed with status code %d", url, resp.StatusCode)
}

return nil
}
Loading
Loading