diff --git a/env/env_test.go b/env/env_test.go index 71c8216..1ecd698 100644 --- a/env/env_test.go +++ b/env/env_test.go @@ -278,3 +278,65 @@ func TestLookupEnvReferenceScheme(t *testing.T) { }) } } + +func TestLookupEnvRejectsErrorStatus(t *testing.T) { + var status atomic.Int32 + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + code := int(status.Load()) + w.WriteHeader(code) + if code >= 200 && code <= 299 { + w.Write([]byte("fetched-value")) + return + } + w.Write([]byte(http.StatusText(code))) + })) + t.Cleanup(ts.Close) + const key = "_TEST_ENV_STATUS" + t.Setenv(key, "env://minio:minio123@"+strings.TrimPrefix(ts.URL, "http://")) + + for _, code := range []int{http.StatusOK, http.StatusCreated, 299} { + t.Setenv("_"+key, "") + os.Unsetenv("_" + key) + status.Store(int32(code)) + if v, _, _, err := LookupEnv(key); err != nil || v != "fetched-value" { + t.Errorf("HTTP %d: LookupEnv = %q, %v; want fetched-value", code, v, err) + } + if c := os.Getenv("_" + key); c != "fetched-value" { + t.Errorf("HTTP %d: cache = %q, want fetched-value", code, c) + } + } + + for _, code := range []int{ + http.StatusMultipleChoices, + http.StatusUnauthorized, + http.StatusForbidden, + http.StatusNotFound, + http.StatusInternalServerError, + http.StatusServiceUnavailable, + } { + t.Setenv("_"+key, "") + os.Unsetenv("_" + key) + status.Store(int32(code)) + v, _, _, err := LookupEnv(key) + if err == nil || v != "" { + t.Errorf("HTTP %d: LookupEnv = %q, %v; want an error", code, v, err) + } + if c, ok := os.LookupEnv("_" + key); ok { + t.Errorf("HTTP %d: cached %q, want no cache", code, c) + } + } + + t.Setenv("_"+key, "") + os.Unsetenv("_" + key) + status.Store(http.StatusOK) + if v, _, _, err := LookupEnv(key); err != nil || v != "fetched-value" { + t.Fatalf("HTTP 200: LookupEnv = %q, %v; want fetched-value", v, err) + } + status.Store(http.StatusServiceUnavailable) + if v, _, _, err := LookupEnv(key); err != nil || v != "fetched-value" { + t.Errorf("HTTP 503 after a fetch: LookupEnv = %q, %v; want the cached fetched-value", v, err) + } + if c := os.Getenv("_" + key); c != "fetched-value" { + t.Errorf("HTTP 503 after a fetch: cache = %q, want fetched-value", c) + } +} diff --git a/env/web_env.go b/env/web_env.go index a09eef6..36c2a70 100644 --- a/env/web_env.go +++ b/env/web_env.go @@ -151,6 +151,12 @@ func getEnvValueFromHTTP(urlStr, envKey string) (string, string, string, error) } defer resp.Body.Close() + // The body of an error response is not the value: returning it would + // also replace the cached value that LookupEnv falls back to. + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return "", "", "", fmt.Errorf("env webhook returned %s", resp.Status) + } + envValueBytes, err := io.ReadAll(resp.Body) if err != nil { return "", "", "", err