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

Allow force deleting agent pools #435

Merged
merged 5 commits into from
Nov 6, 2024
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
2 changes: 2 additions & 0 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
### Improvements

- Allow force deleting agent pools. [#435](https://github.com/pulumi/pulumi-pulumiservice/pull/435)

### Bug Fixes

### Miscellaneous
2 changes: 1 addition & 1 deletion examples/ts-webhooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const stackWebhook = new service.Webhook("stack-webhook", {
organizationName: serviceOrg,
projectName: pulumi.getProject(),
stackName: pulumi.getStack(),
payloadUrl: "https://example.com",
payloadUrl: "https://hooks.slack.com/blahblah",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed an update due to new URL validation for webhooks

format: WebhookFormat.Slack,
groups: [ WebhookGroup.Stacks ],
filters: [WebhookFilters.DeploymentStarted, WebhookFilters.DeploymentSucceeded],
Expand Down
8 changes: 8 additions & 0 deletions provider/cmd/pulumi-resource-pulumiservice/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,10 @@
"description": "The agent pool's token's value.",
"type": "string",
"secret": true
},
"forceDestroy": {
"description": "Optional. Flag indicating whether to delete the agent pool even if stacks are configured to use it.",
"type": "boolean"
}
},
"required": [
Expand All @@ -682,6 +686,10 @@
"organizationName": {
"description": "The organization's name.",
"type": "string"
},
"forceDestroy": {
"description": "Optional. Flag indicating whether to delete the agent pool even if stacks are configured to use it.",
"type": "boolean"
}
},
"requiredInputs": [
Expand Down
6 changes: 3 additions & 3 deletions provider/pkg/internal/pulumiapi/accesstokens_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestDeleteAccessToken(t *testing.T) {
ExpectedReqMethod: http.MethodDelete,
ExpectedReqPath: "/api/user/tokens/" + tokenId,
ResponseCode: 404,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
StatusCode: 404,
Message: "token not found",
},
Expand Down Expand Up @@ -75,7 +75,7 @@ func TestCreateAccessToken(t *testing.T) {
Description: desc,
},
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
StatusCode: 401,
Message: "unauthorized",
},
Expand Down Expand Up @@ -131,7 +131,7 @@ func TestGetAccessToken(t *testing.T) {
ExpectedReqPath: "/api/user/tokens",
ExpectedReqBody: nil,
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
StatusCode: 401,
Message: "unauthorized",
},
Expand Down
14 changes: 10 additions & 4 deletions provider/pkg/internal/pulumiapi/agent_pools.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"path"
)

type AgentPoolClient interface {
CreateAgentPool(ctx context.Context, orgName, name, description string) (*AgentPool, error)
UpdateAgentPool(ctx context.Context, agentPoolId, orgName, name, description string) error
DeleteAgentPool(ctx context.Context, agentPoolId, orgName string) error
DeleteAgentPool(ctx context.Context, agentPoolId, orgName string, forceDestroy bool) error
GetAgentPool(ctx context.Context, agentPoolId, orgName string) (*AgentPool, error)
}

Expand Down Expand Up @@ -106,18 +107,23 @@ func (c *Client) UpdateAgentPool(ctx context.Context, agentPoolId, orgName, name
return nil
}

func (c *Client) DeleteAgentPool(ctx context.Context, agentPoolId, orgName string) error {
func (c *Client) DeleteAgentPool(ctx context.Context, agentPoolId, orgName string, forceDestroy bool) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: should we consider making options like this a struct in case we need to add more later?

if len(agentPoolId) == 0 {
return errors.New("agentPoolId length must be greater than zero")
}

if len(orgName) == 0 {
return errors.New("orgname length must be greater than zero")
return errors.New("orgName length must be greater than zero")
}

apiPath := path.Join("orgs", orgName, "agent-pools", agentPoolId)

_, err := c.do(ctx, http.MethodDelete, apiPath, nil, nil)
var err error
if forceDestroy {
_, err = c.doWithQuery(ctx, http.MethodDelete, apiPath, url.Values{"force": []string{"true"}}, nil, nil)
} else {
_, err = c.do(ctx, http.MethodDelete, apiPath, nil, nil)
}
if err != nil {
return fmt.Errorf("failed to delete agent pool %q: %w", agentPoolId, err)
}
Expand Down
10 changes: 5 additions & 5 deletions provider/pkg/internal/pulumiapi/agent_pools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,22 @@ func TestDeleteAgentPool(t *testing.T) {
ResponseCode: 204,
})
defer cleanup()
assert.NoError(t, c.DeleteAgentPool(teamCtx, agentPoolId, orgName))
assert.NoError(t, c.DeleteAgentPool(teamCtx, agentPoolId, orgName, false))
})

