backend/remote: use entitlements to select backends
Use the entitlements to a) determine if the organization exists, and b) as a means to select which backend to use (the local backend with remote state, or the remote backend).
This commit is contained in:
parent
03ac6ec774
commit
9062d887b8
|
@ -271,15 +271,15 @@ func (b *Remote) Configure(obj cty.Value) tfdiags.Diagnostics {
|
|||
return diags
|
||||
}
|
||||
|
||||
// Check if the organization exists.
|
||||
_, err = b.client.Organizations.Read(context.Background(), b.organization)
|
||||
// Check if the organization exists by reading its entitlements.
|
||||
entitlements, err := b.client.Organizations.Entitlements(context.Background(), b.organization)
|
||||
if err != nil {
|
||||
if err == tfe.ErrResourceNotFound {
|
||||
err = fmt.Errorf("organization %s does not exist", b.organization)
|
||||
}
|
||||
diags = diags.Append(tfdiags.AttributeValue(
|
||||
tfdiags.Error,
|
||||
"Failed to read organization settings",
|
||||
"Failed to read organization entitlements",
|
||||
fmt.Sprintf(
|
||||
`The "remote" backend encountered an unexpected error while reading the `+
|
||||
`organization settings: %s.`, err,
|
||||
|
@ -291,7 +291,7 @@ func (b *Remote) Configure(obj cty.Value) tfdiags.Diagnostics {
|
|||
|
||||
// Configure a local backend for when we need to run operations locally.
|
||||
b.local = backendLocal.NewWithBackend(b)
|
||||
b.forceLocal = os.Getenv("TF_FORCE_LOCAL_BACKEND") != ""
|
||||
b.forceLocal = !entitlements.Operations || os.Getenv("TF_FORCE_LOCAL_BACKEND") != ""
|
||||
|
||||
return diags
|
||||
}
|
||||
|
|
|
@ -322,6 +322,17 @@ func (m *mockOrganizations) Capacity(ctx context.Context, name string) (*tfe.Cap
|
|||
return &tfe.Capacity{Pending: pending, Running: running}, nil
|
||||
}
|
||||
|
||||
func (m *mockOrganizations) Entitlements(ctx context.Context, name string) (*tfe.Entitlements, error) {
|
||||
return &tfe.Entitlements{
|
||||
Operations: true,
|
||||
PrivateModuleRegistry: true,
|
||||
Sentinel: true,
|
||||
StateStorage: true,
|
||||
Teams: true,
|
||||
VCSIntegrations: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *mockOrganizations) RunQueue(ctx context.Context, name string, options tfe.RunQueueOptions) (*tfe.RunQueue, error) {
|
||||
rq := &tfe.RunQueue{}
|
||||
|
||||
|
|
|
@ -354,6 +354,36 @@ func TestRemote_planForceLocal(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRemote_planWithoutOperationsEntitlement(t *testing.T) {
|
||||
b := testBackendNoOperations(t)
|
||||
|
||||
op, configCleanup := testOperationPlan(t, "./test-fixtures/plan")
|
||||
defer configCleanup()
|
||||
|
||||
op.Workspace = backend.DefaultStateName
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
t.Fatalf("error starting operation: %v", err)
|
||||
}
|
||||
|
||||
<-run.Done()
|
||||
if run.Result != backend.OperationSuccess {
|
||||
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
||||
}
|
||||
if run.PlanEmpty {
|
||||
t.Fatalf("expected a non-empty plan")
|
||||
}
|
||||
|
||||
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
||||
if strings.Contains(output, "Running plan in the remote backend") {
|
||||
t.Fatalf("unexpected remote backend header in output: %s", output)
|
||||
}
|
||||
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
||||
t.Fatalf("expected plan summery in output: %s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemote_planWorkspaceWithoutOperations(t *testing.T) {
|
||||
b := testBackendNoDefault(t)
|
||||
ctx := context.Background()
|
||||
|
|
|
@ -66,6 +66,19 @@ func testBackendNoDefault(t *testing.T) *Remote {
|
|||
return testBackend(t, obj)
|
||||
}
|
||||
|
||||
func testBackendNoOperations(t *testing.T) *Remote {
|
||||
obj := cty.ObjectVal(map[string]cty.Value{
|
||||
"hostname": cty.NullVal(cty.String),
|
||||
"organization": cty.StringVal("no-operations"),
|
||||
"token": cty.NullVal(cty.String),
|
||||
"workspaces": cty.ObjectVal(map[string]cty.Value{
|
||||
"name": cty.StringVal("prod"),
|
||||
"prefix": cty.NullVal(cty.String),
|
||||
}),
|
||||
})
|
||||
return testBackend(t, obj)
|
||||
}
|
||||
|
||||
func testRemoteClient(t *testing.T) remote.Client {
|
||||
b := testBackendDefault(t)
|
||||
raw, err := b.StateMgr(backend.DefaultStateName)
|
||||
|
@ -171,30 +184,39 @@ func testServer(t *testing.T) *httptest.Server {
|
|||
io.WriteString(w, `{"tfe.v2":"/api/v2/"}`)
|
||||
})
|
||||
|
||||
// Respond to the initial query to read the organization settings.
|
||||
mux.HandleFunc("/api/v2/organizations/hashicorp", func(w http.ResponseWriter, r *http.Request) {
|
||||
// Respond to the initial query to read the hashicorp org entitlements.
|
||||
mux.HandleFunc("/api/v2/organizations/hashicorp/entitlement-set", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/vnd.api+json")
|
||||
io.WriteString(w, `{
|
||||
"data": {
|
||||
"id": "hashicorp",
|
||||
"type": "organizations",
|
||||
"id": "org-GExadygjSbKP8hsY",
|
||||
"type": "entitlement-sets",
|
||||
"attributes": {
|
||||
"name": "hashicorp",
|
||||
"created-at": "2017-09-07T14:34:40.492Z",
|
||||
"email": "user@example.com",
|
||||
"collaborator-auth-policy": "password",
|
||||
"enterprise-plan": "premium",
|
||||
"permissions": {
|
||||
"can-update": true,
|
||||
"can-destroy": true,
|
||||
"can-create-team": true,
|
||||
"can-create-workspace": true,
|
||||
"can-update-oauth": true,
|
||||
"can-update-api-token": true,
|
||||
"can-update-sentinel": true,
|
||||
"can-traverse": true,
|
||||
"can-create-workspace-migration": true
|
||||
}
|
||||
"operations": true,
|
||||
"private-module-registry": true,
|
||||
"sentinel": true,
|
||||
"state-storage": true,
|
||||
"teams": true,
|
||||
"vcs-integrations": true
|
||||
}
|
||||
}
|
||||
}`)
|
||||
})
|
||||
|
||||
// Respond to the initial query to read the no-operations org entitlements.
|
||||
mux.HandleFunc("/api/v2/organizations/no-operations/entitlement-set", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/vnd.api+json")
|
||||
io.WriteString(w, `{
|
||||
"data": {
|
||||
"id": "org-ufxa3y8jSbKP8hsT",
|
||||
"type": "entitlement-sets",
|
||||
"attributes": {
|
||||
"operations": false,
|
||||
"private-module-registry": true,
|
||||
"sentinel": true,
|
||||
"state-storage": true,
|
||||
"teams": true,
|
||||
"vcs-integrations": true
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
|
Loading…
Reference in New Issue