Skip to content
Open
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
29 changes: 29 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,35 @@ func (c *Client) ListRunnerScaleSets(ctx context.Context, runnerGroupID int) ([]
return list.RunnerScaleSets, nil
}

// GetAcquirableJobs returns jobs that are currently available for acquisition by a runner scale set.
func (c *Client) GetAcquirableJobs(ctx context.Context, runnerScaleSetID int) ([]*JobAvailable, error) {
path := fmt.Sprintf("/%s/%d/acquirablejobs", scaleSetEndpoint, runnerScaleSetID)
req, err := c.newActionsServiceRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, fmt.Errorf("failed to create new actions service request: %w", err)
}

resp, err := c.do(req)
if err != nil {
return nil, fmt.Errorf("failed to issue the request: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode == http.StatusNoContent {
return []*JobAvailable{}, nil
}
if resp.StatusCode != http.StatusOK {
return nil, newRequestResponseError(req, resp, fmt.Errorf("unexpected status code: %d", resp.StatusCode))
}

var list acquirableJobsResponse
if err := json.NewDecoder(resp.Body).Decode(&list); err != nil {
return nil, newRequestResponseError(req, resp, fmt.Errorf("failed to decode acquirable jobs: %w", err))
}

return list.Jobs, nil
}

// GetRunnerScaleSetByID fetches a runner scale set by its ID.
func (c *Client) GetRunnerScaleSetByID(ctx context.Context, runnerScaleSetID int) (*RunnerScaleSet, error) {
path := fmt.Sprintf("/%s/%d", scaleSetEndpoint, runnerScaleSetID)
Expand Down
69 changes: 69 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1617,3 +1617,72 @@ func TestListRunnerScaleSets(t *testing.T) {
assert.Equal(t, "alpha", got[0].Name)
assert.Equal(t, "beta", got[1].Name)
}

func TestGetAcquirableJobs(t *testing.T) {
ctx := context.Background()
auth := actionsAuth{token: "token"}

t.Run("returns jobs using actions admin authentication", func(t *testing.T) {
var server *actionsServer
server = newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodGet, r.Method)
assert.Equal(t, "/tenant/123/_apis/runtime/runnerscalesets/7/acquirablejobs", r.URL.Path)
assert.Equal(t, "6.0-preview", r.URL.Query().Get("api-version"))
assert.Equal(t, "Bearer "+server.token, r.Header.Get("Authorization"))
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(acquirableJobsResponse{
Count: 2,
Jobs: []*JobAvailable{
{JobMessageBase: JobMessageBase{RunnerRequestID: 101, OwnerName: "actions", RepositoryName: "scaleset", JobDisplayName: "unit"}},
{JobMessageBase: JobMessageBase{RunnerRequestID: 102, OwnerName: "actions", RepositoryName: "scaleset", JobDisplayName: "build"}},
},
})
}))
client, err := newClient(testSystemInfo, server.configURLForOrg("my-org"), auth)
require.NoError(t, err)

got, err := client.GetAcquirableJobs(ctx, 7)
require.NoError(t, err)
require.Len(t, got, 2)
assert.EqualValues(t, 101, got[0].RunnerRequestID)
assert.Equal(t, "unit", got[0].JobDisplayName)
assert.EqualValues(t, 102, got[1].RunnerRequestID)
})

t.Run("no content returns an empty list", func(t *testing.T) {
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
client, err := newClient(testSystemInfo, server.configURLForOrg("my-org"), auth)
require.NoError(t, err)

got, err := client.GetAcquirableJobs(ctx, 7)
require.NoError(t, err)
require.Empty(t, got)
})

t.Run("non-success response is returned as an error", func(t *testing.T) {
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusBadRequest)
}))
client, err := newClient(testSystemInfo, server.configURLForOrg("my-org"), auth)
require.NoError(t, err)

_, err = client.GetAcquirableJobs(ctx, 7)
require.Error(t, err)
assert.Contains(t, err.Error(), "unexpected status code: 400")
})

t.Run("invalid response is returned as an error", func(t *testing.T) {
server := newActionsServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte("{"))
}))
client, err := newClient(testSystemInfo, server.configURLForOrg("my-org"), auth)
require.NoError(t, err)

_, err = client.GetAcquirableJobs(ctx, 7)
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to decode acquirable jobs")
})
}
5 changes: 5 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ type acquireJobsResponse struct {
Value []int64 `json:"value"`
}

type acquirableJobsResponse struct {
Count int `json:"count"`
Jobs []*JobAvailable `json:"value"`
}

type RunnerScaleSetSession struct {
SessionID uuid.UUID `json:"sessionId,omitempty"`
OwnerName string `json:"ownerName,omitempty"`
Expand Down