diff --git a/README.md b/README.md index 404e03f..a8cde64 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,18 @@ variable "docker_ports" { image_id = "abc" ``` +- The `-r, --resource` flag outputs all variables as terraform resources for the `tfe_variable resource` found in the `tfe` provider + +- The `-w, --workspace` flag outputs all variables in the payload format for the API . You can use `jq` to filter variables by key name. + ``` + $ tfvar -w . | jq '. | select(.data.attributes.key == "region")' + { + "data": { + ... + } + } + ``` + For more info, checkout the `--help` page: ``` @@ -113,11 +125,13 @@ Flags: -e, --env-var Print output in export TF_VAR_image_id=ami-abc123 format -h, --help help for tfvar --ignore-default Do not use defined default values + -r, --resource Print output in hashicorp/tfe tfe_variable resource format --var stringArray Set a variable in the generated definitions. This flag can be set multiple times. --var-file stringArray Set variables from a file. This flag can be set multiple times. -v, --version version for tfvar + -w, --workspace Print output variables as payloads for workspace API ``` diff --git a/cmd/cmd.go b/cmd/cmd.go index b001873..da4fee8 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -16,8 +16,10 @@ const ( flagDebug = "debug" flagEnvVar = "env-var" flagNoDefault = "ignore-default" + flagResource = "resource" flagVar = "var" flagVarFile = "var-file" + flagWorkspace = "workspace" ) // New returns a new instance of cobra.Command for tfvar. Usage: @@ -49,6 +51,8 @@ one would write it in variable definitions files (.tfvars). variable definitions files e.g. terraform.tfvars[.json] *.auto.tfvars[.json]`) rootCmd.PersistentFlags().BoolP(flagDebug, "d", false, "Print debug log on stderr") rootCmd.PersistentFlags().BoolP(flagEnvVar, "e", false, "Print output in export TF_VAR_image_id=ami-abc123 format") + rootCmd.PersistentFlags().BoolP(flagResource, "r", false, "Print output in hashicorp/tfe tfe_variable resource format") + rootCmd.PersistentFlags().BoolP(flagWorkspace, "w", false, "Print output variables as payloads for workspace API") rootCmd.PersistentFlags().Bool(flagNoDefault, false, "Do not use defined default values") rootCmd.PersistentFlags().StringArray(flagVar, []string{}, `Set a variable in the generated definitions. This flag can be set multiple times.`) @@ -123,6 +127,16 @@ func (r *runner) rootRunE(cmd *cobra.Command, args []string) error { return errors.Wrap(err, "cmd: get flag --auto-assign") } + isWorkspace, err := cmd.PersistentFlags().GetBool(flagWorkspace) + if err != nil { + return errors.Wrap(err, "cmd: get flag --workspace") + } + + isResource, err := cmd.PersistentFlags().GetBool(flagResource) + if err != nil { + return errors.Wrap(err, "cmd: get flag --resource") + } + unparseds := make(map[string]tfvar.UnparsedVariableValue) if isAutoAssign { @@ -172,5 +186,14 @@ func (r *runner) rootRunE(cmd *cobra.Command, args []string) error { writer = tfvar.WriteAsEnvVars } + if isWorkspace { + r.log.Debug("Print outputs in Workspace API payload format") + writer = tfvar.WriteAsWorkspacePayload + } + + if isResource { + r.log.Debug("Print outputs in tfe_resource format") + writer = tfvar.WriteAsTFE_Resource + } return writer(r.out, vars) } diff --git a/pkg/tfvar/tfvar.go b/pkg/tfvar/tfvar.go index 1d7b631..4587afd 100644 --- a/pkg/tfvar/tfvar.go +++ b/pkg/tfvar/tfvar.go @@ -18,8 +18,10 @@ import ( // type = string // } type Variable struct { - Name string - Value cty.Value + Name string + Value cty.Value + Description string + Sensitive bool parsingMode configs.VariableParsingMode } @@ -37,8 +39,10 @@ func Load(dir string) ([]Variable, error) { for _, v := range modules.Variables { variables = append(variables, Variable{ - Name: v.Name, - Value: v.Default, + Name: v.Name, + Value: v.Default, + Description: v.Description, + Sensitive: v.Sensitive, parsingMode: v.ParsingMode, }) @@ -110,7 +114,58 @@ func WriteAsTFVars(w io.Writer, vars []Variable) error { _, err := f.WriteTo(w) return errors.Wrap(err, "tfvar: failed to write as tfvars") } +func WriteAsWorkspacePayload(w io.Writer, vars []Variable) error { + var data error + for _, v := range vars { + val := convertNull(v.Value) + t := hclwrite.TokensForValue(val) + t = oneliner(t) + b := hclwrite.Format(t.Bytes()) + b = bytes.TrimPrefix(b, []byte(`"`)) + b = bytes.TrimSuffix(b, []byte(`"`)) + b = bytes.ReplaceAll(b, []byte(`"`), []byte(`'`)) + data := fmt.Sprintf(`{ + "data": { + "type": "vars", + "attributes": { + "key": "%s", + "value": "%s", + "description": "%s", + "category": "%s", + "hcl": %s, + "sensitive": %v + } + } + } + `, v.Name, string(b), v.Description, "terraform", "false", v.Sensitive) + if _, err := fmt.Fprintf(w, "%s", data); err != nil { + return errors.Wrap(err, "tfvar: unexpected error writing payload") + } + + } + return data +} +func WriteAsTFE_Resource(w io.Writer, vars []Variable) error { + f := hclwrite.NewEmptyFile() + rootBody := f.Body() + + for _, v := range vars { + rootBody.AppendNewline() + resourceBlock := rootBody.AppendNewBlock("resource", []string{"tfe_variable", v.Name}) + resourceBody := resourceBlock.Body() + resourceBody.SetAttributeValue("key", cty.StringVal(v.Name)) + resourceBody.SetAttributeValue("value", v.Value) + resourceBody.SetAttributeValue("sensitive", cty.BoolVal(v.Sensitive)) + resourceBody.SetAttributeValue("description", cty.StringVal(v.Description)) + resourceBody.SetAttributeValue("workspace_id", cty.NilVal) + resourceBody.SetAttributeValue("category", cty.StringVal("terraform")) + + } + + _, err := f.WriteTo(w) + return errors.Wrap(err, "tfe_variable: failed to write as tfe_variable resource") +} func convertNull(v cty.Value) cty.Value { if v.IsNull() { return cty.StringVal("") diff --git a/pkg/tfvar/tfvar_test.go b/pkg/tfvar/tfvar_test.go index 47efca0..acc5134 100644 --- a/pkg/tfvar/tfvar_test.go +++ b/pkg/tfvar/tfvar_test.go @@ -98,3 +98,168 @@ region = null ` assert.Equal(t, expected, buf.String()) } +func TestWriteAsTFE_Resource(t *testing.T) { + vars, err := Load("testdata/defaults") + require.NoError(t, err) + + sort.Slice(vars, func(i, j int) bool { return vars[i].Name < vars[j].Name }) + + var buf bytes.Buffer + assert.NoError(t, WriteAsTFE_Resource(&buf, vars)) + + expected := ` +resource "tfe_variable" "availability_zone_names" { + key = "availability_zone_names" + value = ["us-west-1a"] + sensitive = false + description = "" + workspace_id = null + category = "terraform" +} + +resource "tfe_variable" "aws_amis" { + key = "aws_amis" + value = { + eu-west-1 = "ami-b1cf19c6" + us-east-1 = "ami-de7ab6b6" + us-west-1 = "ami-3f75767a" + us-west-2 = "ami-21f78e11" + } + sensitive = false + description = "" + workspace_id = null + category = "terraform" +} + +resource "tfe_variable" "docker_ports" { + key = "docker_ports" + value = [{ + external = 8300 + internal = 8301 + protocol = "tcp" + }] + sensitive = false + description = "" + workspace_id = null + category = "terraform" +} + +resource "tfe_variable" "instance_name" { + key = "instance_name" + value = "my-instance" + sensitive = false + description = "" + workspace_id = null + category = "terraform" +} + +resource "tfe_variable" "password" { + key = "password" + value = null + sensitive = true + description = "the root password to use with the database" + workspace_id = null + category = "terraform" +} + +resource "tfe_variable" "region" { + key = "region" + value = null + sensitive = false + description = "" + workspace_id = null + category = "terraform" +} +` + assert.Equal(t, expected, buf.String()) +} +func TestWriteAsWorkspacePayload(t *testing.T) { + vars, err := Load("testdata/defaults") + require.NoError(t, err) + + sort.Slice(vars, func(i, j int) bool { return vars[i].Name < vars[j].Name }) + + var buf bytes.Buffer + assert.NoError(t, WriteAsWorkspacePayload(&buf, vars)) + + expected := `{ + "data": { + "type": "vars", + "attributes": { + "key": "availability_zone_names", + "value": "['us-west-1a']", + "description": "", + "category": "terraform", + "hcl": false, + "sensitive": false + } + } + } + { + "data": { + "type": "vars", + "attributes": { + "key": "aws_amis", + "value": "{ eu-west-1 = 'ami-b1cf19c6', us-east-1 = 'ami-de7ab6b6', us-west-1 = 'ami-3f75767a', us-west-2 = 'ami-21f78e11' }", + "description": "", + "category": "terraform", + "hcl": false, + "sensitive": false + } + } + } + { + "data": { + "type": "vars", + "attributes": { + "key": "docker_ports", + "value": "[{ external = 8300, internal = 8301, protocol = 'tcp' }]", + "description": "", + "category": "terraform", + "hcl": false, + "sensitive": false + } + } + } + { + "data": { + "type": "vars", + "attributes": { + "key": "instance_name", + "value": "my-instance", + "description": "", + "category": "terraform", + "hcl": false, + "sensitive": false + } + } + } + { + "data": { + "type": "vars", + "attributes": { + "key": "password", + "value": "", + "description": "the root password to use with the database", + "category": "terraform", + "hcl": false, + "sensitive": true + } + } + } + { + "data": { + "type": "vars", + "attributes": { + "key": "region", + "value": "", + "description": "", + "category": "terraform", + "hcl": false, + "sensitive": false + } + } + } + ` + assert.Equal(t, expected, buf.String()) +}