Skip to content

Commit

Permalink
errors: better error messages when aws credentials are missing
Browse files Browse the repository at this point in the history
  • Loading branch information
thdxr committed Oct 28, 2024
1 parent 13d068a commit 9ec1562
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 27 deletions.
3 changes: 3 additions & 0 deletions cmd/sst/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ func main() {
msg := readableErr.Error()
if msg != "" {
ui.Error(readableErr.Error())
if readableErr.IsHinted() {
fmt.Println(" " + ui.TEXT_DIM.Render(readableErr.Unwrap().Error()))
}
}
} else {
slog.Error("exited with error", "err", err)
Expand Down
77 changes: 51 additions & 26 deletions cmd/sst/mosaic/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package errors

import (
"errors"
"strings"

"github.com/sst/ion/cmd/sst/mosaic/aws"
"github.com/sst/ion/internal/util"
Expand All @@ -10,38 +11,62 @@ import (
"github.com/sst/ion/pkg/server"
)

func Transform(err error) error {
mapping := map[error]string{
project.ErrInvalidStageName: "The stage name is invalid. It can only contain alphanumeric characters and hyphens.",
project.ErrInvalidAppName: "The app name is invalid. It can only contain alphanumeric characters and hyphens.",
project.ErrV2Config: "You are using sst ion and this looks like an sst v2 config",
project.ErrStageNotFound: "Stage not found",
project.ErrPassphraseInvalid: "The passphrase for this app / stage is missing or invalid",
aws.ErrIoTDelay: "This aws account has not had iot initialized in it before which sst depends on. It may take a few minutes before it is ready.",
project.ErrStackRunFailed: "",
provider.ErrLockExists: "",
project.ErrVersionInvalid: "The version range defined in the config is invalid",
provider.ErrCloudflareMissingAccount: "The Cloudflare Account ID was not able to be determined from this token. Make sure it has permissions to fetch account information or you can set the CLOUDFLARE_DEFAULT_ACCOUNT_ID environment variable to the account id you want to use.",
server.ErrServerNotFound: "You are currently trying to run a frontend or some other process on its own - starting from v3 `sst dev` can bring up all of the processes in your application in a single window. Simply run `sst dev` in the same directory as your `sst.config.ts`. If this is not clear check out the monorepo example here: https://github.com/sst/ion/tree/dev/examples/aws-monorepo\n\n If you prefer running your processes in different terminal windows, you can start just the deploy process by running `sst dev --mode=basic` and then bring up your process with `sst dev -- <command>` in another terminal window.",
provider.ErrBucketMissing: "The state bucket is missing, it may have been accidentally deleted. Go to https://console.aws.amazon.com/systems-manager/parameters/%252Fsst%252Fbootstrap/description?tab=Table and check if the state bucket mentioned there exists. If it doesn't you can recreate it or delete the `/sst/bootstrap` key to force recreation.",
}
type ErrorTransformer = func(err error) (bool, error)

readable := []error{
project.ErrBuildFailed,
project.ErrVersionMismatch,
}
var transformers = []ErrorTransformer{
exact(project.ErrInvalidStageName, "The stage name is invalid. It can only contain alphanumeric characters and hyphens."),
exact(project.ErrInvalidAppName, "The app name is invalid. It can only contain alphanumeric characters and hyphens."),
exact(project.ErrV2Config, "You are using sst ion and this looks like an sst v2 config"),
exact(project.ErrStageNotFound, "Stage not found"),
exact(project.ErrPassphraseInvalid, "The passphrase for this app / stage is missing or invalid"),
exact(aws.ErrIoTDelay, "This aws account has not had iot initialized in it before which sst depends on. It may take a few minutes before it is ready."),
exact(project.ErrStackRunFailed, ""),
exact(provider.ErrLockExists, ""),
exact(project.ErrVersionInvalid, "The version range defined in the config is invalid"),
exact(provider.ErrCloudflareMissingAccount, "The Cloudflare Account ID was not able to be determined from this token. Make sure it has permissions to fetch account information or you can set the CLOUDFLARE_DEFAULT_ACCOUNT_ID environment variable to the account id you want to use."),
exact(server.ErrServerNotFound, "You are currently trying to run a frontend or some other process on its own - starting from v3 `sst dev` can bring up all of the processes in your application in a single window. Simply run `sst dev` in the same directory as your `sst.config.ts`. If this is not clear check out the monorepo example here: https://github.com/sst/ion/tree/dev/examples/aws-monorepo\n\n If you prefer running your processes in different terminal windows, you can start just the deploy process by running `sst dev --mode=basic` and then bring up your process with `sst dev -- <command>` in another terminal window."),
exact(provider.ErrBucketMissing, "The state bucket is missing, it may have been accidentally deleted. Go to https://console.aws.amazon.com/systems-manager/parameters/%252Fsst%252Fbootstrap/description?tab=Table and check if the state bucket mentioned there exists. If it doesn't you can recreate it or delete the `/sst/bootstrap` key to force recreation."),
exact(project.ErrBuildFailed, project.ErrBuildFailed.Error()),
exact(project.ErrVersionMismatch, project.ErrVersionMismatch.Error()),
func(err error) (bool, error) {
msg := err.Error()
if !strings.HasPrefix(msg, "aws:") {
return false, nil
}
if strings.Contains(msg, "cached SSO token is expired") {
return true, util.NewHintedError(err, "It looks like you are using AWS SSO but your credentials have expired. Try running `aws sso login` to refresh your credentials.")
}
if strings.Contains(msg, "no EC2 IMDS role found") {
return true, util.NewHintedError(err, "AWS credentials are not configured. Try configuring your profile in `~/.aws/config` and setting the `AWS_PROFILE` environment variable or specifying `providers.aws.profile` in your sst.config.ts")
}
return false, nil
},
}

for compare, msg := range mapping {
if errors.Is(err, compare) || err == compare {
return util.NewReadableError(err, msg)
func Transform(err error) error {
for _, t := range transformers {
if ok, err := t(err); ok {
return err
}
}
return err
}

for _, r := range readable {
if errors.Is(err, r) {
return util.NewReadableError(err, err.Error())
func match[T error](transformer func(T) error) ErrorTransformer {
return func(err error) (bool, error) {
var match T
if errors.As(err, &match) {
return true, transformer(match)
}
return false, nil
}
}

return err
func exact(compare error, msg string) ErrorTransformer {
return func(err error) (bool, error) {
if errors.Is(err, compare) {
return true, util.NewReadableError(err, msg)
}
return false, nil
}
}
9 changes: 9 additions & 0 deletions internal/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@ func RandomString(length int) string {
type ReadableError struct {
message string
error error
hinted bool
}

func NewReadableError(err error, message string) *ReadableError {
return &ReadableError{message: message, error: err}
}

func NewHintedError(err error, message string) *ReadableError {
return &ReadableError{message: message, error: err, hinted: true}
}

func (e *ReadableError) Error() string {
return e.message
}
Expand All @@ -34,6 +39,10 @@ func (e *ReadableError) Unwrap() error {
return e.error
}

func (e *ReadableError) IsHinted() bool {
return e.hinted
}

type CleanupFunc func() error

type KeyLock struct {
Expand Down
2 changes: 1 addition & 1 deletion pkg/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ func (proj *Project) LoadHome() error {
}
err := match.Init(proj.app.Name, proj.app.Stage, args.(map[string]interface{}))
if err != nil {
return util.NewReadableError(err, err.Error())
return util.NewReadableError(err, key+": "+err.Error())
}
env, err := match.Env()
if err != nil {
Expand Down

0 comments on commit 9ec1562

Please sign in to comment.