t.Run("Error", func(t *testing.T) {
c, cleanup := startTestServer(t, testServerConfig{
ExpectedReqMethod: http.MethodDelete,
ExpectedReqPath: "/api/orgs/anOrg/agent-pools/" + agentPoolId,
ResponseCode: 404,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
StatusCode: 404,
Message: "agent pool not found",
},
})
defer cleanup()
assert.EqualError(t,
c.DeleteAgentPool(teamCtx, agentPoolId, orgName),
c.DeleteAgentPool(teamCtx, agentPoolId, orgName, false),
`failed to delete agent pool "abcdegh": 404 API error: agent pool not found`,
)
})
Expand Down Expand Up @@ -80,7 +80,7 @@ func TestCreateAgentPool(t *testing.T) {
Name: name,
},
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
StatusCode: 401,
Message: "unauthorized",
},
Expand Down Expand Up @@ -129,7 +129,7 @@ func TestGetAgentPool(t *testing.T) {
ExpectedReqPath: fmt.Sprintf("/api/orgs/%s/agent-pools/%s", org, id),
ExpectedReqBody: nil,
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
StatusCode: 401,
Message: "unauthorized",
},
Expand Down
6 changes: 3 additions & 3 deletions provider/pkg/internal/pulumiapi/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (c *Client) createRequest(ctx context.Context, method string, url *url.URL,
}

