Skip to content

Commit

Permalink
Implement account deletion API endpoint (#54)
Browse files Browse the repository at this point in the history
* Implement account deletion API endpoint

This change to the SandboxAPI, focuses on the implementation of a new
account deletion API endpoint (DeleteAccountHandler).

Steps are:
1. Pre-conditions are met
2. Close the account
3. Delete the sandbox from DynamoDB table
4. Insert line into the resources_events table for logging

New Account Deletion Endpoint: A new DELETE /accounts/{kind}/{account}
endpoint has been added to the sandbox API. This endpoint allows for the
deletion of AWS sandbox accounts, enforcing specific preconditions
before an account can be deleted. These preconditions include:

- the account being marked for cleanup
- at least three cleanup attempts of the account have been tried
- ensuring that no cleanup process is currently in progress

Modifications to `accounts.go` in the internal/dynamodb package include the introduction of a Delete function, which encapsulates the logic for deleting an account from DynamoDB. This function is called post-validation in the DeleteAccountHandler. Additionally, the AwsAccountDynamoDB struct has been updated to include a new ConanCleanupCount field, reflecting the number of cleanup attempts for an account.

Model Updates: The aws_account.go file in the internal/models package has been updated to include logic for closing an AWS account via the AWS Organizations API, represented by the CloseAccount function. This addition supports the deletion process by ensuring that the account is properly closed before its deletion from the DynamoDB table.

Also:
- Swagger Documentation: Updates to the Swagger
documentation (swagger.yaml) to add the new endpoint
- Go Module and Sum File Updates
- migrate script: fix condition to work with failed cleanup

* Update go modules
  • Loading branch information
fridim authored Feb 18, 2024
1 parent 572d1cd commit 6e1a976
Show file tree
Hide file tree
Showing 11 changed files with 442 additions and 206 deletions.
116 changes: 116 additions & 0 deletions cmd/sandbox-api/account_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"context"

"github.com/jackc/pgx/v4"
"github.com/rhpds/sandbox/internal/api/v1"
Expand Down Expand Up @@ -305,3 +306,118 @@ func (h *BaseHandler) GetStatusAccountHandler(w http.ResponseWriter, r *http.Req
return
}
}

