diff --git a/.github/workflows/build_and_deploy.yaml b/.github/workflows/build_and_deploy.yaml index b7df02b1..a12d8124 100644 --- a/.github/workflows/build_and_deploy.yaml +++ b/.github/workflows/build_and_deploy.yaml @@ -135,6 +135,7 @@ jobs: './examples/local_exec', './examples/remote_exec', './examples/certificates', + './examples/terraform', ] steps: diff --git a/.gitignore b/.gitignore index af59c89c..e4805119 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ bin/ dist/ .vscode .envrc -.terraform \ No newline at end of file +.terraform +.terraform.lock.hcl \ No newline at end of file diff --git a/cmd/dev.go b/cmd/dev.go index 8a313713..ab0e4867 100644 --- a/cmd/dev.go +++ b/cmd/dev.go @@ -7,6 +7,7 @@ import ( "time" "github.com/jumppad-labs/jumppad/cmd/view" + "github.com/jumppad-labs/jumppad/pkg/clients" "github.com/jumppad-labs/jumppad/pkg/jumppad" "github.com/jumppad-labs/jumppad/pkg/utils" "github.com/spf13/cobra" @@ -55,7 +56,11 @@ func newDevCmdFunc(variables *[]string, variablesFile, interval *string, ttyFlag } } - engine, _, _ = createEngine(v.Logger()) + engineClients, _ := clients.GenerateClients(v.Logger()) + engine, _, err := createEngine(v.Logger(), engineClients) + if err != nil { + return fmt.Errorf("unable to create engine: %s", err) + } // create the shipyard and sub folders in the users home directory utils.CreateFolders() diff --git a/cmd/down.go b/cmd/down.go index 0eb5d7ba..1bd21aea 100644 --- a/cmd/down.go +++ b/cmd/down.go @@ -3,6 +3,7 @@ package cmd import ( "os" + "github.com/jumppad-labs/jumppad/pkg/clients" "github.com/jumppad-labs/jumppad/pkg/clients/connector" "github.com/jumppad-labs/jumppad/pkg/utils" "github.com/spf13/cobra" @@ -15,25 +16,31 @@ func newDestroyCmd(cc connector.Connector) *cobra.Command { Long: "Remove all resources in the current state", Example: `jumppad down`, Run: func(cmd *cobra.Command, args []string) { - err := engine.Destroy() + l := createLogger() + engineClients, _ := clients.GenerateClients(l) + engine, _, err := createEngine(l, engineClients) + if err != nil { + l.Error("Unable to create engine", "error", err) + return + } + + err = engine.Destroy() logger := createLogger() if err != nil { - logger.Error("Unable to destroy stack", "error", err) + l.Error("Unable to destroy stack", "error", err) return } - // clean up the data folder - os.RemoveAll(utils.GetDataFolder("", os.ModePerm)) - - // clean up the library folder - os.RemoveAll(utils.GetLibraryFolder("", os.ModePerm)) + // clean up the data folders + os.RemoveAll(utils.DataFolder("", os.ModePerm)) + os.RemoveAll(utils.LibraryFolder("", os.ModePerm)) // shutdown ingress when we destroy all resources if cc.IsRunning() { err = cc.Stop() if err != nil { - logger.Error("Unable to destroy stack", "error", err) + logger.Error("Unable to destroy jumppad daemon", "error", err) } } }, diff --git a/cmd/purge.go b/cmd/purge.go index 30634f61..7d2ac9ff 100644 --- a/cmd/purge.go +++ b/cmd/purge.go @@ -75,7 +75,7 @@ func newPurgeCmdFunc(dt container.Docker, il images.ImageLog, l logger.Logger) f bHasError = true } - hcp := utils.GetBlueprintLocalFolder("") + hcp := utils.BlueprintLocalFolder("") l.Info("Removing cached blueprints", "path", hcp) err = os.RemoveAll(hcp) if err != nil { @@ -83,7 +83,7 @@ func newPurgeCmdFunc(dt container.Docker, il images.ImageLog, l logger.Logger) f bHasError = true } - bcp := utils.GetHelmLocalFolder("") + bcp := utils.HelmLocalFolder("") l.Info("Removing cached Helm charts", "path", bcp) err = os.RemoveAll(bcp) if err != nil { @@ -92,7 +92,7 @@ func newPurgeCmdFunc(dt container.Docker, il images.ImageLog, l logger.Logger) f } // delete the releases - rcp := utils.GetReleasesFolder() + rcp := utils.ReleasesFolder() l.Info("Removing cached releases", "path", rcp) err = os.RemoveAll(rcp) if err != nil { @@ -100,7 +100,7 @@ func newPurgeCmdFunc(dt container.Docker, il images.ImageLog, l logger.Logger) f bHasError = true } - dcp := utils.GetDataFolder("", os.ModePerm) + dcp := utils.DataFolder("", os.ModePerm) l.Info("Removing data folders", "path", dcp) err = os.RemoveAll(dcp) if err != nil { @@ -108,6 +108,14 @@ func newPurgeCmdFunc(dt container.Docker, il images.ImageLog, l logger.Logger) f bHasError = true } + ccp := utils.DataFolder("", os.ModePerm) + l.Info("Removing cache folders", "path", ccp) + err = os.RemoveAll(ccp) + if err != nil { + l.Error("Unable to remove cache folder", "error", err) + bHasError = true + } + cp := path.Join(utils.JumppadHome(), "config") l.Info("Removing config", "path", cp) err = os.RemoveAll(cp) diff --git a/cmd/root.go b/cmd/root.go index 0912f849..1d537ce9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -23,65 +23,23 @@ var rootCmd = &cobra.Command{ Long: `Jumppad is a tool that helps you create and run development, demo, and tutorial environments`, } -var engine jumppad.Engine -var l logger.Logger -var engineClients *clients.Clients - var version string // set by build process var date string // set by build process var commit string // set by build process -func init() { - - var vm gvm.Versions - - // setup dependencies - l = createLogger() - engine, engineClients, vm = createEngine(l) - - rootCmd.AddCommand(checkCmd) - rootCmd.AddCommand(outputCmd) - rootCmd.AddCommand(newDevCmd()) - rootCmd.AddCommand(newEnvCmd(engine)) - rootCmd.AddCommand(newRunCmd(engine, engineClients.ContainerTasks, engineClients.Getter, engineClients.HTTP, engineClients.Browser, vm, engineClients.Connector, l)) - rootCmd.AddCommand(newTestCmd()) - rootCmd.AddCommand(newDestroyCmd(engineClients.Connector)) - rootCmd.AddCommand(statusCmd) - rootCmd.AddCommand(newPurgeCmd(engineClients.Docker, engineClients.ImageLog, l)) - rootCmd.AddCommand(taintCmd) - rootCmd.AddCommand(newVersionCmd(vm)) - rootCmd.AddCommand(uninstallCmd) - rootCmd.AddCommand(newPushCmd(engineClients.ContainerTasks, engineClients.Kubernetes, engineClients.HTTP, engineClients.Nomad, l)) - rootCmd.AddCommand(newLogCmd(engine, engineClients.Docker, os.Stdout, os.Stderr), completionCmd) - - // add the server commands - rootCmd.AddCommand(connectorCmd) - connectorCmd.AddCommand(newConnectorRunCommand()) - connectorCmd.AddCommand(connectorStopCmd) - connectorCmd.AddCommand(newConnectorCertCmd()) +func createEngine(l logger.Logger, c *clients.Clients) (jumppad.Engine, gvm.Versions, error) { - // add the generate command - rootCmd.AddCommand(generateCmd) - generateCmd.AddCommand(newGenerateReadmeCommand(engine)) -} - -func createEngine(l logger.Logger) (jumppad.Engine, *clients.Clients, gvm.Versions) { - engineClients, err := clients.GenerateClients(l) - if err != nil { - return nil, nil, nil - } - - providers := config.NewProviders(engineClients) + providers := config.NewProviders(c) engine, err := jumppad.New(providers, l) if err != nil { - panic(err) + return nil, nil, err } o := gvm.Options{ Organization: "jumppad-labs", Repo: "jumppad", - ReleasesPath: utils.GetReleasesFolder(), + ReleasesPath: utils.ReleasesFolder(), } o.AssetNameFunc = func(version, goos, goarch string) string { @@ -112,7 +70,7 @@ func createEngine(l logger.Logger) (jumppad.Engine, *clients.Clients, gvm.Versio vm := gvm.New(o) - return engine, engineClients, vm + return engine, vm, nil } func createLogger() logger.Logger { @@ -130,9 +88,52 @@ func Execute(v, c, d string) error { commit = c date = d + var vm gvm.Versions + + // setup dependencies + l := createLogger() + + engineClients, _ := clients.GenerateClients(l) + + // Check the system to see if Docker is running and everything is installed + s, err := engineClients.System.Preflight() + if err != nil { + fmt.Println("") + fmt.Println("###### SYSTEM DIAGNOSTICS ######") + fmt.Println(s) + return err + } + + engine, vm, _ := createEngine(l, engineClients) + + rootCmd.AddCommand(checkCmd) + rootCmd.AddCommand(outputCmd) + rootCmd.AddCommand(newDevCmd()) + rootCmd.AddCommand(newEnvCmd(engine)) + rootCmd.AddCommand(newRunCmd(engine, engineClients.ContainerTasks, engineClients.Getter, engineClients.HTTP, engineClients.System, vm, engineClients.Connector, l)) + rootCmd.AddCommand(newTestCmd()) + rootCmd.AddCommand(newDestroyCmd(engineClients.Connector)) + rootCmd.AddCommand(statusCmd) + rootCmd.AddCommand(newPurgeCmd(engineClients.Docker, engineClients.ImageLog, l)) + rootCmd.AddCommand(taintCmd) + rootCmd.AddCommand(newVersionCmd(vm)) + rootCmd.AddCommand(uninstallCmd) + rootCmd.AddCommand(newPushCmd(engineClients.ContainerTasks, engineClients.Kubernetes, engineClients.HTTP, engineClients.Nomad, l)) + rootCmd.AddCommand(newLogCmd(engine, engineClients.Docker, os.Stdout, os.Stderr), completionCmd) + + // add the server commands + rootCmd.AddCommand(connectorCmd) + connectorCmd.AddCommand(newConnectorRunCommand()) + connectorCmd.AddCommand(connectorStopCmd) + connectorCmd.AddCommand(newConnectorCertCmd()) + + // add the generate command + rootCmd.AddCommand(generateCmd) + generateCmd.AddCommand(newGenerateReadmeCommand(engine)) + rootCmd.SilenceErrors = true - err := rootCmd.Execute() + err = rootCmd.Execute() if err != nil { fmt.Println("") diff --git a/cmd/test.go b/cmd/test.go index d9bdb994..5f6bbc03 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -14,7 +14,9 @@ import ( "os" "os/exec" "path/filepath" + "reflect" "regexp" + "strconv" "strings" "syscall" "time" @@ -174,7 +176,12 @@ func (cr *CucumberRunner) initializeSuite(ctx *godog.ScenarioContext) { logger := createLogger() - engine, cli, vm := createEngine(logger) + cli, _ := clients.GenerateClients(logger) + engine, vm, err := createEngine(logger, cli) + if err != nil { + fmt.Printf("Unable to setup tests: %s\n", err) + return + } cr.e = engine cr.l = logger @@ -232,6 +239,7 @@ func (cr *CucumberRunner) initializeSuite(ctx *godog.ScenarioContext) { ctx.Step(`^I expect the exit code to be (\d+)$`, cr.iExpectTheExitCodeToBe) ctx.Step(`^I expect the response to contain "([^"]*)"$`, cr.iExpectTheResponseToContain) ctx.Step(`^a TCP connection to "([^"]*)" should open$`, aTCPConnectionToShouldOpen) + ctx.Step(`^the following output variables should be set$`, cr.theFollowingOutputVaraiblesShouldBeSet) } func (cr *CucumberRunner) iRunApply() error { @@ -249,23 +257,21 @@ func (cr *CucumberRunner) iRunApplyAtPath(path string) error { func (cr *CucumberRunner) iRunApplyAtPathWithVersion(fp, version string) error { output = bytes.NewBufferString("") - args := []string{} - // if filepath is not absolute then it will be relative to args absPath := filepath.Join(cr.basePath, fp) - args = []string{absPath} + args := []string{absPath} noOpen := true approve := true // re-use the run command rc := newRunCmdFunc( - engine, + cr.e, cr.cli.ContainerTasks, cr.cli.Getter, cr.cli.HTTP, - cr.cli.Browser, + cr.cli.System, cr.vm, cr.cli.Connector, &noOpen, @@ -411,7 +417,7 @@ func (cr *CucumberRunner) thereShouldBe1NetworkCalled(arg1 string) error { } if len(n) != 1 { - return fmt.Errorf("Expected 1 network called %s to be created", arg1) + return fmt.Errorf("expected 1 network called %s to be created", arg1) } return nil @@ -447,7 +453,7 @@ func (cr *CucumberRunner) aCallToShouldResultInStatus(arg1 string, arg2 int) err } if err == nil { - err = fmt.Errorf("Expected status code %d, got %d", arg2, resp.StatusCode) + err = fmt.Errorf("expected status code %d, got %d", arg2, resp.StatusCode) } time.Sleep(2 * time.Second) @@ -465,11 +471,11 @@ func (cr *CucumberRunner) theResponseBodyShouldContain(value string) error { s := r.FindString(respBody) if s == "" { - return fmt.Errorf("Expected value %s to be found in response %s", value, respBody) + return fmt.Errorf("expected value %s to be found in response %s", value, respBody) } } else { if !strings.Contains(respBody, value) { - return fmt.Errorf("Expected value %s to be found in response %s", value, respBody) + return fmt.Errorf("expected value %s to be found in response %s", value, respBody) } } @@ -480,14 +486,14 @@ func (cr *CucumberRunner) theFollowingEnvironmentVariablesAreSet(vars *godog.Tab for i, r := range vars.Rows { if i == 0 { if r.Cells[0].Value != "key" || r.Cells[1].Value != "value" { - return fmt.Errorf("Tables should be formatted with a header row containing the columns 'key' and 'value'") + return fmt.Errorf("tables should be formatted with a header row containing the columns 'key' and 'value'") } continue } if len(r.Cells) != 2 { - return fmt.Errorf("Table rows should have two columns 'key' and 'value'") + return fmt.Errorf("table rows should have two columns 'key' and 'value'") } // set the environment variable @@ -501,14 +507,14 @@ func (cr *CucumberRunner) theFollowingShipyardVariablesAreSet(vars *godog.Table) for i, r := range vars.Rows { if i == 0 { if r.Cells[0].Value != "key" || r.Cells[1].Value != "value" { - return fmt.Errorf("Tables should be formatted with a header row containing the columns 'key' and 'value'") + return fmt.Errorf("tables should be formatted with a header row containing the columns 'key' and 'value'") } continue } if len(r.Cells) != 2 { - return fmt.Errorf("Table rows should have two columns 'key' and 'value'") + return fmt.Errorf("table rows should have two columns 'key' and 'value'") } cr.variables = append(cr.variables, fmt.Sprintf("%s=%s", r.Cells[0].Value, r.Cells[1].Value)) @@ -538,7 +544,7 @@ func (cr *CucumberRunner) theResourceInfoShouldContain(path, resource, value str } if !strings.Contains(s, value) { - return fmt.Errorf("String %s is not found in value %s", value, s) + return fmt.Errorf("string %s is not found in value %s", value, s) } return nil @@ -551,7 +557,7 @@ func (cr *CucumberRunner) theResourceInfoShouldEqual(path, resource, value strin } if s != value { - return fmt.Errorf("String %s is not equal to %s", value, s) + return fmt.Errorf("string %s is not equal to %s", value, s) } return nil @@ -606,7 +612,7 @@ func (cr *CucumberRunner) whenIRunTheCommand(arg1 string) error { func (cr *CucumberRunner) iExpectTheExitCodeToBe(arg1 int) error { if commandExitCode != arg1 { - return fmt.Errorf("Expected exit code to be %d, got %d\nOutput:\n%s", arg1, commandExitCode, commandOutput.String()) + return fmt.Errorf("expected exit code to be %d, got %d\nOutput:\n%s", arg1, commandExitCode, commandOutput.String()) } return nil @@ -629,7 +635,7 @@ func (cr *CucumberRunner) iExpectTheResponseToContain(arg1 string) error { } } - return fmt.Errorf("Expected command output to contain %s.\n Output:\n%s", arg1, commandOutput.String()) + return fmt.Errorf("expected command output to contain %s.\n Output:\n%s", arg1, commandOutput.String()) } func aTCPConnectionToShouldOpen(addr string) error { @@ -649,6 +655,67 @@ func aTCPConnectionToShouldOpen(addr string) error { return err } +func (cr *CucumberRunner) theFollowingOutputVaraiblesShouldBeSet(arg1 *godog.Table) error { + c, err := config.LoadState() + if err != nil { + return fmt.Errorf("unable to load state") + } + + for i, row := range arg1.Rows { + if i == 0 { + if len(row.Cells) != 2 || row.Cells[0].Value != "name" || row.Cells[1].Value != "value" { + return fmt.Errorf("tables should be formatted with a header row containing the columns 'name' and value, e.g. | name | value |") + } + + continue + } + + // find the output + r, _ := c.FindResource("output." + row.Cells[0].Value) + if r == nil { + return fmt.Errorf("expected output variable %s to be set but was nil", row.Cells[0].Value) + } + + o := r.(*hcltypes.Output) + + switch v := o.Value.(type) { + case int: + s := strconv.Itoa(int(v)) + if s != row.Cells[1].Value { + return fmt.Errorf("output variable %s value is %s but expected %s", row.Cells[0].Value, s, row.Cells[1].Value) + } + case int32: + s := strconv.Itoa(int(v)) + if s != row.Cells[1].Value { + return fmt.Errorf("output variable %s value is %s but expected %s", row.Cells[0].Value, s, row.Cells[1].Value) + } + case int64: + s := strconv.Itoa(int(v)) + if s != row.Cells[1].Value { + return fmt.Errorf("output variable %s value is %s but expected %s", row.Cells[0].Value, s, row.Cells[1].Value) + } + case float32: + s := fmt.Sprintf("%f", v) + if s != row.Cells[1].Value { + return fmt.Errorf("output variable %s value is %s but expected %s", row.Cells[0].Value, s, row.Cells[1].Value) + } + case float64: + s := fmt.Sprintf("%f", v) + if s != row.Cells[1].Value { + return fmt.Errorf("output variable %s value is %s but expected %s", row.Cells[0].Value, s, row.Cells[1].Value) + } + case string: + if v != row.Cells[1].Value { + return fmt.Errorf("output variable %s value is %s but expected %s", row.Cells[0].Value, v, row.Cells[1].Value) + } + default: + return fmt.Errorf("output type is %s, unable to compare", reflect.TypeOf(o.Value).String()) + } + } + + return nil +} + func (cr *CucumberRunner) executeCommand(cmd string) error { // split command and args parts := strings.Split(cmd, " ") diff --git a/cmd/up.go b/cmd/up.go index 5abef853..67b15d50 100644 --- a/cmd/up.go +++ b/cmd/up.go @@ -85,15 +85,6 @@ func newRunCmdFunc(e jumppad.Engine, dt cclients.ContainerTasks, bp getter.Gette } } - // Check the system to see if Docker is running and everything is installed - s, err := bc.Preflight() - if err != nil { - cmd.Println("") - cmd.Println("###### SYSTEM DIAGNOSTICS ######") - cmd.Println(s) - return err - } - // check the variables file exists if variablesFile != nil && *variablesFile != "" { if _, err := os.Stat(*variablesFile); err != nil { @@ -146,21 +137,15 @@ func newRunCmdFunc(e jumppad.Engine, dt cclients.ContainerTasks, bp getter.Gette if !utils.IsLocalFolder(dst) && !utils.IsHCLFile(dst) { // fetch the remote server from github - err := bp.Get(dst, utils.GetBlueprintLocalFolder(dst)) + err := bp.Get(dst, utils.BlueprintLocalFolder(dst)) if err != nil { return fmt.Errorf("unable to retrieve blueprint: %s", err) } - dst = utils.GetBlueprintLocalFolder(dst) + dst = utils.BlueprintLocalFolder(dst) } } - // Parse the config to check it is valid - _, err = e.ParseConfigWithVariables(dst, vars, *variablesFile) - if err != nil { - return err - } - // update status every 30s to let people know we are still running statusUpdate := time.NewTicker(15 * time.Second) startTime := time.Now() diff --git a/examples/terraform/main.hcl b/examples/terraform/main.hcl new file mode 100644 index 00000000..f22baed8 --- /dev/null +++ b/examples/terraform/main.hcl @@ -0,0 +1,66 @@ +resource "network" "main" { + subnet = "10.10.0.0/16" +} + +resource "container" "vault" { + image { + name = "vault:1.13.3" + } + + network { + id = resource.network.main.id + } + + port { + local = 8200 + host = 8200 + } + + environment = { + VAULT_DEV_ROOT_TOKEN_ID = "root" + } +} + +resource "terraform" "configure_vault" { + network { + id = resource.network.main.id + } + + environment = { + VAULT_TOKEN = "root" + VAULT_ADDR = "http://${resource.container.vault.container_name}:8200" + } + + variables = { + first = "one" + second = 2 + third = { + x = 3 + y = 4 + } + } + + source = "./workspace" + working_directory = "/" + version = "1.6.2" +} + +output "first" { + value = resource.terraform.configure_vault.output.first +} + +output "second" { + value = resource.terraform.configure_vault.output.second +} + +output "third_x" { + value = resource.terraform.configure_vault.output.third.x +} + +output "third_y" { + value = resource.terraform.configure_vault.output.third.y +} + +output "vault_secret" { + value = resource.terraform.configure_vault.output.vault_secret +} \ No newline at end of file diff --git a/examples/terraform/test/terraform.feature b/examples/terraform/test/terraform.feature new file mode 100644 index 00000000..ba1eb7d8 --- /dev/null +++ b/examples/terraform/test/terraform.feature @@ -0,0 +1,20 @@ + +Feature: Terraform provider + In order to test the Terraform provider + I should apply a blueprint + And test the output + + Scenario: Simple example + Given I have a running blueprint + Then the following resources should be running + | name | + | resource.network.main | + | resource.container.vault | + And a HTTP call to "http://localhost:8200" should result in status 200 + And the following output variables should be set + | name | value | + | first | one | + | second | 2.000000 | + | third_x | 3.000000 | + | third_y | 4.000000 | + | vault_secret | zap | \ No newline at end of file diff --git a/examples/terraform/workspace/main.tf b/examples/terraform/workspace/main.tf new file mode 100644 index 00000000..061d0af5 --- /dev/null +++ b/examples/terraform/workspace/main.tf @@ -0,0 +1,21 @@ +resource "random_pet" "pet" {} + +variable "first" {} +variable "second" {} +variable "third" {} + +output "first" { + value = var.first +} + +output "second" { + value = var.second +} + +output "third" { + value = var.third +} + +output "test" { + value = "testing123" +} \ No newline at end of file diff --git a/examples/terraform/workspace/vault.tf b/examples/terraform/workspace/vault.tf new file mode 100644 index 00000000..ff73cbf9 --- /dev/null +++ b/examples/terraform/workspace/vault.tf @@ -0,0 +1,36 @@ +terraform { + required_providers { + vault = { + source = "hashicorp/vault" + version = "3.19.0" + } + } +} + +provider "vault" {} + +resource "vault_mount" "kvv1" { + path = "kvv1" + type = "kv" + options = { version = "1" } + description = "KV Version 1 secret engine mount" +} + +resource "vault_kv_secret" "secret" { + path = "${vault_mount.kvv1.path}/secret" + data_json = jsonencode( + { + zip = "zap", + foo = "bar" + } + ) +} + +data "vault_kv_secret" "secret_data" { + path = vault_kv_secret.secret.path +} + +output "vault_secret" { + sensitive = true + value = data.vault_kv_secret.secret_data.data.zip +} \ No newline at end of file diff --git a/go.mod b/go.mod index 3a60bb3c..2bfc19cc 100644 --- a/go.mod +++ b/go.mod @@ -26,9 +26,11 @@ require ( github.com/hashicorp/go-getter v1.7.0 github.com/hashicorp/go-hclog v1.1.0 github.com/hashicorp/go-uuid v1.0.2 + github.com/hashicorp/hcl/v2 v2.17.0 github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f github.com/jumppad-labs/connector v0.3.0 - github.com/jumppad-labs/hclconfig v0.15.1 + github.com/jumppad-labs/hclconfig v0.16.1 + github.com/kennygrant/sanitize v1.2.4 github.com/mailgun/raymond/v2 v2.0.48 github.com/moby/sys/signal v0.7.0 github.com/moby/term v0.5.0 @@ -44,8 +46,8 @@ require ( github.com/shipyard-run/version-manager v0.0.5 github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 - github.com/zclconf/go-cty v1.12.0 - golang.org/x/crypto v0.9.0 + github.com/zclconf/go-cty v1.13.0 + golang.org/x/crypto v0.14.0 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 google.golang.org/grpc v1.55.0 helm.sh/helm/v3 v3.8.2 @@ -73,7 +75,6 @@ require ( github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/agext/levenshtein v1.2.2 // indirect github.com/alecthomas/chroma v0.7.1 // indirect - github.com/apparentlymart/go-textseg v1.0.0 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect github.com/aws/aws-sdk-go v1.44.122 // indirect @@ -133,13 +134,13 @@ require ( github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect github.com/guillermo/go.procstat v0.0.0-20131123175440-34c2813d2e7f // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-memdb v1.3.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect - github.com/hashicorp/hcl2 v0.0.0-20191002203319-fb75b3253c80 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -201,12 +202,12 @@ require ( go.opencensus.io v0.24.0 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect golang.org/x/image v0.0.0-20191206065243-da761ea9ff43 // indirect - golang.org/x/net v0.10.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sync v0.2.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/term v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/api v0.126.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index edb39724..289337b5 100644 --- a/go.sum +++ b/go.sum @@ -275,7 +275,6 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= -github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= @@ -296,9 +295,6 @@ github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:C github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= -github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= -github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= -github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -337,7 +333,6 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dR github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/bsm/go-vlq v0.0.0-20150828105119-ec6e8d4f5f4e/go.mod h1:N+BjUcTjSxc2mtRGSCPsat1kze3CUtvJN3/jTXlp29k= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= @@ -702,7 +697,6 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= -github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc= github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= @@ -889,8 +883,8 @@ github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/ github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v0.0.0-20180715044906-d6c0cd880357/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -912,7 +906,6 @@ github.com/hashicorp/go-memdb v1.3.2 h1:RBKHOsnSszpU6vxq80LzC2BaQjuuvoyaQbkLTf7V github.com/hashicorp/go-memdb v1.3.2/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= -github.com/hashicorp/go-multierror v0.0.0-20180717150148-3d5d8f294aa0/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= @@ -936,8 +929,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/hcl2 v0.0.0-20191002203319-fb75b3253c80 h1:PFfGModn55JA0oBsvFghhj0v93me+Ctr3uHC/UmFAls= -github.com/hashicorp/hcl2 v0.0.0-20191002203319-fb75b3253c80/go.mod h1:Cxv+IJLuBiEhQ7pBYGEuORa0nr4U994pE8mYLuFd7v0= +github.com/hashicorp/hcl/v2 v2.17.0 h1:z1XvSUyXd1HP10U4lrLg5e0JMVz6CPaJvAgxM0KNZVY= +github.com/hashicorp/hcl/v2 v2.17.0/go.mod h1:gJyW2PTShkJqQBKpAmPO3yxMxIuoXkOF2TpqXzrQyx4= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= @@ -969,7 +962,6 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= @@ -1002,12 +994,14 @@ github.com/jumppad-labs/connector v0.3.0 h1:C+6YpMfhRBzuCAmkDpYEWbIIYN7q6gB9kWRP github.com/jumppad-labs/connector v0.3.0/go.mod h1:PqRmJZKayHw4j8JfNkugRMtdQF8ldjdTvtmTxF3O/44= github.com/jumppad-labs/go-cty v0.0.0-20230804061424-9e985cb751f6 h1:1ADItCWr5prIIwmKMHhIv4bqU5WY79CC28srzpi2CqI= github.com/jumppad-labs/go-cty v0.0.0-20230804061424-9e985cb751f6/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= -github.com/jumppad-labs/hclconfig v0.15.1 h1:b800Bj24MTDLwvL/ALNJEYKbPNoSHWFGkudj+Orchxw= -github.com/jumppad-labs/hclconfig v0.15.1/go.mod h1:uD7I35qbdyuwLu8D5cYbbkEzsexgov3o5A7l4iMeEBQ= +github.com/jumppad-labs/hclconfig v0.16.1 h1:hjxPnsB49cJP64vyJZjzP7+KcI/N1PnV3JKUeJntbXQ= +github.com/jumppad-labs/hclconfig v0.16.1/go.mod h1:9z3D2BUfFOWfw0iD7djsGXxFehnYc/K+rPUmrX092cc= github.com/jumppad-labs/log v0.0.0-20230711151418-55bbc87954b7 h1:jxF+Sxei1/7c7tLcV3Juu+rCIAhBv2jdicokriFohYE= github.com/jumppad-labs/log v0.0.0-20230711151418-55bbc87954b7/go.mod h1:ZApwwzDbbETVTIRTk7724yQRJAXIktt98yGVMMaa3y8= github.com/karrick/godirwalk v1.15.8 h1:7+rWAZPn9zuRxaIqqT8Ohs2Q2Ac0msBqwRdxNCr2VVs= github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o= +github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -1033,7 +1027,6 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kyokomi/emoji/v2 v2.2.8 h1:jcofPxjHWEkJtkIbcLHvZhxKgCPl6C7MyjTrD4KDqUE= github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= @@ -1129,7 +1122,6 @@ github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= @@ -1200,7 +1192,6 @@ github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6 github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -1213,7 +1204,6 @@ github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= @@ -1408,7 +1398,6 @@ github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0 github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -1463,8 +1452,6 @@ github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmF github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= -github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= @@ -1558,7 +1545,6 @@ golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1578,8 +1564,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1633,7 +1619,6 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= @@ -1696,8 +1681,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1757,7 +1742,6 @@ golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1878,16 +1862,16 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1899,8 +1883,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2290,7 +2274,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= diff --git a/pkg/clients/clients.go b/pkg/clients/clients.go index 48dff9cc..4be3ec1e 100644 --- a/pkg/clients/clients.go +++ b/pkg/clients/clients.go @@ -28,7 +28,7 @@ type Clients struct { Command command.Command Logger logger.Logger Getter getter.Getter - Browser system.System + System system.System ImageLog images.ImageLog Connector connector.Connector TarGz *tar.TarGz @@ -36,10 +36,7 @@ type Clients struct { // GenerateClients creates the various clients for creating and destroying resources func GenerateClients(l logger.Logger) (*Clients, error) { - dc, err := container.NewDocker() - if err != nil { - return nil, err - } + dc, _ := container.NewDocker() kc := k8s.NewKubernetes(60*time.Second, l) @@ -74,7 +71,7 @@ func GenerateClients(l logger.Logger) (*Clients, error) { Nomad: nc, Logger: l, Getter: bp, - Browser: bc, + System: bc, ImageLog: il, Connector: cc, TarGz: tgz, diff --git a/pkg/clients/helm/helm.go b/pkg/clients/helm/helm.go index 6ecc1997..cc62cdad 100644 --- a/pkg/clients/helm/helm.go +++ b/pkg/clients/helm/helm.go @@ -50,14 +50,14 @@ type HelmImpl struct { } func NewHelm(l logger.Logger) Helm { - helmCachePath := path.Join(utils.GetHelmLocalFolder(""), "cache") - helmRepoConfig := path.Join(utils.GetHelmLocalFolder(""), "repo") + helmCachePath := path.Join(utils.HelmLocalFolder(""), "cache") + helmRepoConfig := path.Join(utils.HelmLocalFolder(""), "repo") - helmDataPath := path.Join(utils.GetHelmLocalFolder(""), "data") - helmConfigPath := path.Join(utils.GetHelmLocalFolder(""), "config") + helmDataPath := path.Join(utils.HelmLocalFolder(""), "data") + helmConfigPath := path.Join(utils.HelmLocalFolder(""), "config") // create the paths - os.MkdirAll(utils.GetHelmLocalFolder(""), os.ModePerm) + os.MkdirAll(utils.HelmLocalFolder(""), os.ModePerm) os.MkdirAll(helmCachePath, os.ModePerm) os.MkdirAll(helmDataPath, os.ModePerm) diff --git a/pkg/config/resources/blueprint/resource.go b/pkg/config/resources/blueprint/resource.go index 835c6e7a..1a054ff6 100644 --- a/pkg/config/resources/blueprint/resource.go +++ b/pkg/config/resources/blueprint/resource.go @@ -9,8 +9,13 @@ const TypeBlueprint string = "blueprint" type Blueprint struct { types.ResourceMetadata `hcl:",remain"` - Title string `hcl:"title,optional" json:"title,omitempty"` - Author string `hcl:"author,optional" json:"author,omitempty"` - Slug string `hcl:"slug,optional" json:"slug,omitempty"` - Description string `hcl:"description,optional" json:"description,omitempty"` + Title string `hcl:"title,optional" json:"title,omitempty"` + Organization string `hcl:"organization,optional" json:"organization,omitempty"` + Author string `hcl:"author,optional" json:"author,omitempty"` + Authors []string `hcl:"authors,optional" json:"authors,omitempty"` + Slug string `hcl:"slug,optional" json:"slug,omitempty"` + Icon string `hcl:"icon,optional" json:"icon,omitempty"` + Tags []string `hcl:"tags,optional" json:"tags,omitempty"` + Summary string `hcl:"summary,optional" json:"summary,omitempty"` + Description string `hcl:"description,optional" json:"description,omitempty"` } diff --git a/pkg/config/resources/container/resource_port.go b/pkg/config/resources/container/resource_port.go index 60a4d22c..8fe306ba 100644 --- a/pkg/config/resources/container/resource_port.go +++ b/pkg/config/resources/container/resource_port.go @@ -3,7 +3,7 @@ package container // Port is a port mapping type Port struct { Local string `hcl:"local" json:"local"` // Local port in the container - Remote string `hcl:"remote" json:"remote"` // Remote port of the service + Remote string `hcl:"remote,optional" json:"remote,omitempty"` // Remote port of the service Host string `hcl:"host,optional" json:"host,omitempty"` // Host port Protocol string `hcl:"protocol,optional" json:"protocol,omitempty"` // Protocol tcp, udp OpenInBrowser string `hcl:"open_in_browser,optional" json:"open_in_browser" mapstructure:"open_in_browser"` // When a host port is defined open this port with the given path in a browser diff --git a/pkg/config/resources/container/resource_sidecar.go b/pkg/config/resources/container/resource_sidecar.go index 8022ff0d..0f7b83d6 100644 --- a/pkg/config/resources/container/resource_sidecar.go +++ b/pkg/config/resources/container/resource_sidecar.go @@ -37,7 +37,7 @@ type Sidecar struct { // ContainerName is the fully qualified domain name for the container the sidecar is linked to, this can be used // to access the sidecar from other sources - ContainerName string `hcl:"c,optional" json:"fqrn,omitempty"` + ContainerName string `hcl:"container_name,optional" json:"container_name,omitempty"` } func (c *Sidecar) Process() error { diff --git a/pkg/config/resources/container/sidecar_test.go b/pkg/config/resources/container/sidecar_test.go index f54dfeb3..7a6dbf49 100644 --- a/pkg/config/resources/container/sidecar_test.go +++ b/pkg/config/resources/container/sidecar_test.go @@ -38,7 +38,7 @@ func TestSidecarLoadsValuesFromState(t *testing.T) { "name": "test", "status": "created", "type": "sidecar", - "fqdn": "fqdn.mine" + "container_name": "fqdn.mine" } ] }`) diff --git a/pkg/config/resources/docs/provider_book.go b/pkg/config/resources/docs/provider_book.go index 933a8f20..7bc3b766 100644 --- a/pkg/config/resources/docs/provider_book.go +++ b/pkg/config/resources/docs/provider_book.go @@ -2,9 +2,11 @@ package docs import ( "fmt" + "os" htypes "github.com/jumppad-labs/hclconfig/types" "github.com/jumppad-labs/jumppad/pkg/clients/logger" + "github.com/jumppad-labs/jumppad/pkg/utils" ) type BookProvider struct { @@ -46,7 +48,10 @@ func (p *BookProvider) Create() error { } func (p *BookProvider) Destroy() error { - return nil + // clean up the library folder + err := os.RemoveAll(utils.LibraryFolder("", os.ModePerm)) + + return err } func (p *BookProvider) Lookup() ([]string, error) { diff --git a/pkg/config/resources/docs/provider_docs.go b/pkg/config/resources/docs/provider_docs.go index d2cd2a92..a83a9e1e 100644 --- a/pkg/config/resources/docs/provider_docs.go +++ b/pkg/config/resources/docs/provider_docs.go @@ -126,7 +126,7 @@ func (p *DocsProvider) Refresh() error { p.log.Debug("Refresh Docs", "ref", p.config.ID) // refresh content on disk - configPath := utils.GetLibraryFolder("config", 0775) + configPath := utils.LibraryFolder("config", 0775) // jumppad.config.js frontendConfigPath := filepath.Join(configPath, "jumppad.config.js") @@ -150,7 +150,7 @@ func (p *DocsProvider) Refresh() error { } // /content - contentPath := utils.GetLibraryFolder("content", 0775) + contentPath := utils.LibraryFolder("content", 0775) for _, book := range p.config.Content { bookPath := filepath.Join(contentPath, book.Name) @@ -241,7 +241,7 @@ func (p *DocsProvider) createDocsContainer() error { } // ~/.jumppad/library/content - contentPath := utils.GetLibraryFolder("content", 0775) + contentPath := utils.LibraryFolder("content", 0775) for _, book := range p.config.Content { bookPath := filepath.Join(contentPath, book.Name) @@ -272,7 +272,7 @@ func (p *DocsProvider) createDocsContainer() error { ) // ~/.jumppad/library/config - configPath := utils.GetLibraryFolder("config", 0775) + configPath := utils.LibraryFolder("config", 0775) // write the navigation navigationPath := filepath.Join(configPath, "navigation.jsx") @@ -297,7 +297,7 @@ func (p *DocsProvider) createDocsContainer() error { ) // write the frontend config - frontendConfigPath := filepath.Join(utils.GetLibraryFolder("", 0775), "jumppad.config.js") + frontendConfigPath := filepath.Join(utils.LibraryFolder("", 0775), "jumppad.config.js") err = p.writeConfig(frontendConfigPath) if err != nil { return err diff --git a/pkg/config/resources/docs/resource_task.go b/pkg/config/resources/docs/resource_task.go index 33af458b..c2e5585c 100644 --- a/pkg/config/resources/docs/resource_task.go +++ b/pkg/config/resources/docs/resource_task.go @@ -10,7 +10,7 @@ type Task struct { types.ResourceMetadata `hcl:",remain"` Prerequisites []string `hcl:"prerequisites,optional" json:"prerequisites"` - Config Config `hcl:"config,block" json:"config,omitempty"` + Config *Config `hcl:"config,block" json:"config,omitempty"` Conditions []Condition `hcl:"condition,block" json:"conditions"` Status string `hcl:"status,optional" json:"status"` } @@ -42,6 +42,10 @@ type Config struct { func (t *Task) Process() error { // Set defaults + if t.Config == nil { + t.Config = &Config{} + } + if t.Config.Timeout == 0 { t.Config.Timeout = 30 } diff --git a/pkg/config/resources/exec/provider_remote.go b/pkg/config/resources/exec/provider_remote.go index d1be99ba..bb6dc9d4 100644 --- a/pkg/config/resources/exec/provider_remote.go +++ b/pkg/config/resources/exec/provider_remote.go @@ -124,8 +124,7 @@ func (p *RemoteProvider) Changed() (bool, error) { } func (p *RemoteProvider) createRemoteExecContainer() (string, error) { - // generate the ID for the new container based on the clock time and a string - fqdn := utils.FQDN(p.config.Name, p.config.Type, p.config.Module) + fqdn := utils.FQDN(p.config.Name, p.config.Module, p.config.Type) new := ctypes.Container{ Name: fqdn, diff --git a/pkg/config/resources/helm/provider.go b/pkg/config/resources/helm/provider.go index ca4be78d..93b79193 100644 --- a/pkg/config/resources/helm/provider.go +++ b/pkg/config/resources/helm/provider.go @@ -68,7 +68,7 @@ func (p *Provider) Create() error { if !utils.IsLocalFolder(p.config.Chart) && p.config.Repository == nil { p.log.Debug("Fetching remote Helm chart", "ref", p.config.Name, "chart", p.config.Chart) - helmFolder := utils.GetHelmLocalFolder(p.config.Chart) + helmFolder := utils.HelmLocalFolder(p.config.Chart) err := p.getterClient.Get(p.config.Chart, helmFolder) if err != nil { diff --git a/pkg/config/resources/terraform/provider.go b/pkg/config/resources/terraform/provider.go new file mode 100644 index 00000000..53d9f0b2 --- /dev/null +++ b/pkg/config/resources/terraform/provider.go @@ -0,0 +1,406 @@ +package terraform + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "path" + "path/filepath" + "reflect" + + "github.com/hashicorp/hcl/v2/hclwrite" + "github.com/jumppad-labs/hclconfig/convert" + htypes "github.com/jumppad-labs/hclconfig/types" + "github.com/jumppad-labs/jumppad/pkg/clients" + cclient "github.com/jumppad-labs/jumppad/pkg/clients/container" + ctypes "github.com/jumppad-labs/jumppad/pkg/clients/container/types" + "github.com/jumppad-labs/jumppad/pkg/clients/logger" + "github.com/jumppad-labs/jumppad/pkg/utils" + "github.com/kennygrant/sanitize" + "github.com/zclconf/go-cty/cty" +) + +const terraformImageName = "hashicorp/terraform" + +// TerraformProvider provider allows the execution of terraform config +type TerraformProvider struct { + config *Terraform + client cclient.ContainerTasks + log logger.Logger +} + +func (p *TerraformProvider) Init(cfg htypes.Resource, l logger.Logger) error { + c, ok := cfg.(*Terraform) + if !ok { + return fmt.Errorf("unable to initialize Terraform provider, resource is not of type Terraform") + } + + cli, err := clients.GenerateClients(l) + if err != nil { + return err + } + + p.config = c + p.client = cli.ContainerTasks + p.log = l + + return nil +} + +// Create a new terraform container +func (p *TerraformProvider) Create() error { + p.log.Info("Creating Terraform", "ref", p.config.ID) + + err := p.generateVariables() + if err != nil { + return fmt.Errorf("unable to generate variables file: %w", err) + } + + // terraform init & terraform apply + id, err := p.createContainer() + if err != nil { + return fmt.Errorf("unable to create container for terraform.%s: %w", p.config.Name, err) + } + + // always remove the container + defer p.client.RemoveContainer(id, true) + + err = p.terraformApply(id) + if err != nil { + return fmt.Errorf("unable to run apply for terraform.%s: %w", p.config.Name, err) + } + + err = p.generateOutput() + if err != nil { + return fmt.Errorf("unable to generate output: %w", err) + } + + // set the checksum for the source folder + hash, err := utils.HashDir(p.config.Source, "**/.terraform.lock.hcl") + if err != nil { + return fmt.Errorf("unable to hash source directory: %w", err) + } + + p.config.SourceChecksum = hash + + return nil +} + +// Destroy the terraform container +func (p *TerraformProvider) Destroy() error { + p.log.Info("Destroy Terraform", "ref", p.config.ID) + + id, err := p.createContainer() + if err != nil { + return fmt.Errorf("unable to create container for Terraform.%s: %w", p.config.Name, err) + } + + // always remove the container + defer p.client.RemoveContainer(id, true) + + err = p.terraformDestroy(id) + if err != nil { + return fmt.Errorf("unable to destroy Terraform configuration: %w", err) + } + + // remove the temp folders + os.RemoveAll(terraformStateFolder(p.config)) + + return err +} + +// Lookup satisfies the interface requirements but is not used +// as the resource is not persistent +func (p *TerraformProvider) Lookup() ([]string, error) { + return []string{}, nil +} + +func (p *TerraformProvider) Refresh() error { + // has the source folder changed? + changed, err := p.Changed() + if err != nil { + return err + } + + if changed { + // with Terraform resources we can just re-call apply rather than + // destroying and then running create. + p.log.Debug("Refresh Terraform", "ref", p.config.ID) + return p.Create() + } + + // nothing changed set the outputs as these are not persisted to state + err = p.generateOutput() + if err != nil { + return fmt.Errorf("unable to set outputs: %w", err) + } + + return nil +} + +// Changed checks to see if the resource files have changed since the last apply +func (p *TerraformProvider) Changed() (bool, error) { + // check if the hash for the source folder has changed + newHash, err := utils.HashDir(p.config.Source, "**/.terraform.lock.hcl") + if err != nil { + return true, fmt.Errorf("error hashing source directory: %w", err) + } + + if newHash != p.config.SourceChecksum { + p.log.Debug("Terraform source folder changed", "ref", p.config.ID) + return true, nil + } + + p.log.Debug("Terraform source folder unchanged", "ref", p.config.ID) + return false, nil +} + +// generate tfvars file with the passed in variables +func (p *TerraformProvider) generateVariables() error { + // do nothing if empty + if p.config.Variables.IsNull() { + return nil + } + + statePath := terraformStateFolder(p.config) + + f := hclwrite.NewEmptyFile() + root := f.Body() + + if !p.config.Variables.Type().IsObjectType() && !p.config.Variables.Type().IsMapType() && !p.config.Variables.Type().IsTupleType() { + return fmt.Errorf("error: variables is not a map") + } + + for k, v := range p.config.Variables.AsValueMap() { + root.SetAttributeValue(k, v) + } + + variablesPath := filepath.Join(statePath, "terraform.tfvars") + err := os.WriteFile(variablesPath, f.Bytes(), 0755) + if err != nil { + return fmt.Errorf("unable to write variables to disk at %s", variablesPath) + } + + return nil +} + +func (p *TerraformProvider) createContainer() (string, error) { + fqdn := utils.FQDN(p.config.Name, p.config.Module, p.config.Type) + statePath := terraformStateFolder(p.config) + cachePath := terraformCacheFolder() + + image := fmt.Sprintf("%s:%s", terraformImageName, p.config.Version) + + // set the plugin cache so this is re-used + if p.config.Environment == nil { + p.config.Environment = map[string]string{} + } + + p.config.Environment["TF_PLUGIN_CACHE_DIR"] = "/var/lib/terraform.d" + + tf := ctypes.Container{ + Name: fqdn, + Image: &ctypes.Image{Name: image}, + Environment: p.config.Environment, + } + + for _, v := range p.config.Networks { + tf.Networks = append(tf.Networks, ctypes.NetworkAttachment{ + ID: v.ID, + Name: v.Name, + IPAddress: v.IPAddress, + Aliases: v.Aliases, + }) + } + + // Add the config folder + tf.Volumes = append(tf.Volumes, ctypes.Volume{ + Source: p.config.Source, + Destination: "/config", + Type: "bind", + ReadOnly: false, + }) + + // Add the state folder + tf.Volumes = append(tf.Volumes, ctypes.Volume{ + Source: statePath, + Destination: "/var/lib/terraform", + }) + + // Add the plugin cache + tf.Volumes = append(tf.Volumes, ctypes.Volume{ + Source: cachePath, + Destination: "/var/lib/terraform.d", + }) + + tf.Entrypoint = []string{} + tf.Command = []string{"tail", "-f", "/dev/null"} // ensure container does not immediately exit + + // pull any images needed for this container + err := p.client.PullImage(*tf.Image, false) + if err != nil { + p.log.Error("Error pulling container image", "ref", p.config.ID, "image", tf.Image.Name) + + return "", err + } + + id, err := p.client.CreateContainer(&tf) + if err != nil { + p.log.Error("Error creating container for terraform", "ref", p.config.Name, "image", tf.Image.Name, "networks", p.config.Networks) + return "", err + } + + return id, err +} + +func (p *TerraformProvider) terraformApply(containerid string) error { + + // allways run the cleanup + defer func() { + script := "rm -rf /config/.terraform" + _, err := p.client.ExecuteScript(containerid, script, []string{}, "/", "root", "", 300, nil) + if err != nil { + p.log.Debug("unable to remove .terraform folder", "error", err) + } + }() + + // build the environment variables + envs := []string{} + for k, v := range p.config.Environment { + envs = append(envs, fmt.Sprintf("%s=%s", k, v)) + } + + script := `#!/bin/sh + terraform init + terraform apply \ + -state=/var/lib/terraform/terraform.tfstate \ + -var-file=/var/lib/terraform/terraform.tfvars \ + -auto-approve + terraform output \ + -state=/var/lib/terraform/terraform.tfstate \ + -json > /var/lib/terraform/output.json` + + wd := path.Join("/config", p.config.WorkingDirectory) + + planOutput := bytes.NewBufferString("") + + _, err := p.client.ExecuteScript(containerid, script, envs, wd, "root", "", 300, planOutput) + + // write the plan output to the log + p.config.ApplyOutput = planOutput.String() + p.log.Debug("terraform apply output", "id", p.config.ID, "output", planOutput) + + if err != nil { + p.log.Error("Error executing terraform apply", "ref", p.config.Name) + err = fmt.Errorf("unable to execute terraform apply: %w", err) + return err + } + + return nil +} + +func (p *TerraformProvider) generateOutput() error { + statePath := terraformStateFolder(p.config) + outputPath := path.Join(statePath, "output.json") + + data, err := os.ReadFile(outputPath) + if err != nil { + return fmt.Errorf("unable to read terraform output: %w", err) + } + + var output map[string]interface{} + err = json.Unmarshal(data, &output) + if err != nil { + return fmt.Errorf("unable to parse terraform output: %w", err) + } + + values := map[string]cty.Value{} + for k, v := range output { + m, ok := v.(map[string]interface{}) + if !ok { + return fmt.Errorf("terraform output is not in the correct format, expected map[string]interface{} for value but got %T", v) + } + + value, err := convert.GoToCtyValue(m["value"]) + if err != nil { + if reflect.TypeOf(m["type"]).Kind() == reflect.Slice { + obj := map[string]cty.Value{} + + for l, w := range m["value"].(map[string]interface{}) { + subvalue, err := convert.GoToCtyValue(w) + if err != nil { + p.log.Error("could not convert variable", "key", l, "value", w, "error", err) + return err + } + + obj[l] = subvalue + } + + values[k] = cty.ObjectVal(obj) + } + } else { + values[k] = value + } + } + + p.config.Output = cty.ObjectVal(values) + + return nil +} + +func (p *TerraformProvider) terraformDestroy(containerid string) error { + // build the environment variables + envs := []string{} + for k, v := range p.config.Environment { + envs = append(envs, fmt.Sprintf("%s=%s", k, v)) + } + + // check to see if the state files exist, if not then this resource might not + // have been created correctly so just exit + statePath := terraformStateFolder(p.config) + _, err := os.Stat(path.Join(statePath, "terraform.tfstate")) + if err != nil { + return nil + } + + _, err = os.Stat(path.Join(statePath, "terraform.tfvars")) + if err != nil { + return nil + } + + wd := path.Join("/config", p.config.WorkingDirectory) + + script := `#!/bin/sh + terraform init + terraform destroy \ + -state=/var/lib/terraform/terraform.tfstate \ + -var-file=/var/lib/terraform/terraform.tfvars \ + -auto-approve + ` + _, err = p.client.ExecuteScript(containerid, script, envs, wd, "root", "", 300, p.log.StandardWriter()) + if err != nil { + p.log.Error("Error executing terraform destroy", "ref", p.config.Name) + err = fmt.Errorf("unable to execute terraform destroy: %w", err) + return err + } + + return nil +} + +// GetTerraformFolder creates the terraform directory used by the application +func terraformStateFolder(r *Terraform) string { + p := sanitize.Path(htypes.FQDNFromResource(r).String()) + data := filepath.Join(utils.JumppadHome(), "terraform", "state", p) + + // create the folder if it does not exist + os.MkdirAll(data, 0755) + os.Chmod(data, 0755) + + return data +} + +func terraformCacheFolder() string { + // the cache folder is + return utils.CacheFolder("terraform", 0755) +} diff --git a/pkg/config/resources/terraform/provider_test.go b/pkg/config/resources/terraform/provider_test.go new file mode 100644 index 00000000..80f6bc91 --- /dev/null +++ b/pkg/config/resources/terraform/provider_test.go @@ -0,0 +1,190 @@ +package terraform + +import ( + "os" + "path" + "testing" + + "github.com/jumppad-labs/hclconfig/types" + "github.com/jumppad-labs/jumppad/pkg/clients/container/mocks" + ctypes "github.com/jumppad-labs/jumppad/pkg/clients/container/types" + "github.com/jumppad-labs/jumppad/pkg/clients/logger" + "github.com/jumppad-labs/jumppad/pkg/config/resources/container" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/zclconf/go-cty/cty" +) + +func setupProvider(t *testing.T, c *Terraform) (*TerraformProvider, *mocks.ContainerTasks, string) { + // set the home directory to a temporary directory so that everthing is cleaned up + tmpDir := t.TempDir() + os.Setenv("HOME", tmpDir) + + // output should always exist + sd := terraformStateFolder(c) + os.WriteFile(path.Join(sd, "output.json"), []byte("{\"abc\": {\"value\": \"123\"}}"), 0655) + + mc := &mocks.ContainerTasks{} + mc.Mock.On("PullImage", mock.Anything, false).Return(nil) + mc.Mock.On("CreateContainer", mock.Anything).Return("abc", nil) + mc.Mock.On("ExecuteScript", "abc", mock.Anything, mock.Anything, mock.Anything, "root", mock.Anything, 300, mock.Anything).Return(0, nil) + mc.Mock.On("RemoveContainer", "abc", true).Return(nil) + + l := logger.NewTestLogger(t) + p := &TerraformProvider{c, mc, l} + + return p, mc, sd +} + +func TestCreateWithNoVariablesDoesNotReturnError(t *testing.T) { + p, _, _ := setupProvider(t, &Terraform{ResourceMetadata: types.ResourceMetadata{Name: "test"}}) + + err := p.Create() + require.NoError(t, err) +} + +func TestCreateWithWithVariablesGeneratesFile(t *testing.T) { + variables := cty.MapVal(map[string]cty.Value{ + "foo": cty.StringVal("bar"), + }) + + p, _, sd := setupProvider(t, &Terraform{ + ResourceMetadata: types.ResourceMetadata{Name: "test"}, + Variables: variables, + }, + ) + + err := p.Create() + require.NoError(t, err) + + // check variables file + d, err := os.ReadFile(path.Join(sd, "terraform.tfvars")) + require.NoError(t, err) + require.Equal(t, "foo = \"bar\"\n", string(d)) +} + +func TestCreatesTerraformContainerWithTheCorrectValues(t *testing.T) { + res := &Terraform{ + ResourceMetadata: types.ResourceMetadata{Name: "test"}, + Networks: []container.NetworkAttachment{ + container.NetworkAttachment{ + ID: "Abc123", + }, + }, + Version: "1.16.2", + Source: "../../../../examples/terraform/workspace", + } + + p, m, _ := setupProvider(t, res) + + err := p.Create() + require.NoError(t, err) + + c := m.Calls[1].Arguments[0].(*ctypes.Container) + + // ensure the terraform plugin cache is added so that plugins are not constantly downloaded + require.Equal(t, c.Environment["TF_PLUGIN_CACHE_DIR"], "/var/lib/terraform.d") + + // check the correct image is used + require.Equal(t, c.Image.Name, "hashicorp/terraform:1.16.2") + + // check the networks have been added + require.Equal(t, c.Networks[0].ID, "Abc123") + + // check the convig has been added + require.Equal(t, "../../../../examples/terraform/workspace", c.Volumes[0].Source) + require.Equal(t, "/config", c.Volumes[0].Destination) + + // check the state volume has been added + require.Equal(t, terraformStateFolder(res), c.Volumes[1].Source) + require.Equal(t, "/var/lib/terraform", c.Volumes[1].Destination) + + // check the plugin cache has been added + require.Equal(t, terraformCacheFolder(), c.Volumes[2].Source) + require.Equal(t, "/var/lib/terraform.d", c.Volumes[2].Destination) +} + +func TestCreateExecutesCommandInContainer(t *testing.T) { + res := &Terraform{ + ResourceMetadata: types.ResourceMetadata{Name: "test"}, + Networks: []container.NetworkAttachment{ + container.NetworkAttachment{ + ID: "Abc123", + }, + }, + WorkingDirectory: "/test", + } + + p, m, _ := setupProvider(t, res) + + err := p.Create() + require.NoError(t, err) + + // assert script was executed in container + script := m.Calls[2].Arguments[1].(string) + + require.Contains(t, script, "terraform init") + require.Contains(t, script, "terraform apply") + require.Contains(t, script, "terraform output") + + // check the working directory is set + wd := m.Calls[2].Arguments[3].(string) + require.Equal(t, "/config/test", wd) + + // ensure that the .terraform directory is removed + // this contains the providers but these are symlinks + // from the cache + // assert cleanup was executed in container + script = m.Calls[3].Arguments[1].(string) + require.Contains(t, script, "rm -rf /config/") +} + +func TestCreateSetsOutput(t *testing.T) { + res := &Terraform{ + ResourceMetadata: types.ResourceMetadata{Name: "test"}, + Networks: []container.NetworkAttachment{ + container.NetworkAttachment{ + ID: "Abc123", + }, + }, + WorkingDirectory: "/test", + } + + p, _, _ := setupProvider(t, res) + + err := p.Create() + require.NoError(t, err) + + require.Equal(t, "123", res.Output.AsValueMap()["abc"].AsString()) +} + +func TestDestroyExecutesCommandInContainer(t *testing.T) { + res := &Terraform{ + ResourceMetadata: types.ResourceMetadata{Name: "test"}, + Networks: []container.NetworkAttachment{ + container.NetworkAttachment{ + ID: "Abc123", + }, + }, + WorkingDirectory: "/test", + } + + p, m, sd := setupProvider(t, res) + + // setup fake state or destroy will not do anything + os.WriteFile(path.Join(sd, "terraform.tfstate"), []byte("{\"abc\": {\"value\": \"123\"}}"), 0655) + os.WriteFile(path.Join(sd, "terraform.tfvars"), []byte("{\"abc\": {\"value\": \"123\"}}"), 0655) + + err := p.Destroy() + require.NoError(t, err) + + // assert script was executed in container + script := m.Calls[2].Arguments[1].(string) + + require.Contains(t, script, "terraform init") + require.Contains(t, script, "terraform destroy") + + // check the working directory is set + wd := m.Calls[2].Arguments[3].(string) + require.Equal(t, "/config/test", wd) +} diff --git a/pkg/config/resources/terraform/resource.go b/pkg/config/resources/terraform/resource.go new file mode 100644 index 00000000..07acda55 --- /dev/null +++ b/pkg/config/resources/terraform/resource.go @@ -0,0 +1,68 @@ +package terraform + +import ( + "path" + "strings" + + "github.com/jumppad-labs/hclconfig/types" + "github.com/jumppad-labs/jumppad/pkg/config" + ctypes "github.com/jumppad-labs/jumppad/pkg/config/resources/container" + "github.com/jumppad-labs/jumppad/pkg/utils" + "github.com/zclconf/go-cty/cty" +) + +// TypeTerraform is the resource string for a Terraform resource +const TypeTerraform string = "terraform" + +// ExecRemote allows commands to be executed in remote containers +type Terraform struct { + types.ResourceMetadata `hcl:",remain"` + + Networks []ctypes.NetworkAttachment `hcl:"network,block" json:"networks,omitempty"` // Attach to the correct network // only when Image is specified + + Source string `hcl:"source" json:"source"` // Source directory containing Terraform config + Version string `hcl:"version,optional" json:"version,omitempty"` // Version of terraform to use + WorkingDirectory string `hcl:"working_directory,optional" json:"working_directory,omitempty"` // Working directory to run terraform commands + Environment map[string]string `hcl:"environment,optional" json:"environment,omitempty"` // environment variables to set when starting the container + Variables cty.Value `hcl:"variables,optional" json:"-"` // variables to pass to terraform + + // Computed values + + Output cty.Value `hcl:"output,optional"` // output values returned from Terraform + SourceChecksum string `hcl:"source_checksum,optional" json:"source_checksum,omitempty"` // checksum of the source directory + ApplyOutput string `hcl:"apply_output,optional"` // output from the terraform apply +} + +func (t *Terraform) Process() error { + // make sure mount paths are absolute + t.Source = utils.EnsureAbsolute(t.Source, t.File) + + if t.WorkingDirectory == "" { + t.WorkingDirectory = "./" + } else { + if !strings.HasPrefix(t.WorkingDirectory, "/") { + t.WorkingDirectory = "/" + t.WorkingDirectory + } + + t.WorkingDirectory = path.Clean("." + t.WorkingDirectory) + } + + // set the base version + if t.Version == "" { + t.Version = "1.16.2" + } + + // restore the applyoutput from the state + cfg, err := config.LoadState() + if err == nil { + // try and find the resource in the state + r, _ := cfg.FindResource(t.ID) + if r != nil { + kstate := r.(*Terraform) + t.ApplyOutput = kstate.ApplyOutput + t.SourceChecksum = kstate.SourceChecksum + } + } + + return nil +} diff --git a/pkg/config/zz_hclparser.go b/pkg/config/zz_hclparser.go index 36b09043..49605150 100644 --- a/pkg/config/zz_hclparser.go +++ b/pkg/config/zz_hclparser.go @@ -35,9 +35,9 @@ func RegisterResource(name string, r types.Resource, p Provider) { } // setupHCLConfig configures the HCLConfig package and registers the custom types -func NewParser(callback hclconfig.ProcessCallback, variables map[string]string, variablesFiles []string) *hclconfig.Parser { +func NewParser(callback hclconfig.WalkCallback, variables map[string]string, variablesFiles []string) *hclconfig.Parser { cfg := hclconfig.DefaultOptions() - cfg.ParseCallback = callback + cfg.Callback = callback cfg.Variables = variables cfg.VariablesFiles = variablesFiles @@ -81,10 +81,10 @@ func customHCLFuncDataFolderWithPermissions(name string, permissions int) (strin oInt, _ := strconv.ParseInt(strInt, 8, 64) perms := os.FileMode(oInt) - return utils.GetDataFolder(name, perms), nil + return utils.DataFolder(name, perms), nil } func customHCLFuncDataFolder(name string) (string, error) { perms := os.FileMode(0775) - return utils.GetDataFolder(name, perms), nil + return utils.DataFolder(name, perms), nil } diff --git a/pkg/jumppad/engine.go b/pkg/jumppad/engine.go index 9c5d08fa..3b93daa6 100644 --- a/pkg/jumppad/engine.go +++ b/pkg/jumppad/engine.go @@ -10,6 +10,7 @@ import ( "path/filepath" "github.com/jumppad-labs/hclconfig" + hclerrors "github.com/jumppad-labs/hclconfig/errors" "github.com/jumppad-labs/hclconfig/types" "github.com/jumppad-labs/jumppad/pkg/clients/logger" "github.com/jumppad-labs/jumppad/pkg/config" @@ -96,6 +97,10 @@ func (e *EngineImpl) ParseConfigWithVariables(path string, vars map[string]strin return nil }) + if err != nil && err.(*hclerrors.ConfigError).ContainsErrors() { + e.log.Error("Error parsing configuration", "error", err) + } + return e.config, err } @@ -110,9 +115,19 @@ func (e *EngineImpl) Diff(path string, variables map[string]string, variablesFil past, _ := config.LoadState() // Parse the config to check it is valid - res, err := e.ParseConfigWithVariables(path, variables, variablesFile) - if err != nil { - return nil, nil, nil, nil, err + res, parseErr := e.ParseConfigWithVariables(path, variables, variablesFile) + + if parseErr != nil { + // cast the error to a config error + ce := parseErr.(*hclerrors.ConfigError) + + // if we have parser errors return them + // if not it is possible to get process errors at this point as the + // callbacks have not been called for the providers, any referenced + // resources will not be found, it is ok to ignore these errors + if ce.ContainsErrors() { + return nil, nil, nil, nil, parseErr + } } unchanged := []types.Resource{} @@ -129,7 +144,7 @@ func (e *EngineImpl) Diff(path string, variables map[string]string, variablesFil } // check if the resource has changed - if cr.Metadata().Checksum.Parsed != r.Metadata().Checksum.Parsed { + if cr.Metadata().Checksum != r.Metadata().Checksum { // resource has changes rebuild changed = append(changed, r) continue @@ -180,7 +195,7 @@ func (e *EngineImpl) Diff(path string, variables map[string]string, variablesFil } } - return new, changed, removed, res, err + return new, changed, removed, res, nil } // Apply the configuration and create or destroy the resources @@ -305,7 +320,7 @@ func (e *EngineImpl) Destroy() error { // image cache which is manually added by Apply process // should have the correct dependency graph to be // destroyed last - err = e.config.Process(e.destroyCallback, true) + err = e.config.Walk(e.destroyCallback, true) if err != nil { // return the process error @@ -331,7 +346,7 @@ func (e *EngineImpl) ResourceCountForType(t string) int { return len(r) } -func (e *EngineImpl) readAndProcessConfig(path string, variables map[string]string, variablesFile string, callback hclconfig.ProcessCallback) error { +func (e *EngineImpl) readAndProcessConfig(path string, variables map[string]string, variablesFile string, callback hclconfig.WalkCallback) error { var parseError error var parsedConfig *hclconfig.Config diff --git a/pkg/jumppad/init.go b/pkg/jumppad/init.go index dbc9f870..b96f01ff 100644 --- a/pkg/jumppad/init.go +++ b/pkg/jumppad/init.go @@ -19,6 +19,7 @@ import ( "github.com/jumppad-labs/jumppad/pkg/config/resources/null" "github.com/jumppad-labs/jumppad/pkg/config/resources/random" "github.com/jumppad-labs/jumppad/pkg/config/resources/template" + "github.com/jumppad-labs/jumppad/pkg/config/resources/terraform" ) func init() { @@ -49,6 +50,7 @@ func init() { config.RegisterResource(random.TypeRandomPassword, &random.RandomPassword{}, &random.RandomPasswordProvider{}) config.RegisterResource(random.TypeRandomCreature, &random.RandomCreature{}, &random.RandomCreatureProvider{}) config.RegisterResource(template.TypeTemplate, &template.Template{}, &template.TemplateProvider{}) + config.RegisterResource(terraform.TypeTerraform, &terraform.Terraform{}, &terraform.TerraformProvider{}) config.RegisterResource(types.TypeModule, &types.Module{}, &null.Provider{}) config.RegisterResource(types.TypeOutput, &types.Output{}, &null.Provider{}) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 5d5af919..9d80b67c 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -18,6 +18,7 @@ import ( "sync" "github.com/jumppad-labs/jumppad/pkg/utils/dirhash" + "github.com/kennygrant/sanitize" ) // EnsureAbsolute ensure that the given path is either absolute or @@ -56,7 +57,7 @@ func EnsureAbsolute(path, file string) string { // Creates the required file structure in the users Home directory func CreateFolders() { - os.MkdirAll(GetReleasesFolder(), os.FileMode(0755)) + os.MkdirAll(ReleasesFolder(), os.FileMode(0755)) } // ValidateName ensures that the name for a resource is within certain boundaries @@ -77,7 +78,7 @@ func ValidateName(name string) (bool, error) { return true, nil } -// ReplaceNonURIChars replaces any characters in the resrouce name which +// ReplaceNonURIChars replaces any characters in the resource name which // can not be used in a URI func ReplaceNonURIChars(s string) (string, error) { reg, err := regexp.Compile(`[^a-zA-Z0-9\-\.]+`) @@ -235,19 +236,10 @@ func IsHCLFile(path string) bool { return true } -func sanitizeBlueprintFolder(blueprint string) string { - blueprint = strings.ReplaceAll(blueprint, "//", "/") - blueprint = strings.ReplaceAll(blueprint, "?", "/") - blueprint = strings.ReplaceAll(blueprint, "&", "/") - blueprint = strings.ReplaceAll(blueprint, "=", "/") - - return blueprint -} - -// GetBlueprintFolder parses a blueprint uri and returns the top level +// BlueprintFolder parses a blueprint uri and returns the top level // blueprint folder // if the URI is not a blueprint will return an error -func GetBlueprintFolder(blueprint string) (string, error) { +func BlueprintFolder(blueprint string) (string, error) { // get the folder for the blueprint parts := strings.Split(blueprint, "//") @@ -255,34 +247,34 @@ func GetBlueprintFolder(blueprint string) (string, error) { return "", InvalidBlueprintURIError } - return sanitizeBlueprintFolder(parts[1]), nil + return sanitize.Path(parts[1]), nil } -// GetBlueprintLocalFolder returns the full storage path +// BlueprintLocalFolder returns the full storage path // for the given blueprint URI -func GetBlueprintLocalFolder(blueprint string) string { +func BlueprintLocalFolder(blueprint string) string { // we might have a querystring reference such has github.com/abc/cds?ref=dfdf&dfdf // replace these separators with / - blueprint = sanitizeBlueprintFolder(blueprint) + blueprint = sanitize.Path(blueprint) return filepath.Join(JumppadHome(), "blueprints", blueprint) } -// GetHelmLocalFolder returns the full storage path +// HelmLocalFolder returns the full storage path // for the given blueprint URI -func GetHelmLocalFolder(chart string) string { - chart = sanitizeBlueprintFolder(chart) +func HelmLocalFolder(chart string) string { + chart = sanitize.Path(chart) return filepath.Join(JumppadHome(), "helm_charts", chart) } -// GetReleasesFolder return the path of the Shipyard releases -func GetReleasesFolder() string { +// ReleasesFolder return the path of the Shipyard releases +func ReleasesFolder() string { return filepath.Join(JumppadHome(), "releases") } -// GetDataFolder creates the data directory used by the application -func GetDataFolder(p string, perms os.FileMode) string { +// DataFolder creates the data directory used by the application +func DataFolder(p string, perms os.FileMode) string { data := filepath.Join(JumppadHome(), "data", p) // create the folder if it does not exist @@ -292,8 +284,21 @@ func GetDataFolder(p string, perms os.FileMode) string { return data } -// GetLibraryFolder creates the library directory used by the application -func GetLibraryFolder(p string, perms os.FileMode) string { +// CacheFolder creates the cache directory used by the a provider +// unlike DataFolders, cache folders are not removed when down is called +func CacheFolder(p string, perms os.FileMode) string { + data := filepath.Join(JumppadHome(), "cache", p) + + // create the folder if it does not exist + os.MkdirAll(data, perms) + os.Chmod(data, perms) + + return data +} + +// LibraryFolder creates the library directory used by the application +func LibraryFolder(p string, perms os.FileMode) string { + p = sanitize.Path(p) data := filepath.Join(JumppadHome(), "library", p) // create the folder if it does not exist @@ -342,64 +347,6 @@ func GetConnectorLogFile() string { return filepath.Join(LogsDir(), "connector.log") } -func compileShipyardBinary(path string) error { - maxLevels := 10 - currentLevel := 0 - - // we are running from a test so compile the binary - // and returns its path - dir, _ := os.Getwd() - - // walk backwards until we find the go.mod - for { - files, err := ioutil.ReadDir(dir) - if err != nil { - return err - } - - for _, f := range files { - if strings.HasSuffix(f.Name(), "go.mod") { - fp, _ := filepath.Abs(dir) - - // found the project root - file := filepath.Join(fp, "main.go") - tmpBinary := path - - // if windows append the exe extension - if runtime.GOOS == "windows" { - tmpBinary = tmpBinary + ".exe" - } - - os.RemoveAll(tmpBinary) - - outwriter := bytes.NewBufferString("") - cmd := exec.Command("go", "build", "-o", tmpBinary, file) - cmd.Stderr = outwriter - cmd.Stdout = outwriter - - err := cmd.Run() - if err != nil { - fmt.Println("Error building temporary binary:", cmd.Args) - fmt.Println(outwriter.String()) - panic(fmt.Errorf("unable to build connector binary: %s", err)) - } - - return nil - } - } - - // check the parent - dir = filepath.Join(dir, "../") - fmt.Println(dir) - currentLevel++ - if currentLevel > maxLevels { - panic("unable to find go.mod") - } - } -} - -var buildSync = sync.Once{} - // GetShipyardBinaryPath returns the path to the running Shipyard binary func GetShipyardBinaryPath() string { if strings.HasSuffix(os.Args[0], "jumppad") || strings.HasSuffix(os.Args[0], "jumppad-dev") || strings.HasSuffix(os.Args[0], "jumppad.exe") || strings.HasSuffix(os.Args[0], "jp") { @@ -561,3 +508,61 @@ func incIP(ip net.IP) net.IP { } return net.IP(byteIp) } + +func compileShipyardBinary(path string) error { + maxLevels := 10 + currentLevel := 0 + + // we are running from a test so compile the binary + // and returns its path + dir, _ := os.Getwd() + + // walk backwards until we find the go.mod + for { + files, err := ioutil.ReadDir(dir) + if err != nil { + return err + } + + for _, f := range files { + if strings.HasSuffix(f.Name(), "go.mod") { + fp, _ := filepath.Abs(dir) + + // found the project root + file := filepath.Join(fp, "main.go") + tmpBinary := path + + // if windows append the exe extension + if runtime.GOOS == "windows" { + tmpBinary = tmpBinary + ".exe" + } + + os.RemoveAll(tmpBinary) + + outwriter := bytes.NewBufferString("") + cmd := exec.Command("go", "build", "-o", tmpBinary, file) + cmd.Stderr = outwriter + cmd.Stdout = outwriter + + err := cmd.Run() + if err != nil { + fmt.Println("Error building temporary binary:", cmd.Args) + fmt.Println(outwriter.String()) + panic(fmt.Errorf("unable to build connector binary: %s", err)) + } + + return nil + } + } + + // check the parent + dir = filepath.Join(dir, "../") + fmt.Println(dir) + currentLevel++ + if currentLevel > maxLevels { + panic("unable to find go.mod") + } + } +} + +var buildSync = sync.Once{}