Add TF_ORGANIZATION env var support

TF_ORGANIZATION will serve as a fallback for configuring the organization in the `cloud`
block. This is the first step to make it easier for users wanting to configure Terraform
programmatically.
This commit is contained in:
Sebastian Rivera 2022-03-22 17:56:45 -04:00
parent 7e849473a4
commit 45357f5004
2 changed files with 128 additions and 4 deletions

View File

@ -113,7 +113,7 @@ func (b *Cloud) ConfigSchema() *configschema.Block {
},
"organization": {
Type: cty.String,
Required: true,
Optional: true,
Description: schemaDescriptionOrganization,
},
"token": {
@ -152,8 +152,13 @@ func (b *Cloud) PrepareConfig(obj cty.Value) (cty.Value, tfdiags.Diagnostics) {
return obj, diags
}
// check if organization is specified in the config.
if val := obj.GetAttr("organization"); val.IsNull() || val.AsString() == "" {
diags = diags.Append(invalidOrganizationConfigMissingValue)
// organization is specified in the config but is invalid, so
// we'll fallback on TF_ORGANIZATION
if val := os.Getenv("TF_ORGANIZATION"); val == "" {
diags = diags.Append(invalidOrganizationConfigMissingValue)
}
}
WorkspaceMapping := WorkspaceMapping{}
@ -342,8 +347,14 @@ func (b *Cloud) setConfigurationFields(obj cty.Value) tfdiags.Diagnostics {
b.hostname = defaultHostname
}
// Get the organization.
if val := obj.GetAttr("organization"); !val.IsNull() {
// We can have two options, setting the organization via the config
// or using TF_ORGANIZATION. Since PrepareConfig() validates that one of these
// values must exist, we'll initially set it to the env var and override it if
// specified in the configuration.
b.organization = os.Getenv("TF_ORGANIZATION")
// Check if the organization is present and valid in the config.
if val := obj.GetAttr("organization"); !val.IsNull() && val.AsString() != "" {
b.organization = val.AsString()
}

View File

@ -146,6 +146,119 @@ func TestCloud_PrepareConfig(t *testing.T) {
}
}
func TestCloud_PrepareConfigWithEnvVars(t *testing.T) {
cases := map[string]struct {
config cty.Value
vars map[string]string
expectedErr string
}{
"with no organization": {
config: cty.ObjectVal(map[string]cty.Value{
"organization": cty.NullVal(cty.String),
"workspaces": cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("prod"),
"tags": cty.NullVal(cty.Set(cty.String)),
}),
}),
vars: map[string]string{
"TF_ORGANIZATION": "example-org",
},
},
"with no organization attribute or env var": {
config: cty.ObjectVal(map[string]cty.Value{
"organization": cty.NullVal(cty.String),
"workspaces": cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("prod"),
"tags": cty.NullVal(cty.Set(cty.String)),
}),
}),
vars: map[string]string{},
expectedErr: `Invalid organization value: The "organization" attribute value must not be empty.`,
},
}
for name, tc := range cases {
s := testServer(t)
b := New(testDisco(s))
for k, v := range tc.vars {
os.Setenv(k, v)
defer os.Unsetenv(k)
}
_, valDiags := b.PrepareConfig(tc.config)
if valDiags.Err() != nil && tc.expectedErr != "" {
actualErr := valDiags.Err().Error()
if !strings.Contains(actualErr, tc.expectedErr) {
t.Fatalf("%s: unexpected validation result: %v", name, valDiags.Err())
}
}
}
}
func TestCloud_configWithEnvVars(t *testing.T) {
cases := map[string]struct {
config cty.Value
vars map[string]string
expectedOrganization string
}{
"with no organization specified": {
config: cty.ObjectVal(map[string]cty.Value{
"hostname": cty.NullVal(cty.String),
"token": cty.NullVal(cty.String),
"organization": cty.NullVal(cty.String),
"workspaces": cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("prod"),
"tags": cty.NullVal(cty.Set(cty.String)),
}),
}),
vars: map[string]string{
"TF_ORGANIZATION": "hashicorp",
},
expectedOrganization: "hashicorp",
},
"with both organization and env var specified": {
config: cty.ObjectVal(map[string]cty.Value{
"hostname": cty.NullVal(cty.String),
"token": cty.NullVal(cty.String),
"organization": cty.StringVal("hashicorp"),
"workspaces": cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("prod"),
"tags": cty.NullVal(cty.Set(cty.String)),
}),
}),
vars: map[string]string{
"TF_ORGANIZATION": "we-should-not-see-this",
},
expectedOrganization: "hashicorp",
},
}
for name, tc := range cases {
s := testServer(t)
b := New(testDisco(s))
for k, v := range tc.vars {
os.Setenv(k, v)
defer os.Unsetenv(k)
}
_, valDiags := b.PrepareConfig(tc.config)
if valDiags.Err() != nil {
t.Fatalf("%s: unexpected validation result: %v", name, valDiags.Err())
}
diags := b.Configure(tc.config)
if diags.Err() != nil {
t.Fatalf("%s: unexpected configuration result: %v", name, valDiags.Err())
}
if tc.expectedOrganization != "" && tc.expectedOrganization != b.organization {
t.Fatalf("%s: organization not valid: %s, expected: %s", name, b.organization, tc.expectedOrganization)
}
}
}
func TestCloud_config(t *testing.T) {
cases := map[string]struct {
config cty.Value