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
|
return diags
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the organization exists.
|
// Check if the organization exists by reading its entitlements.
|
||||||
_, err = b.client.Organizations.Read(context.Background(), b.organization)
|
entitlements, err := b.client.Organizations.Entitlements(context.Background(), b.organization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == tfe.ErrResourceNotFound {
|
if err == tfe.ErrResourceNotFound {
|
||||||
err = fmt.Errorf("organization %s does not exist", b.organization)
|
err = fmt.Errorf("organization %s does not exist", b.organization)
|
||||||
}
|
}
|
||||||
diags = diags.Append(tfdiags.AttributeValue(
|
diags = diags.Append(tfdiags.AttributeValue(
|
||||||
tfdiags.Error,
|
tfdiags.Error,
|
||||||
"Failed to read organization settings",
|
"Failed to read organization entitlements",
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
`The "remote" backend encountered an unexpected error while reading the `+
|
`The "remote" backend encountered an unexpected error while reading the `+
|
||||||
`organization settings: %s.`, err,
|
`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.
|
// Configure a local backend for when we need to run operations locally.
|
||||||
b.local = backendLocal.NewWithBackend(b)
|
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
|
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
|
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) {
|
func (m *mockOrganizations) RunQueue(ctx context.Context, name string, options tfe.RunQueueOptions) (*tfe.RunQueue, error) {
|
||||||
rq := &tfe.RunQueue{}
|
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) {
|
func TestRemote_planWorkspaceWithoutOperations(t *testing.T) {
|
||||||
b := testBackendNoDefault(t)
|
b := testBackendNoDefault(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
|
@ -66,6 +66,19 @@ func testBackendNoDefault(t *testing.T) *Remote {
|
||||||
return testBackend(t, obj)
|
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 {
|
func testRemoteClient(t *testing.T) remote.Client {
|
||||||
b := testBackendDefault(t)
|
b := testBackendDefault(t)
|
||||||
raw, err := b.StateMgr(backend.DefaultStateName)
|
raw, err := b.StateMgr(backend.DefaultStateName)
|
||||||
|
@ -171,31 +184,40 @@ func testServer(t *testing.T) *httptest.Server {
|
||||||
io.WriteString(w, `{"tfe.v2":"/api/v2/"}`)
|
io.WriteString(w, `{"tfe.v2":"/api/v2/"}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Respond to the initial query to read the organization settings.
|
// Respond to the initial query to read the hashicorp org entitlements.
|
||||||
mux.HandleFunc("/api/v2/organizations/hashicorp", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/api/v2/organizations/hashicorp/entitlement-set", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/vnd.api+json")
|
w.Header().Set("Content-Type", "application/vnd.api+json")
|
||||||
io.WriteString(w, `{
|
io.WriteString(w, `{
|
||||||
"data": {
|
"data": {
|
||||||
"id": "hashicorp",
|
"id": "org-GExadygjSbKP8hsY",
|
||||||
"type": "organizations",
|
"type": "entitlement-sets",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"name": "hashicorp",
|
"operations": true,
|
||||||
"created-at": "2017-09-07T14:34:40.492Z",
|
"private-module-registry": true,
|
||||||
"email": "user@example.com",
|
"sentinel": true,
|
||||||
"collaborator-auth-policy": "password",
|
"state-storage": true,
|
||||||
"enterprise-plan": "premium",
|
"teams": true,
|
||||||
"permissions": {
|
"vcs-integrations": true
|
||||||
"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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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-rootcerts v0.0.0-20160503143440-6bb64b370b90
|
||||||
github.com/hashicorp/go-safetemp v0.0.0-20180326211150-b1a1dbde6fdc // indirect
|
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-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-uuid v1.0.0
|
||||||
github.com/hashicorp/go-version v0.0.0-20180322230233-23480c066577
|
github.com/hashicorp/go-version v0.0.0-20180322230233-23480c066577
|
||||||
github.com/hashicorp/golang-lru v0.5.0 // indirect
|
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-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 h1:7YOlAIO2YWnJZkQp7B5eFykaIY7C9JndqAFQyVV5BhM=
|
||||||
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
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.4 h1:A9pKjZMDTSGozXf2wQlWhBI7QoxCoas14Xg/TSiEAV8=
|
||||||
github.com/hashicorp/go-tfe v0.3.3/go.mod h1:Vssg8/lwVz+PyJ/nAK97zYmXxxLe28MCIMhKo+rva1o=
|
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 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
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=
|
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 shows the current run capacity of an organization.
|
||||||
Capacity(ctx context.Context, organization string) (*Capacity, error)
|
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 shows the current run queue of an organization.
|
||||||
RunQueue(ctx context.Context, organization string, options RunQueueOptions) (*RunQueue, error)
|
RunQueue(ctx context.Context, organization string, options RunQueueOptions) (*RunQueue, error)
|
||||||
}
|
}
|
||||||
|
@ -93,6 +96,17 @@ type Capacity struct {
|
||||||
Running int `jsonapi:"attr,running"`
|
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.
|
// RunQueue represents the current run queue of an organization.
|
||||||
type RunQueue struct {
|
type RunQueue struct {
|
||||||
*Pagination
|
*Pagination
|
||||||
|
@ -283,6 +297,27 @@ func (s *organizations) Capacity(ctx context.Context, organization string) (*Cap
|
||||||
return c, nil
|
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.
|
// RunQueueOptions represents the options for showing the queue.
|
||||||
type RunQueueOptions struct {
|
type RunQueueOptions struct {
|
||||||
ListOptions
|
ListOptions
|
||||||
|
|
|
@ -327,7 +327,7 @@ github.com/hashicorp/go-rootcerts
|
||||||
github.com/hashicorp/go-safetemp
|
github.com/hashicorp/go-safetemp
|
||||||
# github.com/hashicorp/go-slug v0.2.0
|
# github.com/hashicorp/go-slug v0.2.0
|
||||||
github.com/hashicorp/go-slug
|
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-tfe
|
||||||
# github.com/hashicorp/go-uuid v1.0.0
|
# github.com/hashicorp/go-uuid v1.0.0
|
||||||
github.com/hashicorp/go-uuid
|
github.com/hashicorp/go-uuid
|
||||||
|
|
Loading…
Reference in New Issue