You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by at...@apache.org on 2026/08/07 15:55:05 UTC
(iceberg-go) branch main updated: fix(io/gcs): propagate application-default credential errors (#1584)
This is an automated email from the ASF dual-hosted git repository.
laskoviymishka pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git
The following commit(s) were added to refs/heads/main by this push:
new c98c32d4 fix(io/gcs): propagate application-default credential errors (#1584)
c98c32d4 is described below
commit c98c32d44e82c2c14c277ed11af684a20a99b868
Author: Minh Vu <vu...@gmail.com>
AuthorDate: Fri Aug 7 17:54:59 2026 +0200
fix(io/gcs): propagate application-default credential errors (#1584)
* fix(io/gcs): propagate application-default credential errors
Signed-off-by: Minh Vu <vu...@gmail.com>
* fix(io/gcs): preserve implicit anonymous fallback
* fix(io/gcs): propagate all ADC discovery errors
* test(io/gcs): use explicit anonymous emulator access
Signed-off-by: Minh Vu <vu...@gmail.com>
* fix(io/gcs): clarify missing credential failures
* test(io/gcs): cover no-auth credential bypass
* fix(io/gcs): satisfy credential test lint
---------
Signed-off-by: Minh Vu <vu...@gmail.com>
---
io/gocloud/gcs.go | 16 ++++++++++++-
io/gocloud/gcs_integration_test.go | 4 +++-
io/gocloud/gcs_test.go | 49 ++++++++++++++++++++++++++++++++++++++
3 files changed, 67 insertions(+), 2 deletions(-)
diff --git a/io/gocloud/gcs.go b/io/gocloud/gcs.go
index dc68343f..1b01550b 100644
--- a/io/gocloud/gcs.go
+++ b/io/gocloud/gcs.go
@@ -86,6 +86,14 @@ func ParseGCSConfig(props map[string]string) *gcsblob.Options {
const gcsScope = "https://www.googleapis.com/auth/devstorage.read_write"
func gcsCredentials(ctx context.Context, props map[string]string) (*google.Credentials, error) {
+ return gcsCredentialsWithDefault(ctx, props, gcp.DefaultCredentials)
+}
+
+func gcsCredentialsWithDefault(
+ ctx context.Context,
+ props map[string]string,
+ defaultCredentials func(context.Context) (*google.Credentials, error),
+) (*google.Credentials, error) {
// Explicit no-auth wins over everything: use an anonymous client even when
// ADC is available.
if noAuth, err := strconv.ParseBool(props[io.GCSNoAuth]); err == nil && noAuth {
@@ -131,7 +139,13 @@ func gcsCredentials(ctx context.Context, props map[string]string) (*google.Crede
return parse(data, fmt.Sprintf("%s %q", io.GCSKeyPath, path))
}
- creds, _ := gcp.DefaultCredentials(ctx)
+ creds, err := defaultCredentials(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("gcs: no credentials found; set %s=true for anonymous/public bucket access: %w", io.GCSNoAuth, err)
+ }
+ if creds == nil {
+ return nil, fmt.Errorf("gcs: no credentials found; set %s=true for anonymous/public bucket access", io.GCSNoAuth)
+ }
return creds, nil
}
diff --git a/io/gocloud/gcs_integration_test.go b/io/gocloud/gcs_integration_test.go
index 0b86628d..ebaba81f 100644
--- a/io/gocloud/gcs_integration_test.go
+++ b/io/gocloud/gcs_integration_test.go
@@ -115,7 +115,9 @@ func (s *GCSIOTestSuite) TestGCSWarehouse() {
"type": "sql",
"warehouse": fmt.Sprintf("gs://%s/iceberg/", gcsBucketName),
io.GCSEndpoint: fmt.Sprintf("http://%s/", gcsEndpoint),
- io.GCSUseJSONAPI: "true",
+ // The local fake-gcs-server intentionally has no application credentials.
+ io.GCSNoAuth: "true",
+ io.GCSUseJSONAPI: "true",
}
cat, err := catalog.Load(context.Background(), "default", properties)
diff --git a/io/gocloud/gcs_test.go b/io/gocloud/gcs_test.go
index a004ec5f..6b3e7a66 100644
--- a/io/gocloud/gcs_test.go
+++ b/io/gocloud/gcs_test.go
@@ -19,6 +19,7 @@ package gocloud
import (
"context"
+ "errors"
"os"
"path/filepath"
"strconv"
@@ -28,6 +29,7 @@ import (
"github.com/apache/iceberg-go/io"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "golang.org/x/oauth2/google"
)
// Both parse without a real RSA key (the private key is only read lazily when a
@@ -142,6 +144,53 @@ func TestGCSCredentialsNoAuth(t *testing.T) {
assert.Nil(t, creds)
}
+func TestGCSCredentialsPropagatesADCError(t *testing.T) {
+ t.Run("missing configured file", func(t *testing.T) {
+ t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", filepath.Join(t.TempDir(), "missing.json"))
+
+ creds, err := gcsCredentials(context.Background(), nil)
+ require.ErrorContains(t, err, "gcs: no credentials found")
+ require.ErrorContains(t, err, io.GCSNoAuth)
+ assert.Nil(t, creds)
+ })
+
+ t.Run("no configured credentials", func(t *testing.T) {
+ t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "")
+
+ creds, err := gcsCredentialsWithDefault(context.Background(), nil,
+ func(context.Context) (*google.Credentials, error) {
+ return nil, errors.New("application default credentials unavailable")
+ })
+ require.ErrorContains(t, err, "gcs: no credentials found")
+ require.ErrorContains(t, err, io.GCSNoAuth)
+ assert.Nil(t, creds)
+ })
+
+ t.Run("nil credentials", func(t *testing.T) {
+ creds, err := gcsCredentialsWithDefault(context.Background(), nil,
+ func(context.Context) (*google.Credentials, error) { return nil, nil })
+ require.ErrorContains(t, err, "gcs: no credentials found")
+ assert.Nil(t, creds)
+ })
+
+ t.Run("no-auth skips default credentials", func(t *testing.T) {
+ called := false
+ creds, err := gcsCredentialsWithDefault(context.Background(), map[string]string{io.GCSNoAuth: "true"},
+ func(context.Context) (*google.Credentials, error) {
+ called = true
+
+ return nil, errors.New("default credentials should not be requested")
+ })
+ require.NoError(t, err)
+ assert.Nil(t, creds)
+ assert.False(t, called)
+ })
+
+ creds, err := gcsCredentials(context.Background(), map[string]string{io.GCSNoAuth: "true"})
+ require.NoError(t, err)
+ assert.Nil(t, creds)
+}
+
// A vended gcs.oauth2.token is turned into a static token source, not dropped.
func TestGCSCredentialsFromOAuthToken(t *testing.T) {
exp := time.Now().Add(time.Hour).UnixMilli()