From 198c776b942281fa12ae78f262eaa9b3f73efab0 Mon Sep 17 00:00:00 2001 From: Sergiu Ghitea <28300158+sergiught@users.noreply.github.com> Date: Tue, 8 Aug 2023 13:44:18 +0200 Subject: [PATCH] DXCDT-498: Add terraform generate command skeleton (#792) --- docs/auth0_terraform.md | 13 ++++ docs/auth0_terraform_generate.md | 47 +++++++++++++ docs/index.md | 1 + internal/cli/root.go | 1 + internal/cli/terraform.go | 113 +++++++++++++++++++++++++++++++ internal/cli/terraform_test.go | 85 +++++++++++++++++++++++ 6 files changed, 260 insertions(+) create mode 100644 docs/auth0_terraform.md create mode 100644 docs/auth0_terraform_generate.md create mode 100644 internal/cli/terraform.go create mode 100644 internal/cli/terraform_test.go diff --git a/docs/auth0_terraform.md b/docs/auth0_terraform.md new file mode 100644 index 000000000..0d8043ffb --- /dev/null +++ b/docs/auth0_terraform.md @@ -0,0 +1,13 @@ +--- +layout: default +has_toc: false +has_children: true +--- +# auth0 terraform + +This command facilitates the integration of Auth0 with [Terraform](https://www.terraform.io/), an Infrastructure as Code tool. + +## Commands + +- [auth0 terraform generate](auth0_terraform_generate.md) - Generate terraform configuration for your Auth0 Tenant + diff --git a/docs/auth0_terraform_generate.md b/docs/auth0_terraform_generate.md new file mode 100644 index 000000000..548c18609 --- /dev/null +++ b/docs/auth0_terraform_generate.md @@ -0,0 +1,47 @@ +--- +layout: default +parent: auth0 terraform +has_toc: false +--- +# auth0 terraform generate + +This command is designed to streamline the process of generating Terraform configuration files for your Auth0 resources, serving as a bridge between the two. + +It automatically scans your Auth0 Tenant and compiles a set of Terraform configuration files based on the existing resources and configurations. + +The generated Terraform files are written in HashiCorp Configuration Language (HCL). + +## Usage +``` +auth0 terraform generate [flags] +``` + +## Examples + +``` + +``` + + +## Flags + +``` + -o, --output-dir string Output directory for the generated Terraform config files. If not provided, the files will be saved in the current working directory. (default "./") +``` + + +## Inherited Flags + +``` + --debug Enable debug mode. + --no-color Disable colors. + --no-input Disable interactivity. + --tenant string Specific tenant to use. +``` + + +## Related Commands + +- [auth0 terraform generate](auth0_terraform_generate.md) - Generate terraform configuration for your Auth0 Tenant + + diff --git a/docs/index.md b/docs/index.md index 13cff24d5..df3f736d3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -80,6 +80,7 @@ Authenticating as a user is not supported for **private cloud** tenants. Instead - [auth0 roles](auth0_roles.md) - Manage resources for roles - [auth0 rules](auth0_rules.md) - Manage resources for rules - [auth0 tenants](auth0_tenants.md) - Manage configured tenants +- [auth0 terraform](auth0_terraform.md) - Manage terraform configuration for your Auth0 Tenant - [auth0 test](auth0_test.md) - Try your Universal Login box or get a token - [auth0 universal-login](auth0_universal-login.md) - Manage the Universal Login experience - [auth0 users](auth0_users.md) - Manage resources for users diff --git a/internal/cli/root.go b/internal/cli/root.go index 19f7f7f0a..e4d39d2ff 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -168,6 +168,7 @@ func addSubCommands(rootCmd *cobra.Command, cli *cli) { rootCmd.AddCommand(testCmd(cli)) rootCmd.AddCommand(logsCmd(cli)) rootCmd.AddCommand(apiCmd(cli)) + rootCmd.AddCommand(terraformCmd(cli)) // keep completion at the bottom: rootCmd.AddCommand(completionCmd(cli)) diff --git a/internal/cli/terraform.go b/internal/cli/terraform.go new file mode 100644 index 000000000..d11d50b65 --- /dev/null +++ b/internal/cli/terraform.go @@ -0,0 +1,113 @@ +package cli + +import ( + "os" + "path" + + "github.com/spf13/cobra" +) + +var tfFlags = terraformFlags{ + OutputDIR: Flag{ + Name: "Output Dir", + LongForm: "output-dir", + ShortForm: "o", + Help: "Output directory for the generated Terraform config files. If not provided, the files will be " + + "saved in the current working directory.", + }, +} + +type ( + terraformFlags struct { + OutputDIR Flag + } + + terraformInputs struct { + OutputDIR string + } +) + +func terraformCmd(cli *cli) *cobra.Command { + cmd := &cobra.Command{ + Use: "terraform", + Aliases: []string{"tf"}, + Short: "Manage terraform configuration for your Auth0 Tenant", + Long: "This command facilitates the integration of Auth0 with [Terraform](https://www.terraform.io/), an " + + "Infrastructure as Code tool.", + } + + cmd.SetUsageTemplate(resourceUsageTemplate()) + cmd.AddCommand(generateTerraformCmd(cli)) + + return cmd +} + +func generateTerraformCmd(cli *cli) *cobra.Command { + var inputs terraformInputs + + cmd := &cobra.Command{ + Use: "generate", + Aliases: []string{"gen", "export"}, // Reconsider aliases and command name before releasing. + Short: "Generate terraform configuration for your Auth0 Tenant", + Long: "This command is designed to streamline the process of generating Terraform configuration files for " + + "your Auth0 resources, serving as a bridge between the two.\n\nIt automatically scans your Auth0 Tenant " + + "and compiles a set of Terraform configuration files based on the existing resources and configurations." + + "\n\nThe generated Terraform files are written in HashiCorp Configuration Language (HCL).", + RunE: generateTerraformCmdRun(cli, &inputs), + } + + tfFlags.OutputDIR.RegisterString(cmd, &inputs.OutputDIR, "./") + + return cmd +} + +func generateTerraformCmdRun(cli *cli, inputs *terraformInputs) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + if err := generateTerraformConfigFiles(inputs); err != nil { + return err + } + + cli.renderer.Infof("Terraform config files generated successfully.") + cli.renderer.Infof( + "Follow this " + + "[quickstart](https://registry.terraform.io/providers/auth0/auth0/latest/docs/guides/quickstart) " + + "to go through setting up an Auth0 application for the provider to authenticate against and manage " + + "resources.", + ) + + return nil + } +} + +func generateTerraformConfigFiles(inputs *terraformInputs) error { + const readWritePermission = 0755 + if err := os.MkdirAll(inputs.OutputDIR, readWritePermission); err != nil { + if !os.IsExist(err) { + return err + } + } + + mainTerraformConfigFile, err := os.Create(path.Join(inputs.OutputDIR, "main.tf")) + if err != nil { + return err + } + defer mainTerraformConfigFile.Close() + + mainTerraformConfigFileContent := `terraform { + required_version = "~> 1.5.0" + required_providers { + auth0 = { + source = "auth0/auth0" + version = "1.0.0-beta.1" + } + } +} + +provider "auth0" { + debug = true +} +` + + _, err = mainTerraformConfigFile.WriteString(mainTerraformConfigFileContent) + return err +} diff --git a/internal/cli/terraform_test.go b/internal/cli/terraform_test.go new file mode 100644 index 000000000..4f16420b5 --- /dev/null +++ b/internal/cli/terraform_test.go @@ -0,0 +1,85 @@ +package cli + +import ( + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenerateTerraformConfigFiles(t *testing.T) { + testInputs := terraformInputs{ + OutputDIR: "./terraform/dev", + } + defer os.RemoveAll("./terraform") + + t.Run("it can correctly generate the terraform main config file", func(t *testing.T) { + assertTerraformConfigFilesWereGeneratedWithCorrectContent(t, &testInputs) + }) + + t.Run("it can correctly generate the terraform main config file even if the dir exists", func(t *testing.T) { + err := os.MkdirAll(testInputs.OutputDIR, 0755) + require.NoError(t, err) + + assertTerraformConfigFilesWereGeneratedWithCorrectContent(t, &testInputs) + }) + + t.Run("it fails to create the directory if path is empty", func(t *testing.T) { + testInputs := terraformInputs{ + OutputDIR: "", + } + + err := generateTerraformConfigFiles(&testInputs) + assert.EqualError(t, err, "mkdir : no such file or directory") + }) + + t.Run("it fails to create the main.tf file if file is already created and read only", func(t *testing.T) { + err := os.MkdirAll(testInputs.OutputDIR, 0755) + require.NoError(t, err) + + mainFilePath := path.Join(testInputs.OutputDIR, "main.tf") + _, err = os.Create(mainFilePath) + require.NoError(t, err) + + err = os.Chmod(mainFilePath, 0444) + require.NoError(t, err) + + err = generateTerraformConfigFiles(&testInputs) + assert.EqualError(t, err, "open terraform/dev/main.tf: permission denied") + }) +} + +func assertTerraformConfigFilesWereGeneratedWithCorrectContent(t *testing.T, testInputs *terraformInputs) { + err := generateTerraformConfigFiles(testInputs) + require.NoError(t, err) + + // Assert that the directory was created. + _, err = os.Stat(testInputs.OutputDIR) + assert.NoError(t, err) + + // Assert that the main.tf file was created with the correct content. + mainTerraformConfigFilePath := path.Join(testInputs.OutputDIR, "main.tf") + _, err = os.Stat(mainTerraformConfigFilePath) + assert.NoError(t, err) + + expectedContent := `terraform { + required_version = "~> 1.5.0" + required_providers { + auth0 = { + source = "auth0/auth0" + version = "1.0.0-beta.1" + } + } +} + +provider "auth0" { + debug = true +} +` + // Read the file content and check if it matches the expected content + content, err := os.ReadFile(mainTerraformConfigFilePath) + assert.NoError(t, err) + assert.Equal(t, expectedContent, string(content)) +}