Skip to content

Commit

Permalink
atlasexec: decode the stdout when got error
Browse files Browse the repository at this point in the history
  • Loading branch information
giautm committed Sep 12, 2024
1 parent d960a25 commit b13dd7f
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 34 deletions.
59 changes: 31 additions & 28 deletions atlasexec/atlas.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,36 +248,18 @@ func (c *Client) runCommand(ctx context.Context, args []string) (io.Reader, erro
cmd.Stderr = &stderr
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
cerr := &Error{
return nil, &Error{
err: err,
Stderr: strings.TrimSpace(stderr.String()),
Stdout: strings.TrimSpace(stdout.String()),
}
if exitErr := (&exec.ExitError{}); errors.As(err, &exitErr) {
cerr.err = exitErr
}
return nil, cerr
}
return &stdout, nil
}

// TempFile creates a temporary file with the given content and extension.
func TempFile(content, ext string) (string, func() error, error) {
f, err := os.CreateTemp("", "atlasexec-*."+ext)
if err != nil {
return "", nil, err
}
defer f.Close()
_, err = f.WriteString(content)
if err != nil {
return "", nil, err
}
return fmt.Sprintf("file://%s", f.Name()), func() error {
return os.Remove(f.Name())
}, nil
}

// Error is an error returned by the atlasexec package.
type Error struct {
err *exec.ExitError
err error
Stdout string
Stderr string
}
Expand All @@ -291,17 +273,37 @@ func (e *Error) Error() string {
}

// ExitCode returns the exit code of the command.
// If the error is not an exec.ExitError, it returns 1.
func (e *Error) ExitCode() int {
if e.err == nil {
return new(exec.ExitError).ExitCode()
if err, ok := e.err.(*exec.ExitError); ok {
return err.ExitCode()
}
return e.err.ExitCode()
// Not an exec.ExitError or nil.
// Return the system default exit code.
return new(exec.ExitError).ExitCode()
}

// Unwrap returns the underlying error.
func (e *Error) Unwrap() error {
return e.err
}

// TempFile creates a temporary file with the given content and extension.
func TempFile(content, ext string) (string, func() error, error) {
f, err := os.CreateTemp("", "atlasexec-*."+ext)
if err != nil {
return "", nil, err
}
defer f.Close()
_, err = f.WriteString(content)
if err != nil {
return "", nil, err
}
return fmt.Sprintf("file://%s", f.Name()), func() error {
return os.Remove(f.Name())
}, nil
}

// AsArgs returns the variables as arguments.
func (v Vars2) AsArgs() []string {
keys := make([]string, 0, len(v))
Expand Down Expand Up @@ -364,18 +366,19 @@ func jsonDecode[T any](r io.Reader, err error) ([]*T, error) {
default:
return nil, &Error{
Stdout: string(buf),
err: fmt.Errorf("decoding JSON: %w", err),
}
}
}
}

func jsonDecodeErr[T any](fn func([]*T) error) func(io.Reader, error) ([]*T, error) {
func jsonDecodeErr[T any](fn func([]*T, string) error) func(io.Reader, error) ([]*T, error) {
return func(r io.Reader, err error) ([]*T, error) {
if err != nil {
if cliErr := (&Error{}); errors.As(err, &cliErr) && cliErr.Stderr == "" {
if cliErr := (&Error{}); errors.As(err, &cliErr) && cliErr.Stdout != "" {
d, err := jsonDecode[T](strings.NewReader(cliErr.Stdout), nil)
if err == nil {
return nil, fn(d)
return nil, fn(d, cliErr.Stderr)
}
// If the error is not a JSON, return the original error.
}
Expand Down
12 changes: 9 additions & 3 deletions atlasexec/atlas_migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type (
// during a migration applying attempt.
MigrateApplyError struct {
Result []*MigrateApply
Stderr string
}
// MigrateExecOrder define how Atlas computes and executes pending migration files to the database.
// See: https://atlasgo.io/versioned/apply#execution-order
Expand Down Expand Up @@ -569,12 +570,17 @@ func (r MigrateStatus) Amount(version string) (amount uint64, ok bool) {
return amount, false
}

func newMigrateApplyError(r []*MigrateApply) error {
return &MigrateApplyError{Result: r}
func newMigrateApplyError(r []*MigrateApply, stderr string) error {
return &MigrateApplyError{Result: r, Stderr: stderr}
}

// Error implements the error interface.
func (e *MigrateApplyError) Error() string { return last(e.Result).Error }
func (e *MigrateApplyError) Error() string {
if e.Stderr != "" {
return e.Stderr
}
return last(e.Result).Error
}

func plural(n int) (s string) {
if n > 1 {
Expand Down
12 changes: 9 additions & 3 deletions atlasexec/atlas_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type (
// during a schema applying attempt.
SchemaApplyError struct {
Result []*SchemaApply
Stderr string
}
// SchemaInspectParams are the parameters for the `schema inspect` command.
SchemaInspectParams struct {
Expand Down Expand Up @@ -629,9 +630,14 @@ type InvalidParamsError struct {
func (e *InvalidParamsError) Error() string {
return fmt.Sprintf("atlasexec: command %q has invalid parameters: %v", e.cmd, e.msg)
}
func newSchemaApplyError(r []*SchemaApply) error {
return &SchemaApplyError{Result: r}
func newSchemaApplyError(r []*SchemaApply, stderr string) error {
return &SchemaApplyError{Result: r, Stderr: stderr}
}

// Error implements the error interface.
func (e *SchemaApplyError) Error() string { return last(e.Result).Error }
func (e *SchemaApplyError) Error() string {
if e.Stderr != "" {
return e.Stderr
}
return last(e.Result).Error
}

0 comments on commit b13dd7f

Please sign in to comment.