Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CRUD support for Projects API #564

Merged
merged 10 commits into from Nov 29, 2022
6 changes: 6 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,9 @@
# Unreleased

## Enhancements

* Add `Projects` interface with `list`, `read`, `update`, `delete` methods and integration tests by @hs26gill and @mwudka [#564](https://github.com/hashicorp/go-tfe/pull/564)

# v1.13.0

## Bug Fixes
Expand Down
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -93,6 +93,7 @@ This API client covers most of the existing Terraform Cloud API calls and is upd
- [x] Providers
- [x] Provider Provider Versions and Platforms
- [x] GPG Keys
- [x] Projects
- [x] Runs
- [x] Run Tasks
- [ ] Run Tasks Integration
Expand Down
4 changes: 4 additions & 0 deletions errors.go
Expand Up @@ -82,6 +82,10 @@ var (

ErrInvalidRunID = errors.New("invalid value for run ID")

ErrInvalidProjectID = errors.New("invalid value for project ID")

ErrInvalidPagination = errors.New("invalid value for page size or number")

ErrInvalidRunTaskCategory = errors.New(`category must be "task"`)

ErrInvalidRunTaskID = errors.New("invalid value for run task ID")
Expand Down
1 change: 1 addition & 0 deletions generate_mocks.sh
Expand Up @@ -62,3 +62,4 @@ mockgen -source=workspace.go -destination=mocks/workspace_mocks.go -package=mock
mockgen -source=workspace_run_task.go -destination=mocks/workspace_run_tasks_mocks.go -package=mocks
mockgen -source=agent.go -destination=mocks/agents.go -package=mocks
mockgen -source=policy_evaluation.go -destination=mocks/policy_evaluation.go -package=mocks
mockgen -source=project.go -destination=mocks/project_mocks.go -package=mocks
46 changes: 46 additions & 0 deletions helper_test.go
Expand Up @@ -1975,6 +1975,34 @@ func upgradeOrganizationSubscription(t *testing.T, client *Client, organization
}
}

func createProject(t *testing.T, client *Client, org *Organization) (*Project, func()) {
var orgCleanup func()

if org == nil {
org, orgCleanup = createOrganization(t, client)
}

ctx := context.Background()
p, err := client.Projects.Create(ctx, org.Name, ProjectCreateOptions{
Name: randomStringWithoutSpecialChar(t),
})
if err != nil {
t.Fatal(err)
}

return p, func() {
if err := client.Projects.Delete(ctx, p.ID); err != nil {
t.Logf("Error destroying project! WARNING: Dangling resources "+
"may exist! The full error is shown below.\n\n"+
"Project ID: %s\nError: %s", p.ID, err)
}

if orgCleanup != nil {
orgCleanup()
}
}
}

func waitForSVOutputs(t *testing.T, client *Client, svID string) {
t.Helper()

Expand Down Expand Up @@ -2079,6 +2107,24 @@ func randomString(t *testing.T) string {
return v
}

func randomStringWithoutSpecialChar(t *testing.T) string {
v, err := uuid.GenerateUUID()
if err != nil {
t.Fatal(err)
}
uuidWithoutHyphens := strings.Replace(v, "-", "", -1)
return uuidWithoutHyphens
}

func containsProject(pl []*Project, str string) bool {
for _, p := range pl {
if p.Name == str {
return true
}
}
return false
}

func randomSemver(t *testing.T) string {
return fmt.Sprintf("%d.%d.%d", rand.Intn(99)+3, rand.Intn(99)+1, rand.Intn(99)+1)
}
Expand Down
15 changes: 15 additions & 0 deletions mocks/organization_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

110 changes: 110 additions & 0 deletions mocks/project_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 28 additions & 1 deletion organization.go
Expand Up @@ -25,6 +25,9 @@ type Organizations interface {
// Read an organization by its name.
Read(ctx context.Context, organization string) (*Organization, error)

// Read an organization by its name with options
ReadWithOptions(ctx context.Context, organization string, options OrganizationReadOptions) (*Organization, error)

// Update attributes of an existing organization.
Update(ctx context.Context, organization string, options OrganizationUpdateOptions) (*Organization, error)

Expand Down Expand Up @@ -81,6 +84,25 @@ type Organization struct {
// Note: This will be false for TFE versions older than v202211, where the setting was introduced.
// On those TFE versions, safe delete does not exist, so ALL deletes will be force deletes.
AllowForceDeleteWorkspaces bool `jsonapi:"attr,allow-force-delete-workspaces"`

// Relations
DefaultProject *Project `jsonapi:"relation,default-project"`
}


// OrganizationIncludeOpt represents the available options for include query params.
// https://www.terraform.io/cloud-docs/api-docs/organizations#available-related-resources
type OrganizationIncludeOpt string

const (
OrganizationDefaultProject OrganizationIncludeOpt = "default-project"
)

// OrganizationReadOptions represents the options for reading organizations.
type OrganizationReadOptions struct {
// Optional: A list of relations to include. See available resources
// https://www.terraform.io/cloud-docs/api-docs/organizations#available-related-resources
Include []OrganizationIncludeOpt `url:"include,omitempty"`
}

// Capacity represents the current run capacity of an organization.
Expand Down Expand Up @@ -256,12 +278,17 @@ func (s *organizations) Create(ctx context.Context, options OrganizationCreateOp

// Read an organization by its name.
func (s *organizations) Read(ctx context.Context, organization string) (*Organization, error) {
return s.ReadWithOptions(ctx, organization, OrganizationReadOptions{})
}

// Read an organization by its name with options
func (s *organizations) ReadWithOptions(ctx context.Context, organization string, options OrganizationReadOptions) (*Organization, error) {
if !validStringID(&organization) {
return nil, ErrInvalidOrg
}

u := fmt.Sprintf("organizations/%s", url.QueryEscape(organization))
req, err := s.client.NewRequest("GET", u, nil)
req, err := s.client.NewRequest("GET", u, &options)
if err != nil {
return nil, err
}
Expand Down
11 changes: 11 additions & 0 deletions organization_integration_test.go
Expand Up @@ -180,6 +180,17 @@ func TestOrganizationsRead(t *testing.T) {
_, err := client.Organizations.Read(ctx, randomString(t))
assert.Error(t, err)
})

t.Run("reads default project", func(t *testing.T) {
skipIfBeta(t)

org, err := client.Organizations.ReadWithOptions(ctx, orgTest.Name, OrganizationReadOptions{Include: []OrganizationIncludeOpt{OrganizationDefaultProject}})
require.NoError(t, err)
assert.Equal(t, orgTest.Name, org.Name)

require.NotNil(t, org.DefaultProject)
assert.NotNil(t, org.DefaultProject.Name)
})
}

func TestOrganizationsUpdate(t *testing.T) {
Expand Down