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

Commit

Permalink
Merge branch 'main' into 89-cmd-to-select-current-context
Browse files Browse the repository at this point in the history
  • Loading branch information
rafiramadhana committed Sep 19, 2023
2 parents 978e3c6 + 26c3541 commit 0a962c5
Show file tree
Hide file tree
Showing 30 changed files with 944 additions and 164 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,16 @@ jobs:
- name: Output
run: |
layerform output bar test_bar | jq .bar_file.value | tee output
grep -E '\.layerform\/examples\/local\/foo-.{4}\/bar-.{4}\.txt' output
grep -E '\.layerform\/examples\/local\/foo-default\/bar-test_bar\.txt' output
layerform output bar test_bar | jq .foo_file.value | tee output
grep -E '\.layerform\/examples\/local\/foo-.{4}\/\.keep' output
grep -E '\.layerform\/examples\/local\/foo-default\/\.keep' output
- name: Refresh
run: |
layerform refresh bar test_bar --var prefix=test-prefix-
layerform output bar test_bar | jq .bar_file.value | tee output
grep -E '\.layerform\/examples\/local\/test-prefix-foo-.{4}\/bar-.{4}\.txt' output
grep -E '\.layerform\/examples\/local\/test-prefix-foo-default\/bar-test_bar\.txt' output
layerform refresh bar test_bar
- name: Can't kill instance that has dependants
Expand Down
19 changes: 19 additions & 0 deletions cmd/cli/cloud.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package cli

import (
"github.com/spf13/cobra"
)

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

var cloudCmd = &cobra.Command{
Use: "cloud",
Short: "Modify layerform cloud entities",
Long: `Modify layerform cloud entities using subcomands like "layerform cloud create-user"
This command only works if the current context is of type "cloud"`,
Example: `# Create a new cloud user
layerform cloud create-user --name "John Doe" --email [email protected]`,
}
137 changes: 137 additions & 0 deletions cmd/cli/create_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package cli

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"

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

"github.com/ergomake/layerform/internal/lfconfig"
"github.com/ergomake/layerform/internal/validation"
)

func init() {
cloudCreateUserCmd.Flags().StringP("name", "n", "", "name of the new user")
cloudCreateUserCmd.Flags().StringP("email", "e", "", "email of the new user")
cloudCreateUserCmd.MarkFlagRequired("email")

cloudCmd.AddCommand(cloudCreateUserCmd)
}

var cloudCreateUserCmd = &cobra.Command{
Use: "create-user",
Short: "Creates a new user in layerform cloud",
Long: `Creates a new user in layerform cloud.
The password will be printed to stdout, e-mail must be unique.`,
Example: `# Set a context of type local named local-example
layerform cloud create-user --name "John Doe" --email "[email protected]"`,
Run: func(cmd *cobra.Command, _ []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
}

currentCfgCtx := cfg.GetCurrent()
if currentCfgCtx.Type != "cloud" {
fmt.Fprintf(
os.Stderr,
"This command only works if the current context is of type \"cloud\" but current has type \"%s\".\n",
currentCfgCtx.Type,
)
os.Exit(1)
return
}

name, err := cmd.Flags().GetString("name")
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", errors.Wrap(err, "fail to get --name flag, this is a bug in layerform"))
os.Exit(1)
return
}
name = strings.TrimSpace(name)

email, err := cmd.Flags().GetString("email")
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", errors.Wrap(err, "fail to get --email flag, this is a bug in layerform"))
os.Exit(1)
return
}
email = strings.TrimSpace(email)

if !validation.IsValidEmail(email) {
fmt.Fprintf(os.Stderr, "Invalid email \"%s\"\n", email)
os.Exit(1)
return

}

cloudClient, err := cfg.GetCloudClient(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", errors.Wrap(err, "fail to get cloud client"))
os.Exit(1)
}

payload, err := json.Marshal(map[string]string{"name": name, "email": email})
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", errors.Wrap(err, "fail marshal create user payload to json"))
os.Exit(1)
return
}

req, err := cloudClient.NewRequest(ctx, "POST", "/v1/users", bytes.NewReader(payload))
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", errors.Wrap(err, "fail to marshal create user payload to json"))
os.Exit(1)
return
}

res, err := cloudClient.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", errors.Wrap(err, "fail to perform http request to cloud backend"))
os.Exit(1)
return
}
defer res.Body.Close()

