Skip to content

Commit

Permalink
Merge pull request #564 from hashicorp/hs26gill/add-projects-api-support
Browse files Browse the repository at this point in the history
Add CRUD support for Projects API
  • Loading branch information
hs26gill committed Nov 29, 2022
2 parents c24d0fa + 713910c commit 1a66ad2
Show file tree
Hide file tree
Showing 14 changed files with 777 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -3,6 +3,7 @@
## Bug Fixes

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

# v1.14.0

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 @@ -1979,6 +1979,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 @@ -2083,6 +2111,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.

30 changes: 29 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,26 @@ 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
// **Note: This field is still in BETA and subject to change.**
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 (
// **Note: This include option is still in BETA and subject to change.**
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 +279,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

0 comments on commit 1a66ad2

Please sign in to comment.