cloud: Refactor workspaceMapping concerns into strategy()

This commit is contained in:
Chris Arcand 2021-09-09 16:21:35 -05:00
parent 922a8e4488
commit 7a243379fb
2 changed files with 76 additions and 36 deletions

View File

@ -151,23 +151,22 @@ func (b *Cloud) PrepareConfig(obj cty.Value) (cty.Value, tfdiags.Diagnostics) {
diags = diags.Append(invalidOrganizationConfigMissingValue) diags = diags.Append(invalidOrganizationConfigMissingValue)
} }
var name, prefix string workspaceMapping := workspaceMapping{}
if workspaces := obj.GetAttr("workspaces"); !workspaces.IsNull() { if workspaces := obj.GetAttr("workspaces"); !workspaces.IsNull() {
if val := workspaces.GetAttr("name"); !val.IsNull() { if val := workspaces.GetAttr("name"); !val.IsNull() {
name = val.AsString() workspaceMapping.name = val.AsString()
} }
if val := workspaces.GetAttr("prefix"); !val.IsNull() { if val := workspaces.GetAttr("prefix"); !val.IsNull() {
prefix = val.AsString() workspaceMapping.prefix = val.AsString()
} }
} }
// Make sure that we have either a workspace name or a prefix. switch workspaceMapping.strategy() {
if name == "" && prefix == "" { // Make sure have a workspace mapping strategy present
case workspaceNoneStrategy:
diags = diags.Append(invalidWorkspaceConfigMissingValues) diags = diags.Append(invalidWorkspaceConfigMissingValues)
}
// Make sure that only one of workspace name or a prefix is configured. // Make sure that only one of workspace name or a prefix is configured.
if name != "" && prefix != "" { case workspaceInvalidStrategy:
diags = diags.Append(invalidWorkspaceConfigMisconfiguration) diags = diags.Append(invalidWorkspaceConfigMisconfiguration)
} }
@ -512,19 +511,20 @@ func (b *Cloud) retryLogHook(attemptNum int, resp *http.Response) {
// Workspaces implements backend.Enhanced. // Workspaces implements backend.Enhanced.
func (b *Cloud) Workspaces() ([]string, error) { func (b *Cloud) Workspaces() ([]string, error) {
if b.workspaceMapping.prefix == "" { if b.workspaceMapping.strategy() == workspaceNameStrategy {
return nil, backend.ErrWorkspacesNotSupported return nil, backend.ErrWorkspacesNotSupported
} }
return b.workspaces() return b.workspaces()
} }
// workspaces returns a filtered list of remote workspace names. // workspaces returns a filtered list of remote workspace names according to the workspace mapping
// strategy configured.
func (b *Cloud) workspaces() ([]string, error) { func (b *Cloud) workspaces() ([]string, error) {
options := tfe.WorkspaceListOptions{} options := tfe.WorkspaceListOptions{}
switch { switch b.workspaceMapping.strategy() {
case b.workspaceMapping.name != "": case workspaceNameStrategy:
options.Search = tfe.String(b.workspaceMapping.name) options.Search = tfe.String(b.workspaceMapping.name)
case b.workspaceMapping.prefix != "": case workspacePrefixStrategy:
options.Search = tfe.String(b.workspaceMapping.prefix) options.Search = tfe.String(b.workspaceMapping.prefix)
} }
@ -538,12 +538,22 @@ func (b *Cloud) workspaces() ([]string, error) {
} }
for _, w := range wl.Items { for _, w := range wl.Items {
if b.workspaceMapping.name != "" && w.Name == b.workspaceMapping.name { switch b.workspaceMapping.strategy() {
case workspaceNameStrategy:
if w.Name == b.workspaceMapping.name {
names = append(names, backend.DefaultStateName) names = append(names, backend.DefaultStateName)
continue continue
} }
if b.workspaceMapping.prefix != "" && strings.HasPrefix(w.Name, b.workspaceMapping.prefix) { case workspacePrefixStrategy:
if strings.HasPrefix(w.Name, b.workspaceMapping.prefix) {
names = append(names, strings.TrimPrefix(w.Name, b.workspaceMapping.prefix)) names = append(names, strings.TrimPrefix(w.Name, b.workspaceMapping.prefix))
continue
}
default:
// Pass-through. "name" and "prefix" strategies are naive and do
// client-side filtering above, but for any other future
// strategy this filtering should be left to the API.
names = append(names, w.Name)
} }
} }
@ -564,10 +574,10 @@ func (b *Cloud) workspaces() ([]string, error) {
// DeleteWorkspace implements backend.Enhanced. // DeleteWorkspace implements backend.Enhanced.
func (b *Cloud) DeleteWorkspace(name string) error { func (b *Cloud) DeleteWorkspace(name string) error {
if b.workspaceMapping.name == "" && name == backend.DefaultStateName { if b.workspaceMapping.strategy() != workspaceNameStrategy && name == backend.DefaultStateName {
return backend.ErrDefaultWorkspaceNotSupported return backend.ErrDefaultWorkspaceNotSupported
} }
if b.workspaceMapping.prefix == "" && name != backend.DefaultStateName { if b.workspaceMapping.strategy() == workspaceNameStrategy && name != backend.DefaultStateName {
return backend.ErrWorkspacesNotSupported return backend.ErrWorkspacesNotSupported
} }
@ -575,7 +585,7 @@ func (b *Cloud) DeleteWorkspace(name string) error {
switch { switch {
case name == backend.DefaultStateName: case name == backend.DefaultStateName:
name = b.workspaceMapping.name name = b.workspaceMapping.name
case b.workspaceMapping.prefix != "" && !strings.HasPrefix(name, b.workspaceMapping.prefix): case b.workspaceMapping.strategy() == workspacePrefixStrategy && !strings.HasPrefix(name, b.workspaceMapping.prefix):
name = b.workspaceMapping.prefix + name name = b.workspaceMapping.prefix + name
} }
@ -592,10 +602,10 @@ func (b *Cloud) DeleteWorkspace(name string) error {
// StateMgr implements backend.Enhanced. // StateMgr implements backend.Enhanced.
func (b *Cloud) StateMgr(name string) (statemgr.Full, error) { func (b *Cloud) StateMgr(name string) (statemgr.Full, error) {
if b.workspaceMapping.name == "" && name == backend.DefaultStateName { if b.workspaceMapping.strategy() != workspaceNameStrategy && name == backend.DefaultStateName {
return nil, backend.ErrDefaultWorkspaceNotSupported return nil, backend.ErrDefaultWorkspaceNotSupported
} }
if b.workspaceMapping.prefix == "" && name != backend.DefaultStateName { if b.workspaceMapping.strategy() == workspaceNameStrategy && name != backend.DefaultStateName {
return nil, backend.ErrWorkspacesNotSupported return nil, backend.ErrWorkspacesNotSupported
} }
@ -603,7 +613,7 @@ func (b *Cloud) StateMgr(name string) (statemgr.Full, error) {
switch { switch {
case name == backend.DefaultStateName: case name == backend.DefaultStateName:
name = b.workspaceMapping.name name = b.workspaceMapping.name
case b.workspaceMapping.prefix != "" && !strings.HasPrefix(name, b.workspaceMapping.prefix): case b.workspaceMapping.strategy() == workspacePrefixStrategy && !strings.HasPrefix(name, b.workspaceMapping.prefix):
name = b.workspaceMapping.prefix + name name = b.workspaceMapping.prefix + name
} }
@ -662,7 +672,7 @@ func (b *Cloud) Operation(ctx context.Context, op *backend.Operation) (*backend.
switch { switch {
case op.Workspace == backend.DefaultStateName: case op.Workspace == backend.DefaultStateName:
name = b.workspaceMapping.name name = b.workspaceMapping.name
case b.workspaceMapping.prefix != "" && !strings.HasPrefix(op.Workspace, b.workspaceMapping.prefix): case b.workspaceMapping.strategy() == workspacePrefixStrategy && !strings.HasPrefix(op.Workspace, b.workspaceMapping.prefix):
name = b.workspaceMapping.prefix + op.Workspace name = b.workspaceMapping.prefix + op.Workspace
} }
@ -977,6 +987,29 @@ type workspaceMapping struct {
prefix string prefix string
} }
type workspaceStrategy string
const (
workspaceNameStrategy workspaceStrategy = "name"
workspacePrefixStrategy workspaceStrategy = "prefix"
workspaceNoneStrategy workspaceStrategy = "none"
workspaceInvalidStrategy workspaceStrategy = "invalid"
)
func (wm workspaceMapping) strategy() workspaceStrategy {
switch {
case wm.name != "" && wm.prefix == "":
return workspaceNameStrategy
case wm.name == "" && wm.prefix != "":
return workspacePrefixStrategy
case wm.name == "" && wm.prefix == "":
return workspaceNoneStrategy
default:
// Any other combination is invalid as each strategy is mutually exclusive
return workspaceInvalidStrategy
}
}
func generalError(msg string, err error) error { func generalError(msg string, err error) error {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
@ -1042,10 +1075,17 @@ var schemaDescriptions = map[string]string{
"organization": "The name of the organization containing the targeted workspace(s).", "organization": "The name of the organization containing the targeted workspace(s).",
"token": "The token used to authenticate with Terraform Cloud/Enterprise. Typically this argument should not be set,\n" + "token": "The token used to authenticate with Terraform Cloud/Enterprise. Typically this argument should not be set,\n" +
"and 'terraform login' used instead; your credentials will then be fetched from your CLI configuration file or configured credential helper.", "and 'terraform login' used instead; your credentials will then be fetched from your CLI configuration file or configured credential helper.",
"name": "A workspace name used to map the default workspace to a named remote workspace.\n" + "name": "The name of a single Terraform Cloud workspace to be used with this configuration.\n" +
"When configured only the default workspace can be used. This option conflicts\n" + "When configured only the specified workspace can be used. This option conflicts\n" +
"with \"prefix\"", "with \"prefix\".",
"prefix": "A prefix used to filter workspaces using a single configuration. New workspaces\n" + "prefix": "A name prefix used to select remote Terraform Cloud workspaces to be used for this\n" +
"will automatically be prefixed with this prefix. If omitted only the default\n" + "single configuration. New workspaces will automatically be prefixed with this prefix. This option conflicts with \"name\".",
"workspace can be used. This option conflicts with \"name\"",
} }
var workspaceConfigurationHelp = fmt.Sprintf(`The 'workspaces' block configures how Terraform CLI maps its workspaces for this
single configuration to workspaces within a Terraform Cloud organization. Two strategies are available:
[bold]name[reset] - %s
[bold]prefix[reset] - %s
`, schemaDescriptions["name"], schemaDescriptions["prefix"])

View File

@ -322,11 +322,11 @@ func TestCloud_setConfigurationFields(t *testing.T) {
if tc.expectedOrganziation != "" && b.organization != tc.expectedOrganziation { if tc.expectedOrganziation != "" && b.organization != tc.expectedOrganziation {
t.Fatalf("%s: expected organization %s to match actual organization %s", name, tc.expectedOrganziation, b.organization) t.Fatalf("%s: expected organization %s to match actual organization %s", name, tc.expectedOrganziation, b.organization)
} }
if tc.expectedWorkspacePrefix != "" && b.prefix != tc.expectedWorkspacePrefix { if tc.expectedWorkspacePrefix != "" && b.workspaceMapping.prefix != tc.expectedWorkspacePrefix {
t.Fatalf("%s: expected workspace prefix %s to match actual workspace prefix %s", name, tc.expectedWorkspacePrefix, b.prefix) t.Fatalf("%s: expected workspace prefix %s to match actual workspace prefix %s", name, tc.expectedWorkspacePrefix, b.workspaceMapping.prefix)
} }
if tc.expectedWorkspaceName != "" && b.workspace != tc.expectedWorkspaceName { if tc.expectedWorkspaceName != "" && b.workspaceMapping.name != tc.expectedWorkspaceName {
t.Fatalf("%s: expected workspace name %s to match actual workspace name %s", name, tc.expectedWorkspaceName, b.workspace) t.Fatalf("%s: expected workspace name %s to match actual workspace name %s", name, tc.expectedWorkspaceName, b.workspaceMapping.name)
} }
if tc.expectedForceLocal != false && b.forceLocal != tc.expectedForceLocal { if tc.expectedForceLocal != false && b.forceLocal != tc.expectedForceLocal {
t.Fatalf("%s: expected force local backend to be set ", name) t.Fatalf("%s: expected force local backend to be set ", name)