// DeleteAccountHandler deletes an account
// DELETE /accounts/{kind}/{account}
func (h *BaseHandler) DeleteAccountHandler(w http.ResponseWriter, r *http.Request) {
// Grab the parameters from Params
accountName := chi.URLParam(r, "account")
kind := chi.URLParam(r, "kind")

switch kind {
case "AwsSandbox", "AwsAccount", "aws_account":
// Get the account from DynamoDB
sandbox, err := h.awsAccountProvider.FetchByName(accountName)
if err != nil {
if err == models.ErrAccountNotFound {
log.Logger.Warn("DELETE account", "error", err)
w.WriteHeader(http.StatusNotFound)
render.Render(w, r, &v1.Error{
HTTPStatusCode: http.StatusNotFound,
Message: "Account not found",
})
return
}
log.Logger.Error("DELETE account", "error", err)

w.WriteHeader(http.StatusInternalServerError)
render.Render(w, r, &v1.Error{
HTTPStatusCode: 500,
Message: "Error reading account",
})
return
}

// Ensure:
// - the account is marked for cleanup
// - cleanup was attempted at least 3 times
// - cleanup is not in progress
if sandbox.ToCleanup == false {
w.WriteHeader(http.StatusConflict)
render.Render(w, r, &v1.Error{
HTTPStatusCode: http.StatusConflict,
Message: "Account must be marked for cleanup before deletion",
})
return
}

if sandbox.ConanCleanupCount < 3 {
w.WriteHeader(http.StatusConflict)
render.Render(w, r, &v1.Error{
HTTPStatusCode: http.StatusConflict,
Message: "Cleanup must be attempted at least 3 times before deletion",
})
return
}

if sandbox.ConanStatus == "cleanup in progress" {
w.WriteHeader(http.StatusConflict)
render.Render(w, r, &v1.Error{
HTTPStatusCode: http.StatusConflict,
Message: "Cleanup is in progress",
})
return
}


// Close the AWS account using CloseAccount
if err := sandbox.CloseAccount(); err != nil {
log.Logger.Error("Error closing account", "error", err)
w.WriteHeader(http.StatusInternalServerError)
render.Render(w, r, &v1.Error{
HTTPStatusCode: 500,
Message: "Error closing account",
ErrorText: err.Error(),
})

return
}

err = h.awsAccountProvider.Delete(sandbox.Name)
if err != nil {
log.Logger.Error("Error deleting account", "error", err)

w.WriteHeader(http.StatusInternalServerError)
render.Render(w, r, &v1.Error{
HTTPStatusCode: 500,
Message: "Error deleting account",
ErrorText: err.Error(),
})

return
}
// Insert a line into the resources_events table
// here is the table schema for reference
_, err = h.dbpool.Exec(
context.TODO(),
`INSERT INTO resources_events (resource_name, resource_type, event_type, annotations)
VALUES ($1, $2, $3, $4)`,
sandbox.Name, sandbox.Kind, "close_account", nil,
)
if err != nil {
log.Logger.Error("Error inserting into resources_events", "error", err)
}

w.WriteHeader(http.StatusOK)
render.Render(w, r, &v1.SimpleMessage{
Message: "Account deleted",
})
return
default:
w.WriteHeader(http.StatusNotFound)
render.Render(w, r, &v1.Error{
HTTPStatusCode: http.StatusNotFound,
Message: "Account kind not found",
})
}
}
1 change: 1 addition & 0 deletions cmd/sandbox-api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ func main() {
r.Put("/api/v1/accounts/{kind}/{account}/start", baseHandler.LifeCycleAccountHandler("start"))
r.Put("/api/v1/accounts/{kind}/{account}/status", baseHandler.LifeCycleAccountHandler("status"))
r.Get("/api/v1/accounts/{kind}/{account}/status", baseHandler.GetStatusAccountHandler)
r.Delete("/api/v1/accounts/{kind}/{account}", baseHandler.DeleteAccountHandler)
r.Post("/api/v1/placements", baseHandler.CreatePlacementHandler)
r.Get("/api/v1/placements/{uuid}", baseHandler.GetPlacementHandler)
r.Delete("/api/v1/placements/{uuid}", baseHandler.DeletePlacementHandler)
Expand Down
44 changes: 44 additions & 0 deletions docs/api-reference/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,50 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/Error"
delete:
tags:
- account
operationId: deleteAccount
summary: Delete an account
description: |-
Delete an account by its name.
The account will be close and removed from the database.
responses:
'200':
description: The account deletion request is accepted and successful
content:
application/json:
schema:
$ref: "#/components/schemas/Message"
'404':
description: Account not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
'409':
description: Account is not deletable
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
'500':
description: Internal error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
message: 'Error deleting account'
http_code: 500
code: '500'
default:
description: deleteAccount unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"

/accounts/{kind}/{name}/cleanup:
parameters:
Expand Down
88 changes: 43 additions & 45 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,73 +3,71 @@ module github.com/rhpds/sandbox
go 1.21

require (
github.com/aws/aws-lambda-go v1.40.0
github.com/aws/aws-sdk-go v1.44.248
github.com/aws/aws-sdk-go-v2 v1.18.0
github.com/aws/aws-sdk-go-v2/config v1.18.25
github.com/aws/aws-sdk-go-v2/credentials v1.13.24
github.com/aws/aws-sdk-go-v2/service/ec2 v1.98.0
github.com/aws/aws-sdk-go-v2/service/sts v1.19.0
github.com/getkin/kin-openapi v0.116.0
github.com/go-chi/chi/v5 v5.0.10
github.com/go-chi/httplog/v2 v2.0.7
github.com/go-chi/jwtauth/v5 v5.1.0
github.com/go-chi/render v1.0.2
github.com/aws/aws-lambda-go v1.46.0
github.com/aws/aws-sdk-go v1.50.20
github.com/aws/aws-sdk-go-v2 v1.25.0
github.com/aws/aws-sdk-go-v2/config v1.27.0
github.com/aws/aws-sdk-go-v2/credentials v1.17.0
github.com/aws/aws-sdk-go-v2/service/ec2 v1.148.0
github.com/aws/aws-sdk-go-v2/service/organizations v1.24.1
github.com/aws/aws-sdk-go-v2/service/sts v1.27.0
github.com/getkin/kin-openapi v0.123.0
github.com/go-chi/chi/v5 v5.0.12
github.com/go-chi/httplog/v2 v2.0.9
github.com/go-chi/jwtauth/v5 v5.3.0
github.com/go-chi/render v1.0.3
github.com/jackc/pgx/v4 v4.18.1
github.com/lestrrat-go/jwx/v2 v2.0.9
github.com/lestrrat-go/jwx/v2 v2.0.19
github.com/matoous/go-nanoid/v2 v2.0.0
github.com/prometheus/client_golang v1.15.0
github.com/prometheus/client_golang v1.18.0
github.com/sosedoff/ansible-vault-go v0.2.0
golang.org/x/exp v0.0.0-20230420155640-133eef4313cb
golang.org/x/term v0.7.0
golang.org/x/term v0.17.0
)

require (
github.com/ajg/form v1.5.1 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.10 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.19.0 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.22.0 // indirect
github.com/aws/smithy-go v1.20.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/swag v0.19.5 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/go-openapi/jsonpointer v0.20.2 // indirect
github.com/go-openapi/swag v0.22.9 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/invopop/yaml v0.1.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/invopop/yaml v0.2.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.14.0 // indirect
github.com/jackc/pgconn v1.14.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.2 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
github.com/jackc/pgtype v1.14.2 // indirect
github.com/jackc/puddle v1.3.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.4 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/perimeterx/marshmallow v1.1.4 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.47.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading

0 comments on commit 6e1a976

Please sign in to comment.