From 326c3251e74f7ea5156ace251fdcc1d49f1084c3 Mon Sep 17 00:00:00 2001 From: Denis O Date: Fri, 1 Nov 2024 15:04:12 +0000 Subject: [PATCH] Add provider cache offline errors handling (#3527) * add offline errors handling * Improved handling of offline errors * Cleanup * Add test for handling offline errors * Linter fixes * Linter fixes * imports update --- terraform/cache/handlers/provider.go | 27 ++++++++++++++++- terraform/cache/handlers/provider_test.go | 36 +++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 terraform/cache/handlers/provider_test.go diff --git a/terraform/cache/handlers/provider.go b/terraform/cache/handlers/provider.go index 0b76002722..168d671dd6 100644 --- a/terraform/cache/handlers/provider.go +++ b/terraform/cache/handlers/provider.go @@ -4,6 +4,7 @@ package handlers import ( "context" liberrors "errors" + "strings" "syscall" "github.com/gruntwork-io/terragrunt/terraform/cache/models" @@ -31,6 +32,15 @@ var availablePlatforms []*models.Platform = []*models.Platform{ {OS: "windows", Arch: "amd64"}, } +var offlineErrors = []error{ + syscall.ECONNREFUSED, + syscall.ECONNRESET, + syscall.ECONNABORTED, + syscall.EHOSTUNREACH, + syscall.ENETUNREACH, + syscall.ENETDOWN, +} + // ProviderHandlers is a slice of ProviderHandler. type ProviderHandlers []ProviderHandler @@ -120,7 +130,7 @@ func (handler *CommonProviderHandler) DiscoveryURL(ctx context.Context, registry urls, err := DiscoveryURL(ctx, registryName) if err != nil { - if !liberrors.As(err, &NotFoundWellKnownURL{}) && !liberrors.Is(err, syscall.ECONNREFUSED) { + if !IsOfflineError(err) { return nil, err } @@ -134,3 +144,18 @@ func (handler *CommonProviderHandler) DiscoveryURL(ctx context.Context, registry return urls, nil } + +// IsOfflineError returns true if the given error is an offline error and can be use default URL. +func IsOfflineError(err error) bool { + if liberrors.As(err, &NotFoundWellKnownURL{}) { + return true + } + + for _, connErr := range offlineErrors { + if liberrors.Is(err, connErr) || strings.Contains(err.Error(), connErr.Error()) { + return true + } + } + + return false +} diff --git a/terraform/cache/handlers/provider_test.go b/terraform/cache/handlers/provider_test.go new file mode 100644 index 0000000000..c91732400c --- /dev/null +++ b/terraform/cache/handlers/provider_test.go @@ -0,0 +1,36 @@ +package handlers_test + +import ( + "errors" + "syscall" + "testing" + + "github.com/gruntwork-io/terragrunt/terraform/cache/handlers" + + "github.com/stretchr/testify/assert" +) + +func TestIsOfflineError(t *testing.T) { + t.Parallel() + testCases := []struct { + err error + expected bool + desc string + }{ + {syscall.ECONNREFUSED, true, "connection refused"}, + {syscall.ECONNRESET, true, "connection reset by peer"}, + {syscall.ECONNABORTED, true, "connection aborted"}, + {syscall.ENETUNREACH, true, "network is unreachable"}, + {errors.New("get \"https://registry.terraform.io/.well-known/terraform.json\": dial tcp: lookup registry.terraform.io on 185.12.64.1:53: dial udp 185.12.64.1:53: connect: network is unreachable"), true, "network is unreachable"}, + {errors.New("get \"https://registry.terraform.io/.well-known/terraform.json\": read tcp 10.10.230.10:58328->10.245.10.15:443: read: connection reset by peer"), true, "network is unreachable"}, + {errors.New("random error"), false, "a random error that should not be offline"}, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + result := handlers.IsOfflineError(tc.err) + assert.Equal(t, tc.expected, result, "Expected result for %v is %v", tc.desc, tc.expected) + }) + } +}