@@ -16,7 +16,17 @@ limitations under the License.
1616
1717package errorutil
1818
19- import "strings"
19+ import (
20+ "crypto/x509"
21+ "errors"
22+ "fmt"
23+ "net"
24+ "net/http"
25+ "strings"
26+ "syscall"
27+
28+ "github.com/google/go-containerregistry/pkg/v1/remote/transport"
29+ )
2030
2131const CustomTrivyMediaTypesWarning = `` +
2232 "It looks like you are using Project Quay registry and it is not configured correctly for hosting Deckhouse.\n " +
@@ -58,3 +68,271 @@ func IsTrivyMediaTypeNotAllowedError(err error) bool {
5868 return strings .Contains (errMsg , "MANIFEST_INVALID" ) &&
5969 (strings .Contains (errMsg , "vnd.aquasec.trivy" ) || strings .Contains (errMsg , "application/octet-stream" ))
6070}
71+
72+ const (
73+ colorReset = "\033 [0m"
74+ colorRed = "\033 [31m"
75+ colorYellow = "\033 [33m"
76+ colorCyan = "\033 [36m"
77+ colorBold = "\033 [1m"
78+ )
79+
80+ type errorCategory struct {
81+ name string
82+ causes []string
83+ solutions []string
84+ }
85+
86+ func isCertificateError (err error ) bool {
87+ var (
88+ unknownAuthErr x509.UnknownAuthorityError
89+ certInvalidErr x509.CertificateInvalidError
90+ hostnameErr x509.HostnameError
91+ systemRootsErr x509.SystemRootsError
92+ constraintErr x509.ConstraintViolationError
93+ insecureAlgErr x509.InsecureAlgorithmError
94+ )
95+
96+ return errors .As (err , & unknownAuthErr ) ||
97+ errors .As (err , & certInvalidErr ) ||
98+ errors .As (err , & hostnameErr ) ||
99+ errors .As (err , & systemRootsErr ) ||
100+ errors .As (err , & constraintErr ) ||
101+ errors .As (err , & insecureAlgErr )
102+ }
103+
104+ func isAuthenticationError (err error ) bool {
105+ var transportErr * transport.Error
106+ if ! errors .As (err , & transportErr ) {
107+ return false
108+ }
109+
110+ if transportErr .StatusCode == http .StatusUnauthorized || transportErr .StatusCode == http .StatusForbidden {
111+ return true
112+ }
113+
114+ for _ , diag := range transportErr .Errors {
115+ if diag .Code == transport .UnauthorizedErrorCode || diag .Code == transport .DeniedErrorCode {
116+ return true
117+ }
118+ }
119+
120+ return false
121+ }
122+
123+ func isNetworkError (err error ) bool {
124+ var (
125+ netErr net.Error
126+ opErr * net.OpError
127+ syscallErr syscall.Errno
128+ )
129+
130+ if errors .As (err , & netErr ) {
131+ return true
132+ }
133+
134+ if errors .As (err , & opErr ) {
135+ return true
136+ }
137+
138+ if errors .As (err , & syscallErr ) {
139+ return syscallErr == syscall .ECONNREFUSED ||
140+ syscallErr == syscall .ECONNRESET ||
141+ syscallErr == syscall .ETIMEDOUT ||
142+ syscallErr == syscall .ENETUNREACH ||
143+ syscallErr == syscall .EHOSTUNREACH
144+ }
145+
146+ return false
147+ }
148+
149+ func isDNSError (err error ) bool {
150+ var dnsErr * net.DNSError
151+ return errors .As (err , & dnsErr )
152+ }
153+
154+ func formatError (category errorCategory , err error ) string {
155+ var b strings.Builder
156+
157+ b .WriteString ("\n " )
158+ b .WriteString (colorBold )
159+ b .WriteString (colorRed )
160+ b .WriteString ("error" )
161+ b .WriteString (colorReset )
162+ b .WriteString (colorBold )
163+ b .WriteString (": " )
164+ b .WriteString (category .name )
165+ b .WriteString (colorReset )
166+ b .WriteString ("\n " )
167+
168+ b .WriteString (colorCyan )
169+ b .WriteString (" ╰─▶ " )
170+ b .WriteString (colorReset )
171+ b .WriteString (err .Error ())
172+ b .WriteString ("\n \n " )
173+
174+ if len (category .causes ) > 0 {
175+ b .WriteString (colorYellow )
176+ b .WriteString (" Possible causes:\n " )
177+ b .WriteString (colorReset )
178+ for _ , cause := range category .causes {
179+ b .WriteString (" • " )
180+ b .WriteString (cause )
181+ b .WriteString ("\n " )
182+ }
183+ b .WriteString ("\n " )
184+ }
185+
186+ if len (category .solutions ) > 0 {
187+ b .WriteString (colorCyan )
188+ b .WriteString (" How to fix:\n " )
189+ b .WriteString (colorReset )
190+ for _ , solution := range category .solutions {
191+ b .WriteString (" • " )
192+ b .WriteString (solution )
193+ b .WriteString ("\n " )
194+ }
195+ }
196+
197+ return b .String ()
198+ }
199+
200+ func FormatRegistryError (err error ) string {
201+ if err == nil {
202+ return ""
203+ }
204+
205+ var category errorCategory
206+
207+ switch {
208+ case isCertificateError (err ):
209+ category = errorCategory {
210+ name : "TLS/certificate verification failed" ,
211+ causes : []string {
212+ "Self-signed certificate without proper trust chain" ,
213+ "Certificate expired or not yet valid" ,
214+ "Hostname mismatch between certificate and registry URL" ,
215+ "Corporate proxy or middleware intercepting HTTPS connections" ,
216+ },
217+ solutions : []string {
218+ "Use --insecure flag to skip TLS verification (not recommended for production)" ,
219+ "Add the registry's CA certificate to your system trust store" ,
220+ "Verify the registry URL hostname matches the certificate" ,
221+ },
222+ }
223+
224+ case isAuthenticationError (err ):
225+ var transportErr * transport.Error
226+ name := "Authentication failed"
227+ if errors .As (err , & transportErr ) {
228+ switch transportErr .StatusCode {
229+ case http .StatusUnauthorized :
230+ name = "Authentication failed (HTTP 401 Unauthorized)"
231+ case http .StatusForbidden :
232+ name = "Access denied (HTTP 403 Forbidden)"
233+ }
234+ }
235+
236+ category = errorCategory {
237+ name : name ,
238+ causes : []string {
239+ "Invalid or expired credentials" ,
240+ "License key is incorrect, expired, or not provided" ,
241+ "Insufficient permissions for the requested operation" ,
242+ },
243+ solutions : []string {
244+ "Verify your license key is correct and not expired" ,
245+ "Ensure --license flag is specified with a valid key" ,
246+ "Contact registry administrator to verify access rights" ,
247+ },
248+ }
249+
250+ case isDNSError (err ):
251+ var dnsErr * net.DNSError
252+ name := "DNS resolution failed"
253+ if errors .As (err , & dnsErr ) && dnsErr .Name != "" {
254+ name = fmt .Sprintf ("DNS resolution failed for '%s'" , dnsErr .Name )
255+ }
256+
257+ category = errorCategory {
258+ name : name ,
259+ causes : []string {
260+ "Registry hostname cannot be resolved by DNS" ,
261+ "DNS server is unreachable or not responding" ,
262+ "Incorrect registry URL or typo in hostname" ,
263+ },
264+ solutions : []string {
265+ "Verify the registry URL is spelled correctly" ,
266+ "Check your DNS server configuration" ,
267+ "Try using the registry's IP address instead of hostname" ,
268+ },
269+ }
270+
271+ case isNetworkError (err ):
272+ var opErr * net.OpError
273+ name := "Network connection failed"
274+ if errors .As (err , & opErr ) {
275+ if opErr .Addr != nil {
276+ name = fmt .Sprintf ("Network connection failed to %s" , opErr .Addr .String ())
277+ }
278+ }
279+
280+ category = errorCategory {
281+ name : name ,
282+ causes : []string {
283+ "Network connectivity issues or no internet connection" ,
284+ "Firewall or security group blocking the connection" ,
285+ "Registry server is down or unreachable" ,
286+ },
287+ solutions : []string {
288+ "Check your network connection and internet access" ,
289+ "Verify firewall rules allow outbound HTTPS (port 443)" ,
290+ "Test connectivity with: curl -v https://<registry>" ,
291+ },
292+ }
293+
294+ case IsImageNotFoundError (err ):
295+ category = errorCategory {
296+ name : "Image not found in registry" ,
297+ causes : []string {
298+ "Image tag doesn't exist in the registry" ,
299+ "Incorrect image name or tag specified" ,
300+ },
301+ solutions : []string {
302+ "Verify the image name and tag are correct" ,
303+ "Check if you have permission to access this image" ,
304+ },
305+ }
306+
307+ case IsRepoNotFoundError (err ):
308+ category = errorCategory {
309+ name : "Repository not found in registry" ,
310+ causes : []string {
311+ "Repository doesn't exist in the registry" ,
312+ "Incorrect repository path or name" ,
313+ },
314+ solutions : []string {
315+ "Verify the repository path is correct" ,
316+ "Ensure you have permission to access this repository" ,
317+ },
318+ }
319+
320+ case IsTrivyMediaTypeNotAllowedError (err ):
321+ category = errorCategory {
322+ name : "Unsupported OCI artifact type" ,
323+ causes : []string {
324+ "Registry doesn't support required media types for Trivy security databases" ,
325+ "Project Quay registry not configured for Deckhouse artifacts" ,
326+ },
327+ solutions : []string {
328+ "Configure registry to allow custom OCI artifact types" ,
329+ "See: https://deckhouse.io/products/kubernetes-platform/documentation/v1/supported_versions.html#container-registry" ,
330+ },
331+ }
332+
333+ default :
334+ return err .Error ()
335+ }
336+
337+ return formatError (category , err )
338+ }
0 commit comments