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 support for listing Runs in an organization #1059

Merged
merged 6 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

* Adds `DefaultProject` to `OrganizationUpdateOptions` to support updating an organization's default project. This provides BETA support, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users, by @mkam [#1056](https://github.com/hashicorp/go-tfe/pull/1056)
* Adds `ReadTerraformRegistryModule` to support reading a registry module from Terraform Registry's proxied endpoints by @paladin-devops [#1057](https://github.com/hashicorp/go-tfe/pull/1057)
* Adds a new method `ListForOrganization` to list Runs in an organization by @arybolovlev [#1059](https://github.com/hashicorp/go-tfe/pull/1059)

## Bug fixes

Expand Down
15 changes: 15 additions & 0 deletions mocks/run_mocks.go

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

77 changes: 77 additions & 0 deletions run.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ type Runs interface {
// List all the runs of the given workspace.
List(ctx context.Context, workspaceID string, options *RunListOptions) (*RunList, error)

// List all the runs of the given organization.
ListForOrganization(ctx context.Context, organization string, options *RunListForOrganizationOptions) (*RunList, error)

// Create a new run with the given options.
Create(ctx context.Context, options RunCreateOptions) (*Run, error)

Expand Down Expand Up @@ -250,6 +253,52 @@ type RunListOptions struct {
Include []RunIncludeOpt `url:"include,omitempty"`
}

// RunListForOrganizationOptions represents the options for listing runs for an organization.
type RunListForOrganizationOptions struct {
ListOptions

// Optional: Searches runs that matches the supplied VCS username.
User string `url:"search[user],omitempty"`

// Optional: Searches runs that matches the supplied commit sha.
Commit string `url:"search[commit],omitempty"`

// Optional: Searches for runs that match the VCS username, commit sha, run_id, or run message your specify.
// The presence of search[commit] or search[user] takes priority over this parameter and will be omitted.
Basic string `url:"search[basic],omitempty"`

// Optional: Comma-separated list of acceptable run statuses.
// Options are listed at https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#run-states,
// or as constants with the RunStatus string type.
Status string `url:"filter[status],omitempty"`

// Optional: Comma-separated list of acceptable run sources.
// Options are listed at https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#run-sources,
// or as constants with the RunSource string type.
Source string `url:"filter[source],omitempty"`

// Optional: Comma-separated list of acceptable run operation types.
// Options are listed at https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#run-operations,
// or as constants with the RunOperation string type.
Operation string `url:"filter[operation],omitempty"`

// Optional: Comma-separated list of agent pool names.
AgentPoolNames string `url:"filter[agent_pool_names],omitempty"`

// Optional: Comma-separated list of run status groups.
StatusGroup string `url:"filter[status_group],omitempty"`

// Optional: Comma-separated list of run timeframe.
Timeframe string `url:"filter[timeframe],omitempty"`

// Optional: Comma-separated list of workspace names. The result lists runs that belong to one of the workspaces your specify.
WorkspaceNames string `url:"filter[workspace_names],omitempty"`

// Optional: A list of relations to include. See available resources:
// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run#available-related-resources
Include []RunIncludeOpt `url:"include,omitempty"`
}

// RunReadOptions represents the options for reading a run.
type RunReadOptions struct {
// Optional: A list of relations to include. See available resources:
Expand Down Expand Up @@ -398,6 +447,30 @@ func (s *runs) List(ctx context.Context, workspaceID string, options *RunListOpt
return rl, nil
}

// List all the runs of the given workspace.
func (s *runs) ListForOrganization(ctx context.Context, organization string, options *RunListForOrganizationOptions) (*RunList, error) {
if !validStringID(&organization) {
return nil, ErrInvalidOrg
}
if err := options.valid(); err != nil {
return nil, err
}

u := fmt.Sprintf("organizations/%s/runs", url.PathEscape(organization))
req, err := s.client.NewRequest("GET", u, options)
if err != nil {
return nil, err
}

rl := &RunList{}
err = req.Do(ctx, rl)
if err != nil {
return nil, err
}

return rl, nil
}

// Create a new run with the given options.
func (s *runs) Create(ctx context.Context, options RunCreateOptions) (*Run, error) {
if err := options.valid(); err != nil {
Expand Down Expand Up @@ -546,3 +619,7 @@ func (o *RunReadOptions) valid() error {
func (o *RunListOptions) valid() error {
return nil
}

func (o *RunListForOrganizationOptions) valid() error {
return nil
}
123 changes: 123 additions & 0 deletions run_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -720,3 +720,126 @@ func TestRunCreateOptions_Marshal(t *testing.T) {

assert.Equal(t, string(bodyBytes), expectedBody)
}

func TestRunsListForOrganization(t *testing.T) {
client := testClient(t)
ctx := context.Background()

orgTest, orgTestCleanup := createOrganization(t, client)
defer orgTestCleanup()

apTest, _ := createAgentPool(t, client, orgTest)

wTest, _ := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{
Name: String(randomString(t)),
ExecutionMode: String("agent"),
AgentPoolID: &apTest.ID,
})
rTest1, _ := createRun(t, client, wTest)
rTest2, _ := createRun(t, client, wTest)

t.Run("without list options", func(t *testing.T) {
rl, err := client.Runs.ListForOrganization(ctx, orgTest.Name, nil)
require.NoError(t, err)

found := []string{}
for _, r := range rl.Items {
found = append(found, r.ID)
}

assert.Contains(t, found, rTest1.ID)
assert.Contains(t, found, rTest2.ID)
assert.Equal(t, 1, rl.CurrentPage)
assert.Equal(t, 2, rl.TotalCount)
})

t.Run("without list options and include as nil", func(t *testing.T) {
rl, err := client.Runs.ListForOrganization(ctx, orgTest.Name, &RunListForOrganizationOptions{
Include: []RunIncludeOpt{},
})
require.NoError(t, err)
require.NotEmpty(t, rl.Items)

found := []string{}
for _, r := range rl.Items {
found = append(found, r.ID)
}

assert.Contains(t, found, rTest1.ID)
assert.Contains(t, found, rTest2.ID)
assert.Equal(t, 1, rl.CurrentPage)
assert.Equal(t, 2, rl.TotalCount)
})

t.Run("with list options", func(t *testing.T) {
// Request a page number that is out of range. The result should
// be successful, but return no results if the paging options are
// properly passed along.
rl, err := client.Runs.ListForOrganization(ctx, orgTest.Name, &RunListForOrganizationOptions{
ListOptions: ListOptions{
PageNumber: 999,
PageSize: 100,
},
})
require.NoError(t, err)
assert.Empty(t, rl.Items)
assert.Equal(t, 999, rl.CurrentPage)
assert.Equal(t, 2, rl.TotalCount)
})

t.Run("with workspace included", func(t *testing.T) {
rl, err := client.Runs.ListForOrganization(ctx, orgTest.Name, &RunListForOrganizationOptions{
Include: []RunIncludeOpt{RunWorkspace},
})
require.NoError(t, err)

require.NotEmpty(t, rl.Items)
require.NotNil(t, rl.Items[0].Workspace)
assert.NotEmpty(t, rl.Items[0].Workspace.Name)
})

t.Run("without a valid organization name", func(t *testing.T) {
rl, err := client.Runs.ListForOrganization(ctx, badIdentifier, nil)
assert.Nil(t, rl)
assert.EqualError(t, err, ErrInvalidOrg.Error())
})

t.Run("with filter by agent pool", func(t *testing.T) {
rl, err := client.Runs.ListForOrganization(ctx, orgTest.Name, &RunListForOrganizationOptions{
AgentPoolNames: apTest.Name,
})
require.NoError(t, err)

found := make([]string, len(rl.Items))
for i, r := range rl.Items {
found[i] = r.ID
}

assert.Contains(t, found, rTest1.ID)
assert.Contains(t, found, rTest2.ID)
assert.Equal(t, 1, rl.CurrentPage)
assert.Equal(t, 2, rl.TotalCount)
})

t.Run("with filter by workspace", func(t *testing.T) {
rl, err := client.Runs.ListForOrganization(ctx, orgTest.Name, &RunListForOrganizationOptions{
WorkspaceNames: wTest.Name,
Include: []RunIncludeOpt{RunWorkspace},
})
require.NoError(t, err)

found := make([]string, len(rl.Items))
for i, r := range rl.Items {
found[i] = r.ID
}

assert.Contains(t, found, rTest1.ID)
assert.Contains(t, found, rTest2.ID)
require.NotNil(t, rl.Items[0].Workspace)
assert.NotEmpty(t, rl.Items[0].Workspace.Name)
require.NotNil(t, rl.Items[1].Workspace)
assert.NotEmpty(t, rl.Items[1].Workspace.Name)
assert.Equal(t, 1, rl.CurrentPage)
assert.Equal(t, 2, rl.TotalCount)
})
}
2 changes: 1 addition & 1 deletion test_run_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func TestTestRunsCreate(t *testing.T) {
_, err := client.TestRuns.Create(ctx, options)
require.Equal(t, ErrRequiredRegistryModule, err)
})
t.Run("without an organisation", func(t *testing.T) {
t.Run("without an organization", func(t *testing.T) {
rm := &RegistryModule{
ID: rmTest.ID,
Name: rmTest.Name,
Expand Down
Loading