Merge pull request #19555 from hashicorp/svh/f-entitlements
backend/remote: use entitlements to select backends
This commit is contained in:
commit
70689f5aa1
|
@ -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,31 +184,40 @@ 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
|
||||
}
|
||||
}
|
||||
}`)
|
||||
})
|
||||
|
|
2
go.mod
2
go.mod
|
@ -68,7 +68,7 @@ require (
|
|||
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90
|
||||
github.com/hashicorp/go-safetemp v0.0.0-20180326211150-b1a1dbde6fdc // indirect
|
||||
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 // indirect
|
||||
github.com/hashicorp/go-tfe v0.3.3
|
||||
github.com/hashicorp/go-tfe v0.3.4
|
||||
github.com/hashicorp/go-uuid v1.0.0
|
||||
github.com/hashicorp/go-version v0.0.0-20180322230233-23480c066577
|
||||
github.com/hashicorp/golang-lru v0.5.0 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -153,8 +153,8 @@ github.com/hashicorp/go-slug v0.2.0 h1:gekvezBc+9LwN3qC+lesrz0Qg36hhgge9z/an1FCH
|
|||
github.com/hashicorp/go-slug v0.2.0/go.mod h1:+zDycQOzGqOqMW7Kn2fp9vz/NtqpMLQlgb9JUF+0km4=
|
||||
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 h1:7YOlAIO2YWnJZkQp7B5eFykaIY7C9JndqAFQyVV5BhM=
|
||||
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-tfe v0.3.3 h1:v17u0VdSy54n6Xn575cTzLrNJ0gn+Y7mq5J+A/p1fkw=
|
||||
github.com/hashicorp/go-tfe v0.3.3/go.mod h1:Vssg8/lwVz+PyJ/nAK97zYmXxxLe28MCIMhKo+rva1o=
|
||||
github.com/hashicorp/go-tfe v0.3.4 h1:A9pKjZMDTSGozXf2wQlWhBI7QoxCoas14Xg/TSiEAV8=
|
||||
github.com/hashicorp/go-tfe v0.3.4/go.mod h1:Vssg8/lwVz+PyJ/nAK97zYmXxxLe28MCIMhKo+rva1o=
|
||||
github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v0.0.0-20180322230233-23480c066577 h1:at4+18LrM8myamuV7/vT6x2s1JNXp2k4PsSbt4I02X4=
|
||||
|
|
|
@ -35,6 +35,9 @@ type Organizations interface {
|
|||
// Capacity shows the current run capacity of an organization.
|
||||
Capacity(ctx context.Context, organization string) (*Capacity, error)
|
||||
|
||||
// Entitlements shows the entitlements of an organization.
|
||||
Entitlements(ctx context.Context, organization string) (*Entitlements, error)
|
||||
|
||||
// RunQueue shows the current run queue of an organization.
|
||||
RunQueue(ctx context.Context, organization string, options RunQueueOptions) (*RunQueue, error)
|
||||
}
|
||||
|
@ -93,6 +96,17 @@ type Capacity struct {
|
|||
Running int `jsonapi:"attr,running"`
|
||||
}
|
||||
|
||||
// Entitlements represents the entitlements of an organization.
|
||||
type Entitlements struct {
|
||||
ID string `jsonapi:"primary,entitlement-sets"`
|
||||
StateStorage bool `jsonapi:"attr,state-storage"`
|
||||
Operations bool `jsonapi:"attr,operations"`
|
||||
VCSIntegrations bool `jsonapi:"attr,vcs-integrations"`
|
||||
Sentinel bool `jsonapi:"attr,sentinel"`
|
||||
PrivateModuleRegistry bool `jsonapi:"attr,private-module-registry"`
|
||||
Teams bool `jsonapi:"attr,teams"`
|
||||
}
|
||||
|
||||
// RunQueue represents the current run queue of an organization.
|
||||
type RunQueue struct {
|
||||
*Pagination
|
||||
|
@ -283,6 +297,27 @@ func (s *organizations) Capacity(ctx context.Context, organization string) (*Cap
|
|||
return c, nil
|
||||
}
|
||||
|
||||
// Entitlements shows the entitlements of an organization.
|
||||
func (s *organizations) Entitlements(ctx context.Context, organization string) (*Entitlements, error) {
|
||||
if !validStringID(&organization) {
|
||||
return nil, errors.New("invalid value for organization")
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("organizations/%s/entitlement-set", url.QueryEscape(organization))
|
||||
req, err := s.client.newRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e := &Entitlements{}
|
||||
err = s.client.do(ctx, req, e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// RunQueueOptions represents the options for showing the queue.
|
||||
type RunQueueOptions struct {
|
||||
ListOptions
|
||||
|
|
|
@ -327,7 +327,7 @@ github.com/hashicorp/go-rootcerts
|
|||
github.com/hashicorp/go-safetemp
|
||||
# github.com/hashicorp/go-slug v0.2.0
|
||||
github.com/hashicorp/go-slug
|
||||
# github.com/hashicorp/go-tfe v0.3.3
|
||||
# github.com/hashicorp/go-tfe v0.3.4
|
||||
github.com/hashicorp/go-tfe
|
||||
# github.com/hashicorp/go-uuid v1.0.0
|
||||
github.com/hashicorp/go-uuid
|
||||
|
|
Loading…
Reference in New Issue