mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Cloud Backend - Fix logic that forces TF_WORKSPACE to be equal to a tag name (#1930)
Signed-off-by: Ronny Orot <ronny.orot@gmail.com>
This commit is contained in:
parent
b29b55183f
commit
23d69e2351
@ -465,13 +465,15 @@ func (b *Cloud) setConfigurationFields(obj cty.Value) tfdiags.Diagnostics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func reconcileWorkspaceMappingEnvVars(w *WorkspaceMapping) tfdiags.Diagnostic {
|
func reconcileWorkspaceMappingEnvVars(w *WorkspaceMapping) tfdiags.Diagnostic {
|
||||||
// See: https://github.com/opentofu/opentofu/issues/814
|
if v := os.Getenv("TF_WORKSPACE"); v != "" {
|
||||||
if v := os.Getenv("TF_WORKSPACE"); v != "" && w.Name == "" {
|
if w.Name != "" && w.Name != v {
|
||||||
if len(w.Tags) > 0 && !workspaceInTags(w.Tags, v) {
|
return invalidWorkspaceConfigInconsistentNameAndEnvVar()
|
||||||
return invalidWorkspaceConfigMisconfigurationEnvVar
|
}
|
||||||
|
|
||||||
|
// If we don't have workspaces name or tags set in config, we can get the name from the TF_WORKSPACE env var
|
||||||
|
if w.Strategy() == WorkspaceNoneStrategy {
|
||||||
|
w.Name = v
|
||||||
}
|
}
|
||||||
w.Name = v
|
|
||||||
w.Tags = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv("TF_CLOUD_PROJECT"); v != "" && w.Project == "" {
|
if v := os.Getenv("TF_CLOUD_PROJECT"); v != "" && w.Project == "" {
|
||||||
@ -481,15 +483,6 @@ func reconcileWorkspaceMappingEnvVars(w *WorkspaceMapping) tfdiags.Diagnostic {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func workspaceInTags(tags []string, workspace string) bool {
|
|
||||||
for _, tag := range tags {
|
|
||||||
if tag == workspace {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// discover the TFC/E API service URL and version constraints.
|
// discover the TFC/E API service URL and version constraints.
|
||||||
func (b *Cloud) discover() (*url.URL, error) {
|
func (b *Cloud) discover() (*url.URL, error) {
|
||||||
hostname, err := svchost.ForComparison(b.hostname)
|
hostname, err := svchost.ForComparison(b.hostname)
|
||||||
|
@ -351,6 +351,7 @@ func TestCloud_config(t *testing.T) {
|
|||||||
config cty.Value
|
config cty.Value
|
||||||
confErr string
|
confErr string
|
||||||
valErr string
|
valErr string
|
||||||
|
envVars map[string]string
|
||||||
}{
|
}{
|
||||||
"with_a_non_tfe_host": {
|
"with_a_non_tfe_host": {
|
||||||
config: cty.ObjectVal(map[string]cty.Value{
|
config: cty.ObjectVal(map[string]cty.Value{
|
||||||
@ -440,10 +441,33 @@ func TestCloud_config(t *testing.T) {
|
|||||||
"null config": {
|
"null config": {
|
||||||
config: cty.NullVal(cty.EmptyObject),
|
config: cty.NullVal(cty.EmptyObject),
|
||||||
},
|
},
|
||||||
|
"with_tags_and_TF_WORKSPACE_env_var_not_matching_tags": { //TODO: once we have proper e2e backend testing we should also add the opposite test - with_tags_and_TF_WORKSPACE_env_var_matching_tags
|
||||||
|
config: cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"hostname": cty.NullVal(cty.String),
|
||||||
|
"organization": cty.StringVal("opentofu"),
|
||||||
|
"token": cty.NullVal(cty.String),
|
||||||
|
"workspaces": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"tags": cty.SetVal(
|
||||||
|
[]cty.Value{
|
||||||
|
cty.StringVal("billing"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
"project": cty.NullVal(cty.String),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
envVars: map[string]string{
|
||||||
|
"TF_WORKSPACE": "my-workspace",
|
||||||
|
},
|
||||||
|
confErr: `OpenTofu failed to find workspace my-workspace with the tags specified in your configuration`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, tc := range cases {
|
for name, tc := range cases {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
|
for k, v := range tc.envVars {
|
||||||
|
t.Setenv(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
b, cleanup := testUnconfiguredBackend(t)
|
b, cleanup := testUnconfiguredBackend(t)
|
||||||
t.Cleanup(cleanup)
|
t.Cleanup(cleanup)
|
||||||
|
|
||||||
@ -660,7 +684,7 @@ func TestCloud_setConfigurationFieldsHappyPath(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedForceLocal: true,
|
expectedForceLocal: true,
|
||||||
},
|
},
|
||||||
"with hostname and workspace tags set, and tags overwritten by TF_WORKSPACE": {
|
"with hostname and workspace tags set, then tags should not be overwritten by TF_WORKSPACE": {
|
||||||
// see: https://github.com/opentofu/opentofu/issues/814
|
// see: https://github.com/opentofu/opentofu/issues/814
|
||||||
obj: cty.ObjectVal(map[string]cty.Value{
|
obj: cty.ObjectVal(map[string]cty.Value{
|
||||||
"organization": cty.NullVal(cty.String),
|
"organization": cty.NullVal(cty.String),
|
||||||
@ -675,25 +699,24 @@ func TestCloud_setConfigurationFieldsHappyPath(t *testing.T) {
|
|||||||
"TF_WORKSPACE": "foo",
|
"TF_WORKSPACE": "foo",
|
||||||
},
|
},
|
||||||
expectedHostname: "opentofu.org",
|
expectedHostname: "opentofu.org",
|
||||||
expectedWorkspaceName: "foo",
|
expectedWorkspaceName: "",
|
||||||
expectedWorkspaceTags: nil,
|
expectedWorkspaceTags: map[string]struct{}{"foo": {}, "bar": {}},
|
||||||
},
|
},
|
||||||
"with hostname and workspace name set, and TF_WORKSPACE specified": {
|
"with hostname and workspace name set, and workspace name the same as provided TF_WORKSPACE": {
|
||||||
obj: cty.ObjectVal(map[string]cty.Value{
|
obj: cty.ObjectVal(map[string]cty.Value{
|
||||||
"organization": cty.NullVal(cty.String),
|
"organization": cty.NullVal(cty.String),
|
||||||
"hostname": cty.StringVal("opentofu.org"),
|
"hostname": cty.StringVal("opentofu.org"),
|
||||||
"workspaces": cty.ObjectVal(map[string]cty.Value{
|
"workspaces": cty.ObjectVal(map[string]cty.Value{
|
||||||
"name": cty.StringVal("old"),
|
"name": cty.StringVal("my-workspace"),
|
||||||
"tags": cty.NullVal(cty.Set(cty.String)),
|
"tags": cty.NullVal(cty.Set(cty.String)),
|
||||||
"project": cty.NullVal(cty.String),
|
"project": cty.NullVal(cty.String),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
envVars: map[string]string{
|
envVars: map[string]string{
|
||||||
"TF_WORKSPACE": "new",
|
"TF_WORKSPACE": "my-workspace",
|
||||||
},
|
},
|
||||||
expectedHostname: "opentofu.org",
|
expectedHostname: "opentofu.org",
|
||||||
expectedWorkspaceName: "old",
|
expectedWorkspaceName: "my-workspace",
|
||||||
expectedWorkspaceTags: nil,
|
|
||||||
},
|
},
|
||||||
"with hostname and project set, and project overwritten by TF_CLOUD_PROJECT": {
|
"with hostname and project set, and project overwritten by TF_CLOUD_PROJECT": {
|
||||||
obj: cty.ObjectVal(map[string]cty.Value{
|
obj: cty.ObjectVal(map[string]cty.Value{
|
||||||
@ -858,22 +881,21 @@ func TestCloud_setConfigurationFieldsUnhappyPath(t *testing.T) {
|
|||||||
wantSummary: "Hostname is required for the cloud backend",
|
wantSummary: "Hostname is required for the cloud backend",
|
||||||
wantDetail: `OpenTofu does not provide a default "hostname" attribute, so it must be set to the hostname of the cloud backend.`,
|
wantDetail: `OpenTofu does not provide a default "hostname" attribute, so it must be set to the hostname of the cloud backend.`,
|
||||||
},
|
},
|
||||||
"with hostname and workspace tags set, and tags overwritten by TF_WORKSPACE": {
|
"with hostname and workspace name set, and workspace name is not the same as provided TF_WORKSPACE": {
|
||||||
// see: https://github.com/opentofu/opentofu/issues/814
|
|
||||||
obj: cty.ObjectVal(map[string]cty.Value{
|
obj: cty.ObjectVal(map[string]cty.Value{
|
||||||
"organization": cty.NullVal(cty.String),
|
"organization": cty.NullVal(cty.String),
|
||||||
"hostname": cty.StringVal("opentofu.org"),
|
"hostname": cty.StringVal("opentofu.org"),
|
||||||
"workspaces": cty.ObjectVal(map[string]cty.Value{
|
"workspaces": cty.ObjectVal(map[string]cty.Value{
|
||||||
"name": cty.NullVal(cty.String),
|
"name": cty.StringVal("my-workspace"),
|
||||||
"tags": cty.SetVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}),
|
"tags": cty.NullVal(cty.Set(cty.String)),
|
||||||
"project": cty.NullVal(cty.String),
|
"project": cty.NullVal(cty.String),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
envVars: map[string]string{
|
envVars: map[string]string{
|
||||||
"TF_WORKSPACE": "qux",
|
"TF_WORKSPACE": "qux",
|
||||||
},
|
},
|
||||||
wantSummary: invalidWorkspaceConfigMisconfigurationEnvVar.Description().Summary,
|
wantSummary: invalidWorkspaceConfigInconsistentNameAndEnvVar().Description().Summary,
|
||||||
wantDetail: invalidWorkspaceConfigMisconfigurationEnvVar.Description().Detail,
|
wantDetail: invalidWorkspaceConfigInconsistentNameAndEnvVar().Description().Detail,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,13 +40,6 @@ var (
|
|||||||
fmt.Sprintf("Only one of workspace \"tags\" or \"name\" is allowed.\n\n%s", workspaceConfigurationHelp),
|
fmt.Sprintf("Only one of workspace \"tags\" or \"name\" is allowed.\n\n%s", workspaceConfigurationHelp),
|
||||||
cty.Path{cty.GetAttrStep{Name: "workspaces"}},
|
cty.Path{cty.GetAttrStep{Name: "workspaces"}},
|
||||||
)
|
)
|
||||||
|
|
||||||
invalidWorkspaceConfigMisconfigurationEnvVar = tfdiags.AttributeValue(
|
|
||||||
tfdiags.Error,
|
|
||||||
"Invalid workspaces configuration",
|
|
||||||
fmt.Sprintf("The workspace defined using the environment variable \"TF_WORKSPACE\" does not belong to \"tags\".\n\n%s", workspaceConfigurationHelp),
|
|
||||||
cty.Path{cty.GetAttrStep{Name: "workspaces"}},
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const ignoreRemoteVersionHelp = "If you're sure you want to upgrade the state, you can force OpenTofu to continue using the -ignore-remote-version flag. This may result in an unusable workspace."
|
const ignoreRemoteVersionHelp = "If you're sure you want to upgrade the state, you can force OpenTofu to continue using the -ignore-remote-version flag. This may result in an unusable workspace."
|
||||||
@ -70,3 +63,12 @@ func incompatibleWorkspaceTerraformVersion(message string, ignoreVersionConflict
|
|||||||
description := strings.TrimSpace(fmt.Sprintf("%s\n\n%s", message, suggestion))
|
description := strings.TrimSpace(fmt.Sprintf("%s\n\n%s", message, suggestion))
|
||||||
return tfdiags.Sourceless(severity, "Incompatible TF version", description)
|
return tfdiags.Sourceless(severity, "Incompatible TF version", description)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func invalidWorkspaceConfigInconsistentNameAndEnvVar() tfdiags.Diagnostic {
|
||||||
|
return tfdiags.AttributeValue(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Invalid workspaces configuration",
|
||||||
|
fmt.Sprintf("The workspace defined using the environment variable \"TF_WORKSPACE\" is not consistent with the workspace \"name\" in the configuration.\n\n%s", workspaceConfigurationHelp),
|
||||||
|
cty.Path{cty.GetAttrStep{Name: "workspaces"}},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -100,7 +100,7 @@ Use the following environment variables to configure the `cloud` block:
|
|||||||
|
|
||||||
- `TF_CLOUD_PROJECT` - The name of a cloud backend project. OpenTofu reads this when `workspaces.project` is omitted from the `cloud` block. If both are specified, the cloud block configuration takes precedence.
|
- `TF_CLOUD_PROJECT` - The name of a cloud backend project. OpenTofu reads this when `workspaces.project` is omitted from the `cloud` block. If both are specified, the cloud block configuration takes precedence.
|
||||||
|
|
||||||
- `TF_WORKSPACE` - The name of a single cloud backend workspace. OpenTofu reads this when `workspaces` is omitted from the `cloud` block. The cloud backend will not create a new workspace from this variable; the workspace must exist in the specified organization. You can set `TF_WORKSPACE` if the `cloud` block uses tags. However, the value of `TF_WORKSPACE` must be included in the set of tags. This variable also selects the workspace in your local environment. Refer to [TF_WORKSPACE](../config/environment-variables.mdx#tf_workspace) for details.
|
- `TF_WORKSPACE` - The name of a single cloud backend workspace. OpenTofu reads this when `workspaces` is omitted from the `cloud` block. The cloud backend will not create a new workspace from this variable; the workspace must exist in the specified organization. You can set `TF_WORKSPACE` if the `cloud` block uses tags. However, the selected `TF_WORKSPACE` must have a set of tags that match the tags in the `cloud` block. This variable also selects the workspace in your local environment. Refer to [TF_WORKSPACE](../config/environment-variables.mdx#tf_workspace) for details.
|
||||||
|
|
||||||
## Excluding Files from Upload with .terraformignore
|
## Excluding Files from Upload with .terraformignore
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user