github.com/crossplane/upjet@v1.3.0/pkg/terraform/errors/errors.go (about) 1 // SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io> 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package errors 6 7 import ( 8 "fmt" 9 "strings" 10 11 jsoniter "github.com/json-iterator/go" 12 "github.com/pkg/errors" 13 ) 14 15 const ( 16 levelError = "error" 17 ) 18 19 type tfError struct { 20 message string 21 } 22 23 type applyFailed struct { 24 *tfError 25 } 26 27 // TerraformLog represents relevant fields of a Terraform CLI JSON-formatted log line 28 type TerraformLog struct { 29 Level string `json:"@level"` 30 Message string `json:"@message"` 31 Diagnostic LogDiagnostic `json:"diagnostic"` 32 } 33 34 // LogDiagnostic represents relevant fields of a Terraform CLI JSON-formatted 35 // log line diagnostic info 36 type LogDiagnostic struct { 37 Severity string `json:"severity"` 38 Summary string `json:"summary"` 39 Detail string `json:"detail"` 40 } 41 42 func (t *tfError) Error() string { 43 return t.message 44 } 45 46 func newTFError(message string, logs []byte) (string, *tfError) { 47 tfError := &tfError{ 48 message: message, 49 } 50 51 tfLogs, err := parseTerraformLogs(logs) 52 if err != nil { 53 return err.Error(), tfError 54 } 55 56 messages := make([]string, 0, len(tfLogs)) 57 for _, l := range tfLogs { 58 // only use error logs 59 if l == nil || l.Level != levelError { 60 continue 61 } 62 m := l.Message 63 if l.Diagnostic.Severity == levelError && l.Diagnostic.Summary != "" { 64 m = fmt.Sprintf("%s: %s", l.Diagnostic.Summary, l.Diagnostic.Detail) 65 } 66 messages = append(messages, m) 67 } 68 tfError.message = fmt.Sprintf("%s: %s", message, strings.Join(messages, "\n")) 69 return "", tfError 70 } 71 72 func parseTerraformLogs(logs []byte) ([]*TerraformLog, error) { 73 logLines := strings.Split(string(logs), "\n") 74 tfLogs := make([]*TerraformLog, 0, len(logLines)) 75 for _, l := range logLines { 76 log := &TerraformLog{} 77 l := strings.TrimSpace(l) 78 if l == "" { 79 continue 80 } 81 if err := jsoniter.ConfigCompatibleWithStandardLibrary.UnmarshalFromString(l, log); err != nil { 82 return nil, err 83 } 84 tfLogs = append(tfLogs, log) 85 } 86 return tfLogs, nil 87 } 88 89 // NewApplyFailed returns a new apply failure error with given logs. 90 func NewApplyFailed(logs []byte) error { 91 parseError, tfError := newTFError("apply failed", logs) 92 result := &applyFailed{tfError: tfError} 93 if parseError == "" { 94 return result 95 } 96 return errors.WithMessage(result, parseError) 97 } 98 99 // IsApplyFailed returns whether error is due to failure of an apply operation. 100 func IsApplyFailed(err error) bool { 101 r := &applyFailed{} 102 return errors.As(err, &r) 103 } 104 105 type destroyFailed struct { 106 *tfError 107 } 108 109 // NewDestroyFailed returns a new destroy failure error with given logs. 110 func NewDestroyFailed(logs []byte) error { 111 parseError, tfError := newTFError("destroy failed", logs) 112 result := &destroyFailed{tfError: tfError} 113 if parseError == "" { 114 return result 115 } 116 return errors.WithMessage(result, parseError) 117 } 118 119 // IsDestroyFailed returns whether error is due to failure of a destroy operation. 120 func IsDestroyFailed(err error) bool { 121 r := &destroyFailed{} 122 return errors.As(err, &r) 123 } 124 125 type refreshFailed struct { 126 *tfError 127 } 128 129 // NewRefreshFailed returns a new destroy failure error with given logs. 130 func NewRefreshFailed(logs []byte) error { 131 parseError, tfError := newTFError("refresh failed", logs) 132 result := &refreshFailed{tfError: tfError} 133 if parseError == "" { 134 return result 135 } 136 return errors.WithMessage(result, parseError) 137 } 138 139 // IsRefreshFailed returns whether error is due to failure of a destroy operation. 140 func IsRefreshFailed(err error) bool { 141 r := &refreshFailed{} 142 return errors.As(err, &r) 143 } 144 145 type planFailed struct { 146 *tfError 147 } 148 149 // NewPlanFailed returns a new destroy failure error with given logs. 150 func NewPlanFailed(logs []byte) error { 151 parseError, tfError := newTFError("plan failed", logs) 152 result := &planFailed{tfError: tfError} 153 if parseError == "" { 154 return result 155 } 156 return errors.WithMessage(result, parseError) 157 } 158 159 // IsPlanFailed returns whether error is due to failure of a destroy operation. 160 func IsPlanFailed(err error) bool { 161 r := &planFailed{} 162 return errors.As(err, &r) 163 } 164 165 type retrySchedule struct { 166 invocationCount int 167 ttl int 168 } 169 170 func NewRetryScheduleError(invocationCount, ttl int) error { 171 return &retrySchedule{ 172 invocationCount: invocationCount, 173 ttl: ttl, 174 } 175 } 176 177 func (r *retrySchedule) Error() string { 178 return fmt.Sprintf("native provider reuse budget has been exceeded: invocationCount: %d, ttl: %d", r.invocationCount, r.ttl) 179 } 180 181 // IsRetryScheduleError returns whether the error is a retry error 182 // for the scheduler. 183 func IsRetryScheduleError(err error) bool { 184 r := &retrySchedule{} 185 return errors.As(err, &r) 186 } 187 188 type asyncCreateFailed struct { 189 error 190 } 191 192 // NewAsyncCreateFailed returns a new async crate failure. 193 func NewAsyncCreateFailed(err error) error { 194 if err == nil { 195 return nil 196 } 197 return &asyncCreateFailed{ 198 error: errors.Wrap(err, "async create failed"), 199 } 200 } 201 202 // IsAsyncCreateFailed returns whether error is due to failure of 203 // an async create operation. 204 func IsAsyncCreateFailed(err error) bool { 205 r := &asyncCreateFailed{} 206 return errors.As(err, &r) 207 } 208 209 type asyncUpdateFailed struct { 210 error 211 } 212 213 // NewAsyncUpdateFailed returns a new async update failure. 214 func NewAsyncUpdateFailed(err error) error { 215 if err == nil { 216 return nil 217 } 218 return &asyncUpdateFailed{ 219 error: errors.Wrap(err, "async update failed"), 220 } 221 } 222 223 // IsAsyncUpdateFailed returns whether error is due to failure of 224 // an async update operation. 225 func IsAsyncUpdateFailed(err error) bool { 226 r := &asyncUpdateFailed{} 227 return errors.As(err, &r) 228 } 229 230 type asyncDeleteFailed struct { 231 error 232 } 233 234 // NewAsyncDeleteFailed returns a new async delete failure. 235 func NewAsyncDeleteFailed(err error) error { 236 if err == nil { 237 return nil 238 } 239 return &asyncDeleteFailed{ 240 error: errors.Wrap(err, "async delete failed"), 241 } 242 } 243 244 // IsAsyncDeleteFailed returns whether error is due to failure of 245 // an async delete operation. 246 func IsAsyncDeleteFailed(err error) bool { 247 r := &asyncDeleteFailed{} 248 return errors.As(err, &r) 249 }