if res.StatusCode == http.StatusConflict {
fmt.Fprintf(os.Stderr, "User with email %s already exists.\n", email)
os.Exit(1)
return
}

var body struct {
Password string `json:"password"`
}
err = json.NewDecoder(res.Body).Decode(&body)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", errors.Wrap(err, "fail decode create user JSON response"))
os.Exit(1)
return

}

identifier := name
if identifier == "" {
identifier = email
}
fmt.Fprintf(os.Stdout, "User %s created successfully.\nPassword: %s\n", identifier, body.Password)
},
SilenceErrors: true,
}
50 changes: 50 additions & 0 deletions cmd/cli/get_contexts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package cli

import (
"fmt"
"os"
"strings"
"text/tabwriter"

"github.com/pkg/errors"
"github.com/spf13/cobra"

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

func init() {
configCmd.AddCommand(configGetContextsCmd)
}

var configGetContextsCmd = &cobra.Command{
Use: "get-contexts",
Short: "Display contexts from layerform config file",
Long: `Display contexts from layerform config file`,
Run: func(_ *cobra.Command, _ []string) {
cfg, err := lfconfig.Load("")
if err != nil && !errors.Is(err, os.ErrNotExist) {
fmt.Fprintln(os.Stdout, "No contexts configure, configure contexts using the set-context command.")
return
}

w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
fmt.Fprintln(w, "CURRENT\tNAME\tTYPE\tLOCATION")
for name, ctx := range cfg.Contexts {
isCurrent := name == cfg.CurrentContext
current := ""
if isCurrent {
current = "*"
}

fmt.Fprintln(w, strings.Join([]string{current, name, ctx.Type, ctx.Location()}, "\t"))
}
err = w.Flush()

if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", errors.Wrap(err, "fail to print output"))
os.Exit(1)
}

},
SilenceErrors: true,
}
9 changes: 8 additions & 1 deletion cmd/cli/kill.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

func init() {
killCmd.Flags().StringArray("var", []string{}, "a map of variables for the layer's Terraform files. I.e. 'foo=bar,baz=qux'")
killCmd.Flags().Bool("force", false, "force the destruction of the layer instance even if it has dependants")

rootCmd.AddCommand(killCmd)
}
Expand Down Expand Up @@ -47,6 +48,12 @@ Please notice that the kill command cannot destroy a layer instance which has de
os.Exit(1)
return
}
force, err := cmd.Flags().GetBool("force")
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", errors.Wrap(err, "fail to get --force flag, this is a bug in layerform"))
os.Exit(1)
return
}
kill, err := cfg.GetKillCommand(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", errors.Wrap(err, "fail to get kill command"))
Expand All @@ -56,7 +63,7 @@ Please notice that the kill command cannot destroy a layer instance which has de
layerName := args[0]
instanceName := args[1]

err = kill.Run(ctx, layerName, instanceName, false, vars)
err = kill.Run(ctx, layerName, instanceName, false, vars, force)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
Expand Down
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)
}
},
}
8 changes: 1 addition & 7 deletions examples/local/bar.tf
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
resource "local_file" "bar" {
content = "bar content"
filename = "${local.dir}/bar-${random_string.bar_suffix.result}.txt"
}

resource "random_string" "bar_suffix" {
length = 4
upper = false
special = false
filename = "${local.dir}/bar-${var.lf_names.bar}.txt"
}

output "bar_file" {
Expand Down
8 changes: 1 addition & 7 deletions examples/local/baz.tf
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
resource "local_file" "baz" {
content = "baz content"
filename = "${local.dir}/baz-${random_string.baz_suffix.result}.txt"
}

resource "random_string" "baz_suffix" {
length = 4
upper = false
special = false
filename = "${local.dir}/baz-${var.lf_names.baz}.txt"
}

output "baz_file" {
Expand Down
8 changes: 1 addition & 7 deletions examples/local/foo.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,14 @@ variable "prefix" {
}

locals {
dir = pathexpand("~/.layerform/examples/local/${var.prefix}foo-${random_string.foo_suffix.result}")
dir = pathexpand("~/.layerform/examples/local/${var.prefix}foo-${var.lf_names.foo}")
}

resource "local_file" "foo" {
content = ""
filename = "${local.dir}/.keep"
}

resource "random_string" "foo_suffix" {
length = 4
upper = false
special = false
}

output "foo_file" {
value = local_file.foo.filename
}
Loading

0 comments on commit 0a962c5

Please sign in to comment.