deploy_error.go RawHistoryFolder
1package cli
2
3import (
4 "errors"
5 "fmt"
6 "regexp"
7 "strings"
8 "unicode"
9
10 "github.com/bnema/gordon/internal/adapters/in/cli/remote"
11 "github.com/bnema/gordon/internal/domain"
12)
13
14var ansiEscapePattern = regexp.MustCompile(`\x1b\[[0-9;?]*[ -/]*[@-~]`)
15
16type renderedDeployFailureError struct {
17 message string
18 err error
19}
20
21func (e *renderedDeployFailureError) Error() string {
22 return e.message
23}
24
25func (e *renderedDeployFailureError) Unwrap() error {
26 return e.err
27}
28
29func formatDeployFailure(err error) error {
30 if formatted, ok := structuredDeployFailure(err); ok {
31 return formatted
32 }
33
34 return fmt.Errorf("failed to deploy: %w", err)
35}
36
37func structuredDeployFailure(err error) (error, bool) {
38 var deployErr *domain.DeployFailureError
39 if errors.As(err, &deployErr) {
40 return &renderedDeployFailureError{
41 message: renderDeployFailure(deployErr.Summary, deployErr.Cause, deployErr.Hint, deployErr.Logs),
42 err: deployErr,
43 }, true
44 }
45
46 var httpErr *remote.HTTPError
47 if errors.As(err, &httpErr) && (httpErr.Structured || httpErr.Cause != "" || httpErr.Hint != "" || len(httpErr.Logs) > 0) {
48 return &renderedDeployFailureError{
49 message: renderDeployFailure(httpErr.Body, httpErr.Cause, httpErr.Hint, httpErr.Logs),
50 err: httpErr,
51 }, true
52 }
53
54 return nil, false
55}
56
57func renderDeployFailure(summary, cause, hint string, logs []string) string {
58 if summary == "" {
59 summary = "failed to deploy"
60 }
61
62 var b strings.Builder
63 if summary == "failed to deploy" {
64 b.WriteString(summary)
65 } else {
66 b.WriteString("failed to deploy: ")
67 b.WriteString(summary)
68 }
69
70 if cause != "" {
71 b.WriteString("\nCause: ")
72 b.WriteString(cause)
73 }
74 if hint != "" {
75 b.WriteString("\nHint: ")
76 b.WriteString(hint)
77 }
78 cleanedLogs := make([]string, 0, len(logs))
79 for _, line := range logs {
80 if cleanedLine := sanitizeDeployLogLine(line); cleanedLine != "" {
81 cleanedLogs = append(cleanedLogs, cleanedLine)
82 }
83 }
84 if len(cleanedLogs) > 0 {
85 b.WriteString("\n\nRecent container logs:")
86 for _, cleanedLine := range cleanedLogs {
87 b.WriteString("\n ")
88 b.WriteString(cleanedLine)
89 }
90 }
91
92 return b.String()
93}
94
95func sanitizeDeployLogLine(line string) string {
96 line = ansiEscapePattern.ReplaceAllString(line, "")
97 line = strings.Map(func(r rune) rune {
98 switch {
99 case r == '\t':
100 return r
101 case !unicode.IsPrint(r):
102 return -1
103 default:
104 return r
105 }
106 }, line)
107 return strings.TrimSpace(line)
108}