// sendRequest executes req and unmarshals response json into resBody
// returns attempts to unmarshal response into errorResponse if statusCode not 2XX
// returns attempts to unmarshal response into ErrorResponse if statusCode not 2XX
func (c *Client) sendRequest(req *http.Request, resBody interface{}) (*http.Response, error) {
res, err := c.httpClient.Do(req)
if err != nil {
Expand All @@ -91,9 +91,9 @@ func (c *Client) sendRequest(req *http.Request, resBody interface{}) (*http.Resp
return nil, fmt.Errorf("failed to read response body: %w", err)
}
if !ok(res.StatusCode) {
// if we didn't get an 2XX status code, unmarshal the response as an errorResponse
// if we didn't get an 2XX status code, unmarshal the response as an ErrorResponse
// and return an error
var errRes errorResponse
var errRes ErrorResponse
err = json.Unmarshal(body, &errRes)
if err != nil {
return res, fmt.Errorf("failed to parse response body from url %q, status code %d: %w\n\n%s\n",
Expand Down
2 changes: 1 addition & 1 deletion provider/pkg/internal/pulumiapi/deployment_setting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestGetDeploymentSettings(t *testing.T) {
ExpectedReqMethod: http.MethodGet,
ExpectedReqPath: "/" + path.Join("api", "stacks", orgName, projectName, stackName, "deployments", "settings"),
ResponseCode: 404,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
StatusCode: 404,
Message: "not found",
},
Expand Down
8 changes: 4 additions & 4 deletions provider/pkg/internal/pulumiapi/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ import (
"fmt"
)

// errorResponse is returned from pulumi service api when there's been an error
type errorResponse struct {
// ErrorResponse is returned from pulumi service api when there's been an error
type ErrorResponse struct {
StatusCode int `json:"code"`
Message string `json:"message"`
}

func (err *errorResponse) Error() string {
func (err *ErrorResponse) Error() string {
return fmt.Sprintf("%d API error: %s", err.StatusCode, err.Message)
}

func GetErrorStatusCode(err error) int {
var errResp *errorResponse
var errResp *ErrorResponse
if errors.As(err, &errResp) {
return errResp.StatusCode
}
Expand Down
6 changes: 3 additions & 3 deletions provider/pkg/internal/pulumiapi/members_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestAddMemberToOrg(t *testing.T) {
Role: role,
},
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
Message: "unauthorized",
},
})
Expand Down Expand Up @@ -75,7 +75,7 @@ func TestListOrgMembers(t *testing.T) {
ExpectedReqMethod: http.MethodGet,
ExpectedReqPath: "/api/orgs/an-organization/members",
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
Message: "unauthorized",
},
})
Expand Down Expand Up @@ -105,7 +105,7 @@ func TestDeleteMemberFromOrg(t *testing.T) {
ExpectedReqMethod: http.MethodDelete,
ExpectedReqPath: "/api/orgs/an-organization/members/a-user",
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
Message: "unauthorized",
},
})
Expand Down
6 changes: 3 additions & 3 deletions provider/pkg/internal/pulumiapi/orgtokens_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func TestDeleteOrgAccessToken(t *testing.T) {
ExpectedReqMethod: http.MethodDelete,
ExpectedReqPath: "/api/orgs/anOrg/tokens/" + tokenId,
ResponseCode: 404,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
StatusCode: 404,
Message: "token not found",
},
Expand Down Expand Up @@ -105,7 +105,7 @@ func TestCreateOrgAccessToken(t *testing.T) {
Name: name,
},
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
StatusCode: 401,
Message: "unauthorized",
},
Expand Down Expand Up @@ -162,7 +162,7 @@ func TestGetOrgAccessToken(t *testing.T) {
ExpectedReqPath: fmt.Sprintf("/api/orgs/%s/tokens", org),
ExpectedReqBody: nil,
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
StatusCode: 401,
Message: "unauthorized",
},
Expand Down
18 changes: 9 additions & 9 deletions provider/pkg/internal/pulumiapi/schedules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func TestCreateDeploymentSchedule(t *testing.T) {
ExpectedReqPath: "/api/stacks/org/project/stack/deployments/schedules",
ExpectedReqBody: createDeploymentScheduleReq,
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
Message: "unauthorized",
},
})
Expand Down Expand Up @@ -95,7 +95,7 @@ func TestGetDeploymentSchedule(t *testing.T) {
ExpectedReqMethod: http.MethodGet,
ExpectedReqPath: "/api/stacks/org/project/stack/deployments/schedules/" + testScheduleID,
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
Message: "unauthorized",
},
})
Expand All @@ -110,7 +110,7 @@ func TestGetDeploymentSchedule(t *testing.T) {
ExpectedReqMethod: http.MethodGet,
ExpectedReqPath: "/api/stacks/org/project/stack/deployments/schedules/" + testScheduleID,
ResponseCode: 404,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
StatusCode: 404,
Message: "not found",
},
Expand Down Expand Up @@ -144,7 +144,7 @@ func TestUpdateDeploymentSchedule(t *testing.T) {
ExpectedReqPath: "/api/stacks/org/project/stack/deployments/schedules/" + testScheduleID,
ExpectedReqBody: createDeploymentScheduleReq,
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
Message: "unauthorized",
},
})
Expand Down Expand Up @@ -172,7 +172,7 @@ func TestDeleteSchedule(t *testing.T) {
ExpectedReqMethod: http.MethodDelete,
ExpectedReqPath: "/api/stacks/org/project/stack/deployments/schedules/" + testScheduleID,
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
Message: "unauthorized",
},
})
Expand Down Expand Up @@ -204,7 +204,7 @@ func TestCreateDriftSchedule(t *testing.T) {
ExpectedReqPath: "/api/stacks/org/project/stack/deployments/drift/schedules",
ExpectedReqBody: createDriftScheduleReq,
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
Message: "unauthorized",
},
})
Expand Down Expand Up @@ -237,7 +237,7 @@ func TestUpdateDriftSchedule(t *testing.T) {
ExpectedReqPath: "/api/stacks/org/project/stack/deployments/drift/schedules/" + testScheduleID,
ExpectedReqBody: createDriftScheduleReq,
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
Message: "unauthorized",
},
})
Expand Down Expand Up @@ -270,7 +270,7 @@ func TestCreateTtlSchedule(t *testing.T) {
ExpectedReqPath: "/api/stacks/org/project/stack/deployments/ttl/schedules",
ExpectedReqBody: createTtlScheduleReq,
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
Message: "unauthorized",
},
})
Expand Down Expand Up @@ -303,7 +303,7 @@ func TestUpdateTtlSchedule(t *testing.T) {
ExpectedReqPath: "/api/stacks/org/project/stack/deployments/ttl/schedules/" + testScheduleID,
ExpectedReqBody: createTtlScheduleReq,
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
Message: "unauthorized",
},
})
Expand Down
4 changes: 2 additions & 2 deletions provider/pkg/internal/pulumiapi/stack_tags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestCreateStackTags(t *testing.T) {
ExpectedReqMethod: http.MethodPost,
ExpectedReqPath: fmt.Sprintf("/api/stacks/%s/%s/%s/tags", stackName.OrgName, stackName.ProjectName, stackName.StackName),
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
Message: "unauthorized",
},
})
Expand Down Expand Up @@ -68,7 +68,7 @@ func TestDeleteStackTags(t *testing.T) {
ExpectedReqMethod: http.MethodDelete,
ExpectedReqPath: "/api/stacks/organization/project/stack/tags/tagName",
ResponseCode: 401,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
Message: "unauthorized",
},
})
Expand Down
4 changes: 2 additions & 2 deletions provider/pkg/internal/pulumiapi/stack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestCreateStack(t *testing.T) {
ExpectedReqMethod: http.MethodPost,
ExpectedReqPath: fmt.Sprintf("/api/stacks/%s/%s", s.OrgName, s.ProjectName),
ResponseCode: http.StatusUnauthorized,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
Message: "unauthorized",
},
})
Expand Down Expand Up @@ -76,7 +76,7 @@ func TestDeleteStack(t *testing.T) {
ExpectedReqMethod: http.MethodDelete,
ExpectedReqPath: "/api/stacks/organization/project/stack",
ResponseCode: http.StatusUnauthorized,
ResponseBody: errorResponse{
ResponseBody: ErrorResponse{
Message: "unauthorized",
},
})
Expand Down
Loading