vendor: go get github.com/hashicorp/go-tfe@v0.8.0
This includes a new TargetAddrs field on both Run and RunCreateOptions which we'll use to send resource addresses that were specified using -target on the CLI command line when using the remote backend. There were some unrelated upstream breaking changes compared to the last version we had vendored, so this commit also includes some changes to the backend/remote package to work with this new API, which now requires the remote backend to be aware of the remote system's opaque workspace id.
This commit is contained in:
parent
1d834fb1d0
commit
db4f3f8bc5
|
@ -30,23 +30,17 @@ func (b *Remote) Context(op *backend.Operation) (*terraform.Context, statemgr.Fu
|
|||
}
|
||||
|
||||
// Get the remote workspace name.
|
||||
workspace := op.Workspace
|
||||
switch {
|
||||
case op.Workspace == backend.DefaultStateName:
|
||||
workspace = b.workspace
|
||||
case b.prefix != "" && !strings.HasPrefix(op.Workspace, b.prefix):
|
||||
workspace = b.prefix + op.Workspace
|
||||
}
|
||||
remoteWorkspaceName := b.getRemoteWorkspaceName(op.Workspace)
|
||||
|
||||
// Get the latest state.
|
||||
log.Printf("[TRACE] backend/remote: requesting state manager for workspace %q", workspace)
|
||||
log.Printf("[TRACE] backend/remote: requesting state manager for workspace %q", remoteWorkspaceName)
|
||||
stateMgr, err := b.StateMgr(op.Workspace)
|
||||
if err != nil {
|
||||
diags = diags.Append(errwrap.Wrapf("Error loading state: {{err}}", err))
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
log.Printf("[TRACE] backend/remote: requesting state lock for workspace %q", workspace)
|
||||
log.Printf("[TRACE] backend/remote: requesting state lock for workspace %q", remoteWorkspaceName)
|
||||
if err := op.StateLocker.Lock(stateMgr, op.Type.String()); err != nil {
|
||||
diags = diags.Append(errwrap.Wrapf("Error locking state: {{err}}", err))
|
||||
return nil, nil, diags
|
||||
|
@ -63,7 +57,7 @@ func (b *Remote) Context(op *backend.Operation) (*terraform.Context, statemgr.Fu
|
|||
}
|
||||
}()
|
||||
|
||||
log.Printf("[TRACE] backend/remote: reading remote state for workspace %q", workspace)
|
||||
log.Printf("[TRACE] backend/remote: reading remote state for workspace %q", remoteWorkspaceName)
|
||||
if err := stateMgr.RefreshState(); err != nil {
|
||||
diags = diags.Append(errwrap.Wrapf("Error loading state: {{err}}", err))
|
||||
return nil, nil, diags
|
||||
|
@ -83,7 +77,7 @@ func (b *Remote) Context(op *backend.Operation) (*terraform.Context, statemgr.Fu
|
|||
// Load the latest state. If we enter contextFromPlanFile below then the
|
||||
// state snapshot in the plan file must match this, or else it'll return
|
||||
// error diagnostics.
|
||||
log.Printf("[TRACE] backend/remote: retrieving remote state snapshot for workspace %q", workspace)
|
||||
log.Printf("[TRACE] backend/remote: retrieving remote state snapshot for workspace %q", remoteWorkspaceName)
|
||||
opts.State = stateMgr.State()
|
||||
|
||||
log.Printf("[TRACE] backend/remote: loading configuration for the current working directory")
|
||||
|
@ -94,11 +88,17 @@ func (b *Remote) Context(op *backend.Operation) (*terraform.Context, statemgr.Fu
|
|||
}
|
||||
opts.Config = config
|
||||
|
||||
log.Printf("[TRACE] backend/remote: retrieving variables from workspace %q", workspace)
|
||||
tfeVariables, err := b.client.Variables.List(context.Background(), tfe.VariableListOptions{
|
||||
Organization: tfe.String(b.organization),
|
||||
Workspace: tfe.String(workspace),
|
||||
})
|
||||
// The underlying API expects us to use the opaque workspace id to request
|
||||
// variables, so we'll need to look that up using our organization name
|
||||
// and workspace name.
|
||||
remoteWorkspaceID, err := b.getRemoteWorkspaceID(context.Background(), op.Workspace)
|
||||
if err != nil {
|
||||
diags = diags.Append(errwrap.Wrapf("Error finding remote workspace: {{err}}", err))
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
log.Printf("[TRACE] backend/remote: retrieving variables from workspace %s/%s (%s)", remoteWorkspaceName, b.organization, remoteWorkspaceID)
|
||||
tfeVariables, err := b.client.Variables.List(context.Background(), remoteWorkspaceID, tfe.VariableListOptions{})
|
||||
if err != nil && err != tfe.ErrResourceNotFound {
|
||||
diags = diags.Append(errwrap.Wrapf("Error loading variables: {{err}}", err))
|
||||
return nil, nil, diags
|
||||
|
@ -142,6 +142,32 @@ func (b *Remote) Context(op *backend.Operation) (*terraform.Context, statemgr.Fu
|
|||
return tfCtx, stateMgr, diags
|
||||
}
|
||||
|
||||
func (b *Remote) getRemoteWorkspaceName(localWorkspaceName string) string {
|
||||
switch {
|
||||
case localWorkspaceName == backend.DefaultStateName:
|
||||
// The default workspace name is a special case, for when the backend
|
||||
// is configured to with to an exact remote workspace rather than with
|
||||
// a remote workspace _prefix_.
|
||||
return b.workspace
|
||||
case b.prefix != "" && !strings.HasPrefix(localWorkspaceName, b.prefix):
|
||||
return b.prefix + localWorkspaceName
|
||||
default:
|
||||
return localWorkspaceName
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Remote) getRemoteWorkspaceID(ctx context.Context, localWorkspaceName string) (string, error) {
|
||||
remoteWorkspaceName := b.getRemoteWorkspaceName(localWorkspaceName)
|
||||
|
||||
log.Printf("[TRACE] backend/remote: looking up workspace id for %s/%s", b.organization, remoteWorkspaceName)
|
||||
remoteWorkspace, err := b.client.Workspaces.Read(ctx, b.organization, remoteWorkspaceName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return remoteWorkspace.ID, nil
|
||||
}
|
||||
|
||||
func stubAllVariables(vv map[string]backend.UnparsedVariableValue, decls map[string]*configs.Variable) terraform.InputValues {
|
||||
ret := make(terraform.InputValues, len(decls))
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package remote
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
tfe "github.com/hashicorp/go-tfe"
|
||||
|
@ -176,6 +177,11 @@ func TestRemoteContextWithVars(t *testing.T) {
|
|||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||
defer configCleanup()
|
||||
|
||||
workspaceID, err := b.getRemoteWorkspaceID(context.Background(), backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
op := &backend.Operation{
|
||||
ConfigDir: configDir,
|
||||
ConfigLoader: configLoader,
|
||||
|
@ -187,12 +193,7 @@ func TestRemoteContextWithVars(t *testing.T) {
|
|||
key := "key"
|
||||
v.Key = &key
|
||||
}
|
||||
if v.Workspace == nil {
|
||||
v.Workspace = &tfe.Workspace{
|
||||
Name: b.workspace,
|
||||
}
|
||||
}
|
||||
b.client.Variables.Create(nil, *v)
|
||||
b.client.Variables.Create(nil, workspaceID, *v)
|
||||
|
||||
_, _, diags := b.Context(op)
|
||||
|
||||
|
|
|
@ -952,6 +952,8 @@ type mockVariables struct {
|
|||
workspaces map[string]*tfe.VariableList
|
||||
}
|
||||
|
||||
var _ tfe.Variables = (*mockVariables)(nil)
|
||||
|
||||
func newMockVariables(client *mockClient) *mockVariables {
|
||||
return &mockVariables{
|
||||
client: client,
|
||||
|
@ -959,12 +961,12 @@ func newMockVariables(client *mockClient) *mockVariables {
|
|||
}
|
||||
}
|
||||
|
||||
func (m *mockVariables) List(ctx context.Context, options tfe.VariableListOptions) (*tfe.VariableList, error) {
|
||||
vl := m.workspaces[*options.Workspace]
|
||||
func (m *mockVariables) List(ctx context.Context, workspaceID string, options tfe.VariableListOptions) (*tfe.VariableList, error) {
|
||||
vl := m.workspaces[workspaceID]
|
||||
return vl, nil
|
||||
}
|
||||
|
||||
func (m *mockVariables) Create(ctx context.Context, options tfe.VariableCreateOptions) (*tfe.Variable, error) {
|
||||
func (m *mockVariables) Create(ctx context.Context, workspaceID string, options tfe.VariableCreateOptions) (*tfe.Variable, error) {
|
||||
v := &tfe.Variable{
|
||||
ID: generateID("var-"),
|
||||
Key: *options.Key,
|
||||
|
@ -980,7 +982,7 @@ func (m *mockVariables) Create(ctx context.Context, options tfe.VariableCreateOp
|
|||
v.Sensitive = *options.Sensitive
|
||||
}
|
||||
|
||||
workspace := options.Workspace.Name
|
||||
workspace := workspaceID
|
||||
|
||||
if m.workspaces[workspace] == nil {
|
||||
m.workspaces[workspace] = &tfe.VariableList{}
|
||||
|
@ -992,15 +994,15 @@ func (m *mockVariables) Create(ctx context.Context, options tfe.VariableCreateOp
|
|||
return v, nil
|
||||
}
|
||||
|
||||
func (m *mockVariables) Read(ctx context.Context, variableID string) (*tfe.Variable, error) {
|
||||
func (m *mockVariables) Read(ctx context.Context, workspaceID string, variableID string) (*tfe.Variable, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (m *mockVariables) Update(ctx context.Context, variableID string, options tfe.VariableUpdateOptions) (*tfe.Variable, error) {
|
||||
func (m *mockVariables) Update(ctx context.Context, workspaceID string, variableID string, options tfe.VariableUpdateOptions) (*tfe.Variable, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (m *mockVariables) Delete(ctx context.Context, variableID string) error {
|
||||
func (m *mockVariables) Delete(ctx context.Context, workspaceID string, variableID string) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
|
|
2
go.mod
2
go.mod
|
@ -64,7 +64,7 @@ require (
|
|||
github.com/hashicorp/go-retryablehttp v0.5.2
|
||||
github.com/hashicorp/go-rootcerts v1.0.0
|
||||
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 // indirect
|
||||
github.com/hashicorp/go-tfe v0.3.27
|
||||
github.com/hashicorp/go-tfe v0.8.0
|
||||
github.com/hashicorp/go-uuid v1.0.1
|
||||
github.com/hashicorp/go-version v1.2.0
|
||||
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f
|
||||
|
|
4
go.sum
4
go.sum
|
@ -231,8 +231,8 @@ github.com/hashicorp/go-slug v0.4.1 h1:/jAo8dNuLgSImoLXaX7Od7QB4TfYCVPam+OpAt5bZ
|
|||
github.com/hashicorp/go-slug v0.4.1/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8=
|
||||
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.27 h1:7XZ/ZoPyYoeuNXaWWW0mJOq016y0qb7I4Q0P/cagyu8=
|
||||
github.com/hashicorp/go-tfe v0.3.27/go.mod h1:DVPSW2ogH+M9W1/i50ASgMht8cHP7NxxK0nrY9aFikQ=
|
||||
github.com/hashicorp/go-tfe v0.8.0 h1:kz3x3tbIKRkEAzKg05P/qbFY88fkEU7TiSX3w8xUrmE=
|
||||
github.com/hashicorp/go-tfe v0.8.0/go.mod h1:XAV72S4O1iP8BDaqiaPLmL2B4EE6almocnOn8E8stHc=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
|
|
|
@ -26,19 +26,22 @@ Currently the following endpoints are supported:
|
|||
- [x] [OAuth Clients](https://www.terraform.io/docs/enterprise/api/oauth-clients.html)
|
||||
- [x] [OAuth Tokens](https://www.terraform.io/docs/enterprise/api/oauth-tokens.html)
|
||||
- [x] [Organizations](https://www.terraform.io/docs/enterprise/api/organizations.html)
|
||||
- [x] [Organization Memberships](https://www.terraform.io/docs/cloud/api/organization-memberships.html)
|
||||
- [x] [Organization Tokens](https://www.terraform.io/docs/enterprise/api/organization-tokens.html)
|
||||
- [x] [Policies](https://www.terraform.io/docs/enterprise/api/policies.html)
|
||||
- [x] [Policy Set Parameters](https://www.terraform.io/docs/enterprise/api/policy-set-params.html)
|
||||
- [x] [Policy Sets](https://www.terraform.io/docs/enterprise/api/policy-sets.html)
|
||||
- [x] [Policy Checks](https://www.terraform.io/docs/enterprise/api/policy-checks.html)
|
||||
- [ ] [Registry Modules](https://www.terraform.io/docs/enterprise/api/modules.html)
|
||||
- [x] [Runs](https://www.terraform.io/docs/enterprise/api/run.html)
|
||||
- [x] [Run Triggers](https://www.terraform.io/docs/cloud/api/run-triggers.html)
|
||||
- [x] [SSH Keys](https://www.terraform.io/docs/enterprise/api/ssh-keys.html)
|
||||
- [x] [State Versions](https://www.terraform.io/docs/enterprise/api/state-versions.html)
|
||||
- [x] [Team Access](https://www.terraform.io/docs/enterprise/api/team-access.html)
|
||||
- [x] [Team Memberships](https://www.terraform.io/docs/enterprise/api/team-members.html)
|
||||
- [x] [Team Tokens](https://www.terraform.io/docs/enterprise/api/team-tokens.html)
|
||||
- [x] [Teams](https://www.terraform.io/docs/enterprise/api/teams.html)
|
||||
- [x] [Variables](https://www.terraform.io/docs/enterprise/api/variables.html)
|
||||
- [x] [Workspace Variables](https://www.terraform.io/docs/enterprise/api/workspace-variables.html)
|
||||
- [x] [Workspaces](https://www.terraform.io/docs/enterprise/api/workspaces.html)
|
||||
- [ ] [Admin](https://www.terraform.io/docs/enterprise/api/admin/index.html)
|
||||
|
||||
|
@ -145,7 +148,7 @@ and token.
|
|||
1. `TFE_TOKEN` - A [user API token](https://www.terraform.io/docs/cloud/users-teams-organizations/users.html#api-tokens) for the Terraform Cloud or Terraform Enterprise instance being used for testing.
|
||||
|
||||
##### Optional:
|
||||
1. `GITHUB_TOKEN` - [GitHub personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). Required for running OAuth client tests.
|
||||
1. `GITHUB_TOKEN` - [GitHub personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). Required for running any tests that use VCS (OAuth clients, policy sets, etc).
|
||||
1. `GITHUB_POLICY_SET_IDENTIFIER` - GitHub policy set repository identifier in the format `username/repository`. Required for running policy set tests.
|
||||
|
||||
You can set your environment variables up however you prefer. The following are instructions for setting up environment variables using [envchain](https://github.com/sorah/envchain).
|
||||
|
@ -233,4 +236,4 @@ Documentation updates and test fixes that only touch test files don't require a
|
|||
|
||||
- Don't attach any binaries. The zip and tar.gz assets are automatically created and attached after you publish your release.
|
||||
- Click "Publish release" to save and publish your release.
|
||||
|
||||
|
||||
|
|
|
@ -11,3 +11,5 @@ require (
|
|||
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
|
||||
)
|
||||
|
||||
go 1.12
|
||||
|
|
|
@ -8,10 +8,6 @@ github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6K
|
|||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.2 h1:AoISa4P4IsW0/m4T6St8Yw38gTl5GtBAgfkhYh1xAz4=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-slug v0.4.0 h1:YSz3afoEZZJVVB46NITf0+opd2cHpaYJ1XSojOyP0x8=
|
||||
github.com/hashicorp/go-slug v0.4.0/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8=
|
||||
github.com/hashicorp/go-slug v0.4.1-0.20191114211806-d9ee9eb3692a h1:EmBGX5Ja8JEKRHqTDG9+PYq0qL5qyOUmPZFQfH7VfXo=
|
||||
github.com/hashicorp/go-slug v0.4.1-0.20191114211806-d9ee9eb3692a/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8=
|
||||
github.com/hashicorp/go-slug v0.4.1 h1:/jAo8dNuLgSImoLXaX7Od7QB4TfYCVPam+OpAt5bZqc=
|
||||
github.com/hashicorp/go-slug v0.4.1/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8=
|
||||
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
||||
|
|
|
@ -120,6 +120,9 @@ type OAuthClientCreateOptions struct {
|
|||
// The token string you were given by your VCS provider.
|
||||
OAuthToken *string `jsonapi:"attr,oauth-token-string"`
|
||||
|
||||
// Private key associated with this vcs provider - only available for ado_server
|
||||
PrivateKey *string `jsonapi:"attr,private-key"`
|
||||
|
||||
// The VCS provider being connected with.
|
||||
ServiceProvider *ServiceProviderType `jsonapi:"attr,service-provider"`
|
||||
}
|
||||
|
@ -137,6 +140,9 @@ func (o OAuthClientCreateOptions) valid() error {
|
|||
if o.ServiceProvider == nil {
|
||||
return errors.New("service provider is required")
|
||||
}
|
||||
if validString(o.PrivateKey) && *o.ServiceProvider != *ServiceProvider(ServiceProviderAzureDevOpsServer) {
|
||||
return errors.New("Private Key can only be present with Azure DevOps Server service provider")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
package tfe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Compile-time proof of interface implementation.
|
||||
var _ OrganizationMemberships = (*organizationMemberships)(nil)
|
||||
|
||||
// OrganizationMemberships describes all the organization membership related methods that
|
||||
// the Terraform Enterprise API supports.
|
||||
//
|
||||
// TFE API docs:
|
||||
// https://www.terraform.io/docs/cloud/api/organization-memberships.html
|
||||
type OrganizationMemberships interface {
|
||||
// List all the organization memberships of the given organization.
|
||||
List(ctx context.Context, organization string, options OrganizationMembershipListOptions) (*OrganizationMembershipList, error)
|
||||
|
||||
// Create a new organization membership with the given options.
|
||||
Create(ctx context.Context, organization string, options OrganizationMembershipCreateOptions) (*OrganizationMembership, error)
|
||||
|
||||
// Read an organization membership by ID
|
||||
Read(ctx context.Context, organizationMembershipID string) (*OrganizationMembership, error)
|
||||
|
||||
// Read an organization membership by ID with options
|
||||
ReadWithOptions(ctx context.Context, organizationMembershipID string, options OrganizationMembershipReadOptions) (*OrganizationMembership, error)
|
||||
|
||||
// Delete an organization membership by its ID.
|
||||
Delete(ctx context.Context, organizationMembershipID string) error
|
||||
}
|
||||
|
||||
// organizationMemberships implements OrganizationMemberships.
|
||||
type organizationMemberships struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// OrganizationMembershipStatus represents an organization membership status.
|
||||
type OrganizationMembershipStatus string
|
||||
|
||||
// List all available organization membership statuses.
|
||||
const (
|
||||
OrganizationMembershipActive = "active"
|
||||
OrganizationMembershipInvited = "invited"
|
||||
)
|
||||
|
||||
// OrganizationMembershipList represents a list of organization memberships.
|
||||
type OrganizationMembershipList struct {
|
||||
*Pagination
|
||||
Items []*OrganizationMembership
|
||||
}
|
||||
|
||||
// OrganizationMembership represents a Terraform Enterprise organization membership.
|
||||
type OrganizationMembership struct {
|
||||
ID string `jsonapi:"primary,organization-memberships"`
|
||||
Status OrganizationMembershipStatus `jsonapi:"attr,status"`
|
||||
Email string `jsonapi:"attr,email"`
|
||||
|
||||
// Relations
|
||||
Organization *Organization `jsonapi:"relation,organization"`
|
||||
User *User `jsonapi:"relation,user"`
|
||||
Teams []*Team `jsonapi:"relation,teams"`
|
||||
}
|
||||
|
||||
// OrganizationMembershipListOptions represents the options for listing organization memberships.
|
||||
type OrganizationMembershipListOptions struct {
|
||||
ListOptions
|
||||
|
||||
Include string `url:"include"`
|
||||
}
|
||||
|
||||
// List all the organization memberships of the given organization.
|
||||
func (s *organizationMemberships) List(ctx context.Context, organization string, options OrganizationMembershipListOptions) (*OrganizationMembershipList, error) {
|
||||
if !validStringID(&organization) {
|
||||
return nil, errors.New("invalid value for organization")
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("organizations/%s/organization-memberships", url.QueryEscape(organization))
|
||||
req, err := s.client.newRequest("GET", u, &options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ml := &OrganizationMembershipList{}
|
||||
err = s.client.do(ctx, req, ml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ml, nil
|
||||
}
|
||||
|
||||
// OrganizationMembershipCreateOptions represents the options for creating an organization membership.
|
||||
type OrganizationMembershipCreateOptions struct {
|
||||
// For internal use only!
|
||||
ID string `jsonapi:"primary,organization-memberships"`
|
||||
|
||||
// User's email address.
|
||||
Email *string `jsonapi:"attr,email"`
|
||||
}
|
||||
|
||||
func (o OrganizationMembershipCreateOptions) valid() error {
|
||||
if o.Email == nil {
|
||||
return errors.New("email is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create an organization membership with the given options.
|
||||
func (s *organizationMemberships) Create(ctx context.Context, organization string, options OrganizationMembershipCreateOptions) (*OrganizationMembership, error) {
|
||||
if !validStringID(&organization) {
|
||||
return nil, errors.New("invalid value for organization")
|
||||
}
|
||||
if err := options.valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
options.ID = ""
|
||||
|
||||
u := fmt.Sprintf("organizations/%s/organization-memberships", url.QueryEscape(organization))
|
||||
req, err := s.client.newRequest("POST", u, &options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := &OrganizationMembership{}
|
||||
err = s.client.do(ctx, req, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Read an organization membership by its ID.
|
||||
func (s *organizationMemberships) Read(ctx context.Context, organizationMembershipID string) (*OrganizationMembership, error) {
|
||||
return s.ReadWithOptions(ctx, organizationMembershipID, OrganizationMembershipReadOptions{})
|
||||
}
|
||||
|
||||
// OrganizationMembershipReadOptions represents the options for reading organization memberships.
|
||||
type OrganizationMembershipReadOptions struct {
|
||||
Include string `url:"include"`
|
||||
}
|
||||
|
||||
// Read an organization membership by ID with options
|
||||
func (s *organizationMemberships) ReadWithOptions(ctx context.Context, organizationMembershipID string, options OrganizationMembershipReadOptions) (*OrganizationMembership, error) {
|
||||
if !validStringID(&organizationMembershipID) {
|
||||
return nil, errors.New("invalid value for membership")
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("organization-memberships/%s", url.QueryEscape(organizationMembershipID))
|
||||
req, err := s.client.newRequest("GET", u, &options)
|
||||
|
||||
mem := &OrganizationMembership{}
|
||||
err = s.client.do(ctx, req, mem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mem, nil
|
||||
}
|
||||
|
||||
// Delete an organization membership by its ID.
|
||||
func (s *organizationMemberships) Delete(ctx context.Context, organizationMembershipID string) error {
|
||||
if !validStringID(&organizationMembershipID) {
|
||||
return errors.New("invalid value for membership")
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("organization-memberships/%s", url.QueryEscape(organizationMembershipID))
|
||||
req, err := s.client.newRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.client.do(ctx, req, nil)
|
||||
}
|
|
@ -209,6 +209,19 @@ type PolicySetUpdateOptions struct {
|
|||
|
||||
// Whether or not the policy set is global.
|
||||
Global *bool `jsonapi:"attr,global,omitempty"`
|
||||
|
||||
// The sub-path within the attached VCS repository to ingress. All
|
||||
// files and directories outside of this sub-path will be ignored.
|
||||
// This option may only be specified when a VCS repo is present.
|
||||
PoliciesPath *string `jsonapi:"attr,policies-path,omitempty"`
|
||||
|
||||
// VCS repository information. When present, the policies and
|
||||
// configuration will be sourced from the specified VCS repository
|
||||
// instead of being defined within the policy set itself. Note that
|
||||
// specifying this option may only be used on policy sets with no
|
||||
// directly-attached policies (*PolicySet.Policies). Specifying this
|
||||
// option when policies are already present will result in an error.
|
||||
VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo,omitempty"`
|
||||
}
|
||||
|
||||
func (o PolicySetUpdateOptions) valid() error {
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
package tfe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Compile-time proof of interface implementation.
|
||||
var _ PolicySetParameters = (*policySetParameters)(nil)
|
||||
|
||||
// PolicySetParameters describes all the parameter related methods that the Terraform
|
||||
// Enterprise API supports.
|
||||
//
|
||||
// TFE API docs: https://www.terraform.io/docs/enterprise/api/policy-set-params.html
|
||||
type PolicySetParameters interface {
|
||||
// List all the parameters associated with the given policy-set.
|
||||
List(ctx context.Context, policySetID string, options PolicySetParameterListOptions) (*PolicySetParameterList, error)
|
||||
|
||||
// Create is used to create a new parameter.
|
||||
Create(ctx context.Context, policySetID string, options PolicySetParameterCreateOptions) (*PolicySetParameter, error)
|
||||
|
||||
// Read a parameter by its ID.
|
||||
Read(ctx context.Context, policySetID string, parameterID string) (*PolicySetParameter, error)
|
||||
|
||||
// Update values of an existing parameter.
|
||||
Update(ctx context.Context, policySetID string, parameterID string, options PolicySetParameterUpdateOptions) (*PolicySetParameter, error)
|
||||
|
||||
// Delete a parameter by its ID.
|
||||
Delete(ctx context.Context, policySetID string, parameterID string) error
|
||||
}
|
||||
|
||||
// policySetParameters implements Parameters.
|
||||
type policySetParameters struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// PolicySetParameterList represents a list of parameters.
|
||||
type PolicySetParameterList struct {
|
||||
*Pagination
|
||||
Items []*PolicySetParameter
|
||||
}
|
||||
|
||||
// PolicySetParameter represents a Policy Set parameter
|
||||
type PolicySetParameter struct {
|
||||
ID string `jsonapi:"primary,vars"`
|
||||
Key string `jsonapi:"attr,key"`
|
||||
Value string `jsonapi:"attr,value"`
|
||||
Category CategoryType `jsonapi:"attr,category"`
|
||||
Sensitive bool `jsonapi:"attr,sensitive"`
|
||||
|
||||
// Relations
|
||||
PolicySet *PolicySet `jsonapi:"relation,configurable"`
|
||||
}
|
||||
|
||||
// PolicySetParameterListOptions represents the options for listing parameters.
|
||||
type PolicySetParameterListOptions struct {
|
||||
ListOptions
|
||||
}
|
||||
|
||||
func (o PolicySetParameterListOptions) valid() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// List all the parameters associated with the given policy-set.
|
||||
func (s *policySetParameters) List(ctx context.Context, policySetID string, options PolicySetParameterListOptions) (*PolicySetParameterList, error) {
|
||||
if !validStringID(&policySetID) {
|
||||
return nil, errors.New("invalid value for policy set ID")
|
||||
}
|
||||
if err := options.valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("policy-sets/%s/parameters", policySetID)
|
||||
req, err := s.client.newRequest("GET", u, &options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vl := &PolicySetParameterList{}
|
||||
err = s.client.do(ctx, req, vl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vl, nil
|
||||
}
|
||||
|
||||
// PolicySetParameterCreateOptions represents the options for creating a new parameter.
|
||||
type PolicySetParameterCreateOptions struct {
|
||||
// For internal use only!
|
||||
ID string `jsonapi:"primary,vars"`
|
||||
|
||||
// The name of the parameter.
|
||||
Key *string `jsonapi:"attr,key"`
|
||||
|
||||
// The value of the parameter.
|
||||
Value *string `jsonapi:"attr,value,omitempty"`
|
||||
|
||||
// The Category of the parameter, should always be "policy-set"
|
||||
Category *CategoryType `jsonapi:"attr,category"`
|
||||
|
||||
// Whether the value is sensitive.
|
||||
Sensitive *bool `jsonapi:"attr,sensitive,omitempty"`
|
||||
}
|
||||
|
||||
func (o PolicySetParameterCreateOptions) valid() error {
|
||||
if !validString(o.Key) {
|
||||
return errors.New("key is required")
|
||||
}
|
||||
if o.Category == nil {
|
||||
return errors.New("category is required")
|
||||
}
|
||||
if *o.Category != CategoryPolicySet {
|
||||
return errors.New("category must be policy-set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create is used to create a new parameter.
|
||||
func (s *policySetParameters) Create(ctx context.Context, policySetID string, options PolicySetParameterCreateOptions) (*PolicySetParameter, error) {
|
||||
if !validStringID(&policySetID) {
|
||||
return nil, errors.New("invalid value for policy set ID")
|
||||
}
|
||||
if err := options.valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make sure we don't send a user provided ID.
|
||||
options.ID = ""
|
||||
|
||||
u := fmt.Sprintf("policy-sets/%s/parameters", url.QueryEscape(policySetID))
|
||||
req, err := s.client.newRequest("POST", u, &options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &PolicySetParameter{}
|
||||
err = s.client.do(ctx, req, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Read a parameter by its ID.
|
||||
func (s *policySetParameters) Read(ctx context.Context, policySetID string, parameterID string) (*PolicySetParameter, error) {
|
||||
if !validStringID(&policySetID) {
|
||||
return nil, errors.New("invalid value for policy set ID")
|
||||
}
|
||||
if !validStringID(¶meterID) {
|
||||
return nil, errors.New("invalid value for parameter ID")
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("policy-sets/%s/parameters/%s", url.QueryEscape(policySetID), url.QueryEscape(parameterID))
|
||||
req, err := s.client.newRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &PolicySetParameter{}
|
||||
err = s.client.do(ctx, req, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, err
|
||||
}
|
||||
|
||||
// PolicySetParameterUpdateOptions represents the options for updating a parameter.
|
||||
type PolicySetParameterUpdateOptions struct {
|
||||
// For internal use only!
|
||||
ID string `jsonapi:"primary,vars"`
|
||||
|
||||
// The name of the parameter.
|
||||
Key *string `jsonapi:"attr,key,omitempty"`
|
||||
|
||||
// The value of the parameter.
|
||||
Value *string `jsonapi:"attr,value,omitempty"`
|
||||
|
||||
// Whether the value is sensitive.
|
||||
Sensitive *bool `jsonapi:"attr,sensitive,omitempty"`
|
||||
}
|
||||
|
||||
// Update values of an existing parameter.
|
||||
func (s *policySetParameters) Update(ctx context.Context, policySetID string, parameterID string, options PolicySetParameterUpdateOptions) (*PolicySetParameter, error) {
|
||||
if !validStringID(&policySetID) {
|
||||
return nil, errors.New("invalid value for policy set ID")
|
||||
}
|
||||
if !validStringID(¶meterID) {
|
||||
return nil, errors.New("invalid value for parameter ID")
|
||||
}
|
||||
|
||||
// Make sure we don't send a user provided ID.
|
||||
options.ID = parameterID
|
||||
|
||||
u := fmt.Sprintf("policy-sets/%s/parameters/%s", url.QueryEscape(policySetID), url.QueryEscape(parameterID))
|
||||
req, err := s.client.newRequest("PATCH", u, &options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &PolicySetParameter{}
|
||||
err = s.client.do(ctx, req, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Delete a parameter by its ID.
|
||||
func (s *policySetParameters) Delete(ctx context.Context, policySetID string, parameterID string) error {
|
||||
if !validStringID(&policySetID) {
|
||||
return errors.New("invalid value for policy set ID")
|
||||
}
|
||||
if !validStringID(¶meterID) {
|
||||
return errors.New("invalid value for parameter ID")
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("policy-sets/%s/parameters/%s", url.QueryEscape(policySetID), url.QueryEscape(parameterID))
|
||||
req, err := s.client.newRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.client.do(ctx, req, nil)
|
||||
}
|
|
@ -98,6 +98,7 @@ type Run struct {
|
|||
Source RunSource `jsonapi:"attr,source"`
|
||||
Status RunStatus `jsonapi:"attr,status"`
|
||||
StatusTimestamps *RunStatusTimestamps `jsonapi:"attr,status-timestamps"`
|
||||
TargetAddrs []string `jsonapi:"attr,target-addrs,omitempty"`
|
||||
|
||||
// Relations
|
||||
Apply *Apply `jsonapi:"relation,apply"`
|
||||
|
@ -184,6 +185,19 @@ type RunCreateOptions struct {
|
|||
|
||||
// Specifies the workspace where the run will be executed.
|
||||
Workspace *Workspace `jsonapi:"relation,workspace"`
|
||||
|
||||
// If non-empty, requests that Terraform should create a plan including
|
||||
// actions only for the given objects (specified using resource address
|
||||
// syntax) and the objects they depend on.
|
||||
//
|
||||
// This capability is provided for exceptional circumstances only, such as
|
||||
// recovering from mistakes or working around existing Terraform
|
||||
// limitations. Terraform will generally mention the -target command line
|
||||
// option in its error messages describing situations where setting this
|
||||
// argument may be appropriate. This argument should not be used as part
|
||||
// of routine workflow and Terraform will emit warnings reminding about
|
||||
// this whenever this property is set.
|
||||
TargetAddrs []string `jsonapi:"attr,target-addrs,omitempty"`
|
||||
}
|
||||
|
||||
func (o RunCreateOptions) valid() error {
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
package tfe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Compile-time proof of interface implementation.
|
||||
var _ RunTriggers = (*runTriggers)(nil)
|
||||
|
||||
// RunTriggers describes all the Run Trigger
|
||||
// related methods that the Terraform Cloud API supports.
|
||||
//
|
||||
// TFE API docs:
|
||||
// https://www.terraform.io/docs/cloud/api/run-triggers.html
|
||||
type RunTriggers interface {
|
||||
// List all the run triggers within a workspace.
|
||||
List(ctx context.Context, workspaceID string, options RunTriggerListOptions) (*RunTriggerList, error)
|
||||
|
||||
// Create a new run trigger with the given options.
|
||||
Create(ctx context.Context, workspaceID string, options RunTriggerCreateOptions) (*RunTrigger, error)
|
||||
|
||||
// Read a run trigger by its ID.
|
||||
Read(ctx context.Context, RunTriggerID string) (*RunTrigger, error)
|
||||
|
||||
// Delete a run trigger by its ID.
|
||||
Delete(ctx context.Context, RunTriggerID string) error
|
||||
}
|
||||
|
||||
// runTriggers implements RunTriggers.
|
||||
type runTriggers struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// RunTriggerList represents a list of Run Triggers
|
||||
type RunTriggerList struct {
|
||||
*Pagination
|
||||
Items []*RunTrigger
|
||||
}
|
||||
|
||||
// RunTrigger represents a run trigger.
|
||||
type RunTrigger struct {
|
||||
ID string `jsonapi:"primary,run-triggers"`
|
||||
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
|
||||
SourceableName string `jsonapi:"attr,sourceable-name"`
|
||||
WorkspaceName string `jsonapi:"attr,workspace-name"`
|
||||
|
||||
// Relations
|
||||
// TODO: this will eventually need to be polymorphic
|
||||
Sourceable *Workspace `jsonapi:"relation,sourceable"`
|
||||
Workspace *Workspace `jsonapi:"relation,workspace"`
|
||||
}
|
||||
|
||||
// RunTriggerListOptions represents the options for listing
|
||||
// run triggers.
|
||||
type RunTriggerListOptions struct {
|
||||
ListOptions
|
||||
RunTriggerType *string `url:"filter[run-trigger][type]"`
|
||||
}
|
||||
|
||||
func (o RunTriggerListOptions) valid() error {
|
||||
if !validString(o.RunTriggerType) {
|
||||
return errors.New("run-trigger type is required")
|
||||
}
|
||||
if *o.RunTriggerType != "inbound" && *o.RunTriggerType != "outbound" {
|
||||
return errors.New("invalid value for run-trigger type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List all the run triggers associated with a workspace.
|
||||
func (s *runTriggers) List(ctx context.Context, workspaceID string, options RunTriggerListOptions) (*RunTriggerList, error) {
|
||||
if !validStringID(&workspaceID) {
|
||||
return nil, errors.New("invalid value for workspace ID")
|
||||
}
|
||||
|
||||
if err := options.valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("workspaces/%s/run-triggers", url.QueryEscape(workspaceID))
|
||||
req, err := s.client.newRequest("GET", u, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rtl := &RunTriggerList{}
|
||||
err = s.client.do(ctx, req, rtl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rtl, nil
|
||||
}
|
||||
|
||||
// RunTriggerCreateOptions represents the options for
|
||||
// creating a new run trigger.
|
||||
type RunTriggerCreateOptions struct {
|
||||
// For internal use only!
|
||||
ID string `jsonapi:"primary,run-triggers"`
|
||||
|
||||
// The source workspace
|
||||
Sourceable *Workspace `jsonapi:"relation,sourceable"`
|
||||
}
|
||||
|
||||
func (o RunTriggerCreateOptions) valid() error {
|
||||
if o.Sourceable == nil {
|
||||
return errors.New("sourceable is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Creates a run trigger with the given options.
|
||||
func (s *runTriggers) Create(ctx context.Context, workspaceID string, options RunTriggerCreateOptions) (*RunTrigger, error) {
|
||||
if !validStringID(&workspaceID) {
|
||||
return nil, errors.New("invalid value for workspace ID")
|
||||
}
|
||||
if err := options.valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make sure we don't send a user provided ID.
|
||||
options.ID = ""
|
||||
|
||||
u := fmt.Sprintf("workspaces/%s/run-triggers", url.QueryEscape(workspaceID))
|
||||
req, err := s.client.newRequest("POST", u, &options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rt := &RunTrigger{}
|
||||
err = s.client.do(ctx, req, rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rt, nil
|
||||
}
|
||||
|
||||
// Read a run trigger by its ID.
|
||||
func (s *runTriggers) Read(ctx context.Context, runTriggerID string) (*RunTrigger, error) {
|
||||
if !validStringID(&runTriggerID) {
|
||||
return nil, errors.New("invalid value for run trigger ID")
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("run-triggers/%s", url.QueryEscape(runTriggerID))
|
||||
req, err := s.client.newRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rt := &RunTrigger{}
|
||||
err = s.client.do(ctx, req, rt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rt, nil
|
||||
}
|
||||
|
||||
// Delete a run trigger by its ID.
|
||||
func (s *runTriggers) Delete(ctx context.Context, runTriggerID string) error {
|
||||
if !validStringID(&runTriggerID) {
|
||||
return errors.New("invalid value for run trigger ID")
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("run-triggers/%s", url.QueryEscape(runTriggerID))
|
||||
req, err := s.client.newRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.client.do(ctx, req, nil)
|
||||
}
|
|
@ -47,11 +47,13 @@ type Team struct {
|
|||
ID string `jsonapi:"primary,teams"`
|
||||
Name string `jsonapi:"attr,name"`
|
||||
OrganizationAccess *OrganizationAccess `jsonapi:"attr,organization-access"`
|
||||
Visibility string `jsonapi:"attr,visibility"`
|
||||
Permissions *TeamPermissions `jsonapi:"attr,permissions"`
|
||||
UserCount int `jsonapi:"attr,users-count"`
|
||||
|
||||
// Relations
|
||||
Users []*User `jsonapi:"relation,users"`
|
||||
Users []*User `jsonapi:"relation,users"`
|
||||
OrganizationMemberships []*OrganizationMembership `jsonapi:"relation,organization-memberships"`
|
||||
}
|
||||
|
||||
// OrganizationAccess represents the team's permissions on its organization
|
||||
|
@ -103,6 +105,9 @@ type TeamCreateOptions struct {
|
|||
|
||||
// The team's organization access
|
||||
OrganizationAccess *OrganizationAccessOptions `jsonapi:"attr,organization-access,omitempty"`
|
||||
|
||||
// The team's visibility ("secret", "organization")
|
||||
Visibility *string `jsonapi:"attr,visibility,omitempty"`
|
||||
}
|
||||
|
||||
// OrganizationAccessOptions represents the organization access options of a team.
|
||||
|
@ -177,6 +182,9 @@ type TeamUpdateOptions struct {
|
|||
|
||||
// The team's organization access
|
||||
OrganizationAccess *OrganizationAccessOptions `jsonapi:"attr,organization-access,omitempty"`
|
||||
|
||||
// The team's visibility ("secret", "organization")
|
||||
Visibility *string `jsonapi:"attr,visibility,omitempty"`
|
||||
}
|
||||
|
||||
// Update a team by its ID.
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
retryablehttp "github.com/hashicorp/go-retryablehttp"
|
||||
)
|
||||
|
||||
// Compile-time proof of interface implementation.
|
||||
|
@ -16,9 +18,16 @@ var _ TeamMembers = (*teamMembers)(nil)
|
|||
// TFE API docs:
|
||||
// https://www.terraform.io/docs/enterprise/api/team-members.html
|
||||
type TeamMembers interface {
|
||||
// List all members of a team.
|
||||
// List returns all Users of a team calling ListUsers
|
||||
// See ListOrganizationMemberships for fetching memberships
|
||||
List(ctx context.Context, teamID string) ([]*User, error)
|
||||
|
||||
// ListUsers returns the Users of this team.
|
||||
ListUsers(ctx context.Context, teamID string) ([]*User, error)
|
||||
|
||||
// ListOrganizationMemberships returns the OrganizationMemberships of this team.
|
||||
ListOrganizationMemberships(ctx context.Context, teamID string) ([]*OrganizationMembership, error)
|
||||
|
||||
// Add multiple users to a team.
|
||||
Add(ctx context.Context, teamID string, options TeamMemberAddOptions) error
|
||||
|
||||
|
@ -31,12 +40,22 @@ type teamMembers struct {
|
|||
client *Client
|
||||
}
|
||||
|
||||
type teamMember struct {
|
||||
type teamMemberUser struct {
|
||||
Username string `jsonapi:"primary,users"`
|
||||
}
|
||||
|
||||
// List all members of a team.
|
||||
type teamMemberOrgMembership struct {
|
||||
ID string `jsonapi:"primary,organization-memberships"`
|
||||
}
|
||||
|
||||
// List returns all Users of a team calling ListUsers
|
||||
// See ListOrganizationMemberships for fetching memberships
|
||||
func (s *teamMembers) List(ctx context.Context, teamID string) ([]*User, error) {
|
||||
return s.ListUsers(ctx, teamID)
|
||||
}
|
||||
|
||||
// ListUsers returns the Users of this team.
|
||||
func (s *teamMembers) ListUsers(ctx context.Context, teamID string) ([]*User, error) {
|
||||
if !validStringID(&teamID) {
|
||||
return nil, errors.New("invalid value for team ID")
|
||||
}
|
||||
|
@ -62,21 +81,65 @@ func (s *teamMembers) List(ctx context.Context, teamID string) ([]*User, error)
|
|||
return t.Users, nil
|
||||
}
|
||||
|
||||
// TeamMemberAddOptions represents the options for adding team members.
|
||||
// ListOrganizationMemberships returns the OrganizationMemberships of this team.
|
||||
func (s *teamMembers) ListOrganizationMemberships(ctx context.Context, teamID string) ([]*OrganizationMembership, error) {
|
||||
if !validStringID(&teamID) {
|
||||
return nil, errors.New("invalid value for team ID")
|
||||
}
|
||||
|
||||
options := struct {
|
||||
Include string `url:"include"`
|
||||
}{
|
||||
Include: "organization-memberships",
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("teams/%s", url.QueryEscape(teamID))
|
||||
req, err := s.client.newRequest("GET", u, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := &Team{}
|
||||
err = s.client.do(ctx, req, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t.OrganizationMemberships, nil
|
||||
}
|
||||
|
||||
// TeamMemberAddOptions represents the options for
|
||||
// adding or removing team members.
|
||||
type TeamMemberAddOptions struct {
|
||||
Usernames []string
|
||||
Usernames []string
|
||||
OrganizationMembershipIDs []string
|
||||
}
|
||||
|
||||
func (o *TeamMemberAddOptions) valid() error {
|
||||
if o.Usernames == nil {
|
||||
return errors.New("usernames is required")
|
||||
if o.Usernames == nil && o.OrganizationMembershipIDs == nil {
|
||||
return errors.New("usernames or organization membership ids are required")
|
||||
}
|
||||
if len(o.Usernames) == 0 {
|
||||
if o.Usernames != nil && o.OrganizationMembershipIDs != nil {
|
||||
return errors.New("only one of usernames or organization membership ids can be provided")
|
||||
}
|
||||
if o.Usernames != nil && len(o.Usernames) == 0 {
|
||||
return errors.New("invalid value for usernames")
|
||||
}
|
||||
if o.OrganizationMembershipIDs != nil && len(o.OrganizationMembershipIDs) == 0 {
|
||||
return errors.New("invalid value for organization membership ids")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// kind returns "users" or "organization-memberships"
|
||||
// depending on which is defined
|
||||
func (o *TeamMemberAddOptions) kind() string {
|
||||
if o.Usernames != nil && len(o.Usernames) != 0 {
|
||||
return "users"
|
||||
}
|
||||
return "organization-memberships"
|
||||
}
|
||||
|
||||
// Add multiple users to a team.
|
||||
func (s *teamMembers) Add(ctx context.Context, teamID string, options TeamMemberAddOptions) error {
|
||||
if !validStringID(&teamID) {
|
||||
|
@ -86,35 +149,68 @@ func (s *teamMembers) Add(ctx context.Context, teamID string, options TeamMember
|
|||
return err
|
||||
}
|
||||
|
||||
var tms []*teamMember
|
||||
for _, name := range options.Usernames {
|
||||
tms = append(tms, &teamMember{Username: name})
|
||||
}
|
||||
usersOrMemberships := options.kind()
|
||||
URL := fmt.Sprintf("teams/%s/relationships/%s", url.QueryEscape(teamID), usersOrMemberships)
|
||||
|
||||
u := fmt.Sprintf("teams/%s/relationships/users", url.QueryEscape(teamID))
|
||||
req, err := s.client.newRequest("POST", u, tms)
|
||||
if err != nil {
|
||||
return err
|
||||
var req *retryablehttp.Request
|
||||
|
||||
if usersOrMemberships == "users" {
|
||||
var err error
|
||||
var members []*teamMemberUser
|
||||
for _, name := range options.Usernames {
|
||||
members = append(members, &teamMemberUser{Username: name})
|
||||
}
|
||||
req, err = s.client.newRequest("POST", URL, members)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
var members []*teamMemberOrgMembership
|
||||
for _, ID := range options.OrganizationMembershipIDs {
|
||||
members = append(members, &teamMemberOrgMembership{ID: ID})
|
||||
}
|
||||
req, err = s.client.newRequest("POST", URL, members)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return s.client.do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// TeamMemberRemoveOptions represents the options for deleting team members.
|
||||
// TeamMemberRemoveOptions represents the options for
|
||||
// adding or removing team members.
|
||||
type TeamMemberRemoveOptions struct {
|
||||
Usernames []string
|
||||
Usernames []string
|
||||
OrganizationMembershipIDs []string
|
||||
}
|
||||
|
||||
func (o *TeamMemberRemoveOptions) valid() error {
|
||||
if o.Usernames == nil {
|
||||
return errors.New("usernames is required")
|
||||
if o.Usernames == nil && o.OrganizationMembershipIDs == nil {
|
||||
return errors.New("usernames or organization membership ids are required")
|
||||
}
|
||||
if len(o.Usernames) == 0 {
|
||||
if o.Usernames != nil && o.OrganizationMembershipIDs != nil {
|
||||
return errors.New("only one of usernames or organization membership ids can be provided")
|
||||
}
|
||||
if o.Usernames != nil && len(o.Usernames) == 0 {
|
||||
return errors.New("invalid value for usernames")
|
||||
}
|
||||
if o.OrganizationMembershipIDs != nil && len(o.OrganizationMembershipIDs) == 0 {
|
||||
return errors.New("invalid value for organization membership ids")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// kind returns "users" or "organization-memberships"
|
||||
// depending on which is defined
|
||||
func (o *TeamMemberRemoveOptions) kind() string {
|
||||
if o.Usernames != nil && len(o.Usernames) != 0 {
|
||||
return "users"
|
||||
}
|
||||
return "organization-memberships"
|
||||
}
|
||||
|
||||
// Remove multiple users from a team.
|
||||
func (s *teamMembers) Remove(ctx context.Context, teamID string, options TeamMemberRemoveOptions) error {
|
||||
if !validStringID(&teamID) {
|
||||
|
@ -124,15 +220,31 @@ func (s *teamMembers) Remove(ctx context.Context, teamID string, options TeamMem
|
|||
return err
|
||||
}
|
||||
|
||||
var tms []*teamMember
|
||||
for _, name := range options.Usernames {
|
||||
tms = append(tms, &teamMember{Username: name})
|
||||
}
|
||||
usersOrMemberships := options.kind()
|
||||
URL := fmt.Sprintf("teams/%s/relationships/%s", url.QueryEscape(teamID), usersOrMemberships)
|
||||
|
||||
u := fmt.Sprintf("teams/%s/relationships/users", url.QueryEscape(teamID))
|
||||
req, err := s.client.newRequest("DELETE", u, tms)
|
||||
if err != nil {
|
||||
return err
|
||||
var req *retryablehttp.Request
|
||||
|
||||
if usersOrMemberships == "users" {
|
||||
var err error
|
||||
var members []*teamMemberUser
|
||||
for _, name := range options.Usernames {
|
||||
members = append(members, &teamMemberUser{Username: name})
|
||||
}
|
||||
req, err = s.client.newRequest("DELETE", URL, members)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
var members []*teamMemberOrgMembership
|
||||
for _, ID := range options.OrganizationMembershipIDs {
|
||||
members = append(members, &teamMemberOrgMembership{ID: ID})
|
||||
}
|
||||
req, err = s.client.newRequest("DELETE", URL, members)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return s.client.do(ctx, req, nil)
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package tfe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestAccountDetails represents the basic account information
|
||||
// of a TFE/TFC user.
|
||||
//
|
||||
// See FetchTestAccountDetails for more information.
|
||||
type TestAccountDetails struct {
|
||||
ID string `json:"id" jsonapi:"primary,users"`
|
||||
Username string `jsonapi:"attr,username"`
|
||||
Email string `jsonapi:"attr,email"`
|
||||
}
|
||||
|
||||
// FetchTestAccountDetails returns TestAccountDetails
|
||||
// of the user running the tests.
|
||||
//
|
||||
// Use this helper to fetch the username and email
|
||||
// address associated with the token used to run the tests.
|
||||
func FetchTestAccountDetails(t *testing.T, client *Client) *TestAccountDetails {
|
||||
tad := &TestAccountDetails{}
|
||||
req, err := client.newRequest("GET", "account/details", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("could not create account details request: %v", err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
err = client.do(ctx, req, tad)
|
||||
if err != nil {
|
||||
t.Fatalf("could not fetch test user details: %v", err)
|
||||
}
|
||||
return tad
|
||||
}
|
|
@ -24,15 +24,16 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
userAgent = "go-tfe"
|
||||
headerRateLimit = "X-RateLimit-Limit"
|
||||
headerRateReset = "X-RateLimit-Reset"
|
||||
userAgent = "go-tfe"
|
||||
headerRateLimit = "X-RateLimit-Limit"
|
||||
headerRateReset = "X-RateLimit-Reset"
|
||||
headerAPIVersion = "TFP-API-Version"
|
||||
|
||||
// DefaultAddress of Terraform Enterprise.
|
||||
DefaultAddress = "https://app.terraform.io"
|
||||
// DefaultBasePath on which the API is served.
|
||||
DefaultBasePath = "/api/v2/"
|
||||
// No-op API endpoint used to configure the rate limiter
|
||||
// PingEndpoint is a no-op API endpoint used to configure the rate limiter
|
||||
PingEndpoint = "ping"
|
||||
)
|
||||
|
||||
|
@ -105,6 +106,7 @@ type Client struct {
|
|||
limiter *rate.Limiter
|
||||
retryLogHook RetryLogHook
|
||||
retryServerErrors bool
|
||||
remoteAPIVersion string
|
||||
|
||||
Applies Applies
|
||||
ConfigurationVersions ConfigurationVersions
|
||||
|
@ -113,13 +115,16 @@ type Client struct {
|
|||
OAuthClients OAuthClients
|
||||
OAuthTokens OAuthTokens
|
||||
Organizations Organizations
|
||||
OrganizationMemberships OrganizationMemberships
|
||||
OrganizationTokens OrganizationTokens
|
||||
Plans Plans
|
||||
PlanExports PlanExports
|
||||
Policies Policies
|
||||
PolicyChecks PolicyChecks
|
||||
PolicySetParameters PolicySetParameters
|
||||
PolicySets PolicySets
|
||||
Runs Runs
|
||||
RunTriggers RunTriggers
|
||||
SSHKeys SSHKeys
|
||||
StateVersions StateVersions
|
||||
Teams Teams
|
||||
|
@ -191,11 +196,18 @@ func NewClient(cfg *Config) (*Client, error) {
|
|||
RetryMax: 30,
|
||||
}
|
||||
|
||||
// Configure the rate limiter.
|
||||
if err := client.configureLimiter(); err != nil {
|
||||
meta, err := client.getRawAPIMetadata()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Configure the rate limiter.
|
||||
client.configureLimiter(meta.RateLimit)
|
||||
|
||||
// Save the API version so we can return it from the RemoteAPIVersion
|
||||
// method later.
|
||||
client.remoteAPIVersion = meta.APIVersion
|
||||
|
||||
// Create the services.
|
||||
client.Applies = &applies{client: client}
|
||||
client.ConfigurationVersions = &configurationVersions{client: client}
|
||||
|
@ -204,13 +216,16 @@ func NewClient(cfg *Config) (*Client, error) {
|
|||
client.OAuthClients = &oAuthClients{client: client}
|
||||
client.OAuthTokens = &oAuthTokens{client: client}
|
||||
client.Organizations = &organizations{client: client}
|
||||
client.OrganizationMemberships = &organizationMemberships{client: client}
|
||||
client.OrganizationTokens = &organizationTokens{client: client}
|
||||
client.Plans = &plans{client: client}
|
||||
client.PlanExports = &planExports{client: client}
|
||||
client.Policies = &policies{client: client}
|
||||
client.PolicyChecks = &policyChecks{client: client}
|
||||
client.PolicySetParameters = &policySetParameters{client: client}
|
||||
client.PolicySets = &policySets{client: client}
|
||||
client.Runs = &runs{client: client}
|
||||
client.RunTriggers = &runTriggers{client: client}
|
||||
client.SSHKeys = &sshKeys{client: client}
|
||||
client.StateVersions = &stateVersions{client: client}
|
||||
client.Teams = &teams{client: client}
|
||||
|
@ -224,6 +239,26 @@ func NewClient(cfg *Config) (*Client, error) {
|
|||
return client, nil
|
||||
}
|
||||
|
||||
// RemoteAPIVersion returns the server's declared API version string.
|
||||
//
|
||||
// A Terraform Cloud or Enterprise API server returns its API version in an
|
||||
// HTTP header field in all responses. The NewClient function saves the
|
||||
// version number returned in its initial setup request and RemoteAPIVersion
|
||||
// returns that cached value.
|
||||
//
|
||||
// The API protocol calls for this string to be a dotted-decimal version number
|
||||
// like 2.3.0, where the first number indicates the API major version while the
|
||||
// second indicates a minor version which may have introduced some
|
||||
// backward-compatible additional features compared to its predecessor.
|
||||
//
|
||||
// Explicit API versioning was added to the Terraform Cloud and Enterprise
|
||||
// APIs as a later addition, so older servers will not return version
|
||||
// information. In that case, this function returns an empty string as the
|
||||
// version.
|
||||
func (c *Client) RemoteAPIVersion() string {
|
||||
return c.remoteAPIVersion
|
||||
}
|
||||
|
||||
// RetryServerErrors configures the retry HTTP check to also retry
|
||||
// unexpected errors or requests that failed with a server error.
|
||||
func (c *Client) RetryServerErrors(retry bool) {
|
||||
|
@ -292,16 +327,29 @@ func rateLimitBackoff(min, max time.Duration, attemptNum int, resp *http.Respons
|
|||
return min + jitter
|
||||
}
|
||||
|
||||
// configureLimiter configures the rate limiter.
|
||||
func (c *Client) configureLimiter() error {
|
||||
type rawAPIMetadata struct {
|
||||
// APIVersion is the raw API version string reported by the server in the
|
||||
// TFP-API-Version response header, or an empty string if that header
|
||||
// field was not included in the response.
|
||||
APIVersion string
|
||||
|
||||
// RateLimit is the raw API version string reported by the server in the
|
||||
// X-RateLimit-Limit response header, or an empty string if that header
|
||||
// field was not included in the response.
|
||||
RateLimit string
|
||||
}
|
||||
|
||||
func (c *Client) getRawAPIMetadata() (rawAPIMetadata, error) {
|
||||
var meta rawAPIMetadata
|
||||
|
||||
// Create a new request.
|
||||
u, err := c.baseURL.Parse(PingEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
return meta, err
|
||||
}
|
||||
req, err := http.NewRequest("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return meta, err
|
||||
}
|
||||
|
||||
// Attach the default headers.
|
||||
|
@ -314,15 +362,24 @@ func (c *Client) configureLimiter() error {
|
|||
// Make a single request to retrieve the rate limit headers.
|
||||
resp, err := c.http.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
return meta, err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
meta.APIVersion = resp.Header.Get(headerAPIVersion)
|
||||
meta.RateLimit = resp.Header.Get(headerRateLimit)
|
||||
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
// configureLimiter configures the rate limiter.
|
||||
func (c *Client) configureLimiter(rawLimit string) {
|
||||
|
||||
// Set default values for when rate limiting is disabled.
|
||||
limit := rate.Inf
|
||||
burst := 0
|
||||
|
||||
if v := resp.Header.Get(headerRateLimit); v != "" {
|
||||
if v := rawLimit; v != "" {
|
||||
if rateLimit, _ := strconv.ParseFloat(v, 64); rateLimit > 0 {
|
||||
// Configure the limit and burst using a split of 2/3 for the limit and
|
||||
// 1/3 for the burst. This enables clients to burst 1/3 of the allowed
|
||||
|
@ -336,8 +393,6 @@ func (c *Client) configureLimiter() error {
|
|||
|
||||
// Create a new limiter using the calculated values.
|
||||
c.limiter = rate.NewLimiter(limit, burst)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newRequest creates an API request. A relative URL path can be provided in
|
||||
|
|
|
@ -16,19 +16,19 @@ var _ Variables = (*variables)(nil)
|
|||
// TFE API docs: https://www.terraform.io/docs/enterprise/api/variables.html
|
||||
type Variables interface {
|
||||
// List all the variables associated with the given workspace.
|
||||
List(ctx context.Context, options VariableListOptions) (*VariableList, error)
|
||||
List(ctx context.Context, workspaceID string, options VariableListOptions) (*VariableList, error)
|
||||
|
||||
// Create is used to create a new variable.
|
||||
Create(ctx context.Context, options VariableCreateOptions) (*Variable, error)
|
||||
Create(ctx context.Context, workspaceID string, options VariableCreateOptions) (*Variable, error)
|
||||
|
||||
// Read a variable by its ID.
|
||||
Read(ctx context.Context, variableID string) (*Variable, error)
|
||||
Read(ctx context.Context, workspaceID string, variableID string) (*Variable, error)
|
||||
|
||||
// Update values of an existing variable.
|
||||
Update(ctx context.Context, variableID string, options VariableUpdateOptions) (*Variable, error)
|
||||
Update(ctx context.Context, workspaceID string, variableID string, options VariableUpdateOptions) (*Variable, error)
|
||||
|
||||
// Delete a variable by its ID.
|
||||
Delete(ctx context.Context, variableID string) error
|
||||
Delete(ctx context.Context, workspaceID string, variableID string) error
|
||||
}
|
||||
|
||||
// variables implements Variables.
|
||||
|
@ -42,6 +42,7 @@ type CategoryType string
|
|||
//List all available categories.
|
||||
const (
|
||||
CategoryEnv CategoryType = "env"
|
||||
CategoryPolicySet CategoryType = "policy-set"
|
||||
CategoryTerraform CategoryType = "terraform"
|
||||
)
|
||||
|
||||
|
@ -56,38 +57,28 @@ type Variable struct {
|
|||
ID string `jsonapi:"primary,vars"`
|
||||
Key string `jsonapi:"attr,key"`
|
||||
Value string `jsonapi:"attr,value"`
|
||||
Description string `jsonapi:"attr,description"`
|
||||
Category CategoryType `jsonapi:"attr,category"`
|
||||
HCL bool `jsonapi:"attr,hcl"`
|
||||
Sensitive bool `jsonapi:"attr,sensitive"`
|
||||
|
||||
// Relations
|
||||
Workspace *Workspace `jsonapi:"relation,workspace"`
|
||||
Workspace *Workspace `jsonapi:"relation,configurable"`
|
||||
}
|
||||
|
||||
// VariableListOptions represents the options for listing variables.
|
||||
type VariableListOptions struct {
|
||||
ListOptions
|
||||
Organization *string `url:"filter[organization][name]"`
|
||||
Workspace *string `url:"filter[workspace][name]"`
|
||||
}
|
||||
|
||||
func (o VariableListOptions) valid() error {
|
||||
if !validString(o.Organization) {
|
||||
return errors.New("organization is required")
|
||||
}
|
||||
if !validString(o.Workspace) {
|
||||
return errors.New("workspace is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List all the variables associated with the given workspace.
|
||||
func (s *variables) List(ctx context.Context, options VariableListOptions) (*VariableList, error) {
|
||||
if err := options.valid(); err != nil {
|
||||
return nil, err
|
||||
func (s *variables) List(ctx context.Context, workspaceID string, options VariableListOptions) (*VariableList, error) {
|
||||
if !validStringID(&workspaceID) {
|
||||
return nil, errors.New("invalid value for workspace ID")
|
||||
}
|
||||
|
||||
req, err := s.client.newRequest("GET", "vars", &options)
|
||||
u := fmt.Sprintf("workspaces/%s/vars", workspaceID)
|
||||
req, err := s.client.newRequest("GET", u, &options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -112,6 +103,9 @@ type VariableCreateOptions struct {
|
|||
// The value of the variable.
|
||||
Value *string `jsonapi:"attr,value,omitempty"`
|
||||
|
||||
// The description of the variable.
|
||||
Description *string `jsonapi:"attr,description,omitempty"`
|
||||
|
||||
// Whether this is a Terraform or environment variable.
|
||||
Category *CategoryType `jsonapi:"attr,category"`
|
||||
|
||||
|
@ -120,9 +114,6 @@ type VariableCreateOptions struct {
|
|||
|
||||
// Whether the value is sensitive.
|
||||
Sensitive *bool `jsonapi:"attr,sensitive,omitempty"`
|
||||
|
||||
// The workspace that owns the variable.
|
||||
Workspace *Workspace `jsonapi:"relation,workspace"`
|
||||
}
|
||||
|
||||
func (o VariableCreateOptions) valid() error {
|
||||
|
@ -132,14 +123,14 @@ func (o VariableCreateOptions) valid() error {
|
|||
if o.Category == nil {
|
||||
return errors.New("category is required")
|
||||
}
|
||||
if o.Workspace == nil {
|
||||
return errors.New("workspace is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create is used to create a new variable.
|
||||
func (s *variables) Create(ctx context.Context, options VariableCreateOptions) (*Variable, error) {
|
||||
func (s *variables) Create(ctx context.Context, workspaceID string, options VariableCreateOptions) (*Variable, error) {
|
||||
if !validStringID(&workspaceID) {
|
||||
return nil, errors.New("invalid value for workspace ID")
|
||||
}
|
||||
if err := options.valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -147,7 +138,8 @@ func (s *variables) Create(ctx context.Context, options VariableCreateOptions) (
|
|||
// Make sure we don't send a user provided ID.
|
||||
options.ID = ""
|
||||
|
||||
req, err := s.client.newRequest("POST", "vars", &options)
|
||||
u := fmt.Sprintf("workspaces/%s/vars", url.QueryEscape(workspaceID))
|
||||
req, err := s.client.newRequest("POST", u, &options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -162,12 +154,15 @@ func (s *variables) Create(ctx context.Context, options VariableCreateOptions) (
|
|||
}
|
||||
|
||||
// Read a variable by its ID.
|
||||
func (s *variables) Read(ctx context.Context, variableID string) (*Variable, error) {
|
||||
func (s *variables) Read(ctx context.Context, workspaceID string, variableID string) (*Variable, error) {
|
||||
if !validStringID(&workspaceID) {
|
||||
return nil, errors.New("invalid value for workspace ID")
|
||||
}
|
||||
if !validStringID(&variableID) {
|
||||
return nil, errors.New("invalid value for variable ID")
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("vars/%s", url.QueryEscape(variableID))
|
||||
u := fmt.Sprintf("workspaces/%s/vars/%s", url.QueryEscape(workspaceID), url.QueryEscape(variableID))
|
||||
req, err := s.client.newRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -193,6 +188,9 @@ type VariableUpdateOptions struct {
|
|||
// The value of the variable.
|
||||
Value *string `jsonapi:"attr,value,omitempty"`
|
||||
|
||||
// The description of the variable.
|
||||
Description *string `jsonapi:"attr,description,omitempty"`
|
||||
|
||||
// Whether to evaluate the value of the variable as a string of HCL code.
|
||||
HCL *bool `jsonapi:"attr,hcl,omitempty"`
|
||||
|
||||
|
@ -201,7 +199,10 @@ type VariableUpdateOptions struct {
|
|||
}
|
||||
|
||||
// Update values of an existing variable.
|
||||
func (s *variables) Update(ctx context.Context, variableID string, options VariableUpdateOptions) (*Variable, error) {
|
||||
func (s *variables) Update(ctx context.Context, workspaceID string, variableID string, options VariableUpdateOptions) (*Variable, error) {
|
||||
if !validStringID(&workspaceID) {
|
||||
return nil, errors.New("invalid value for workspace ID")
|
||||
}
|
||||
if !validStringID(&variableID) {
|
||||
return nil, errors.New("invalid value for variable ID")
|
||||
}
|
||||
|
@ -209,7 +210,7 @@ func (s *variables) Update(ctx context.Context, variableID string, options Varia
|
|||
// Make sure we don't send a user provided ID.
|
||||
options.ID = variableID
|
||||
|
||||
u := fmt.Sprintf("vars/%s", url.QueryEscape(variableID))
|
||||
u := fmt.Sprintf("workspaces/%s/vars/%s", url.QueryEscape(workspaceID), url.QueryEscape(variableID))
|
||||
req, err := s.client.newRequest("PATCH", u, &options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -225,12 +226,15 @@ func (s *variables) Update(ctx context.Context, variableID string, options Varia
|
|||
}
|
||||
|
||||
// Delete a variable by its ID.
|
||||
func (s *variables) Delete(ctx context.Context, variableID string) error {
|
||||
func (s *variables) Delete(ctx context.Context, workspaceID string, variableID string) error {
|
||||
if !validStringID(&workspaceID) {
|
||||
return errors.New("invalid value for workspace ID")
|
||||
}
|
||||
if !validStringID(&variableID) {
|
||||
return errors.New("invalid value for variable ID")
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("vars/%s", url.QueryEscape(variableID))
|
||||
u := fmt.Sprintf("workspaces/%s/vars/%s", url.QueryEscape(workspaceID), url.QueryEscape(variableID))
|
||||
req, err := s.client.newRequest("DELETE", u, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -348,7 +348,7 @@ github.com/hashicorp/go-safetemp
|
|||
github.com/hashicorp/go-slug
|
||||
# github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86
|
||||
## explicit
|
||||
# github.com/hashicorp/go-tfe v0.3.27
|
||||
# github.com/hashicorp/go-tfe v0.8.0
|
||||
## explicit
|
||||
github.com/hashicorp/go-tfe
|
||||
# github.com/hashicorp/go-uuid v1.0.1
|
||||
|
|
Loading…
Reference in New Issue