Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## Aug 26 2026

- `credURL=op://...` loads credentials via scy/1Password without a local credentials file.
- DSN and `SetOptions` credentials are detected via explicit client options; application default credentials (`GOOGLE_APPLICATION_CREDENTIALS`) remain supported when no DSN credentials are configured.
- Requires scy with `op://` support (see [scy](https://github.com/viant/scy) / [afsc/op](https://github.com/viant/afsc/tree/master/op)).

## Aug 17 2022 0.2.0
* Integration with SCY secret manager
## Dec 25 2021 0.1.0
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The BigQuery driver accepts the following DSN
* 'bigquery://projectID/[location/]datasetID?queryString'

Where queryString can optionally configure the following option:
- credURL: (url encoded) local location or URL supported by [Scy](https://github.com/viant/scy)
- credURL: (url encoded) local location or URL supported by [Scy](https://github.com/viant/scy), including `op://` 1Password references (registered via `github.com/viant/afsc/op` in the driver)
- credKey: optional (url encoded) [Scy](https://github.com/viant/scy) secret manager key or key location
- credID: [Scy](https://github.com/viant/scy) resource secret ID
- credJSON: rawURL base64 encoded cred JSON (not recommended)
Expand All @@ -34,7 +34,9 @@ The BigQuery driver accepts the following DSN
- scopes

Since this library uses [Google Cloud API](google.golang.org/api/bigquery/v2)
you can pass your credentials via GOOGLE_APPLICATION_CREDENTIALS environment variable.
you can pass your credentials via GOOGLE_APPLICATION_CREDENTIALS environment variable when no credentials are configured in the DSN or via `SetOptions`.

When `credURL`, `credJSON`, or `SetOptions`/`WithCredentialsJSON` is used, `GOOGLE_APPLICATION_CREDENTIALS` is not required.

## Usage:

Expand Down
17 changes: 4 additions & 13 deletions connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,26 +81,17 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
}

func isAuth(options []option.ClientOption) bool {
credentials, _ := google.FindDefaultCredentials(context.Background())
if credentials != nil {
return true
}
if len(options) == 0 {
return false
}
for _, opt := range options {
if _, ok := opt.(oauth2.TokenSource); ok {
return ok
}
if _, ok := opt.(oauth2.TokenSource); ok {
return ok
return true
}
optName := reflect.TypeOf(opt).String()
if strings.Contains(optName, "HTTP") || strings.Contains(optName, "Creds") {
if strings.Contains(optName, "Credentials") || strings.Contains(optName, "HTTP") {
return true
}
}
return false
credentials, _ := google.FindDefaultCredentials(context.Background())
return credentials != nil
}

// Driver returns a driver
Expand Down
2 changes: 2 additions & 0 deletions driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"database/sql"
"database/sql/driver"

_ "github.com/viant/afsc/op"
"google.golang.org/api/option"
)

Expand Down
77 changes: 77 additions & 0 deletions dsn_credurl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package bigquery

import (
"encoding/json"
"net/url"
"os"
"path/filepath"
"runtime"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/api/option"
)

func TestMain(m *testing.M) {
_, filename, _, ok := runtime.Caller(0)
if ok {
fakeOP := filepath.Join(filepath.Dir(filename), "testdata", "fake-op.sh")
_ = os.Setenv("OP_CLI", fakeOP)
}
os.Exit(m.Run())
}

func TestParseDSN_credURLOp(t *testing.T) {
opRef := "op://Private/viant-e2e.json/notesPlain"
dsn := "bigquery://viant-e2e/mdp?credURL=" + url.QueryEscape(opRef)

cfg, err := ParseDSN(dsn)
require.NoError(t, err)
require.True(t, cfg.hasCred())
require.NotEmpty(t, cfg.CredentialJSON)

var payload map[string]interface{}
require.NoError(t, json.Unmarshal(cfg.CredentialJSON, &payload))
assert.Equal(t, "service_account", payload["type"])
assert.Equal(t, "test", payload["project_id"])

opts := cfg.options()
assert.True(t, isAuth(opts), "credURL-loaded JSON should produce explicit client credentials")
}

func TestIsAuth_explicitCredentials(t *testing.T) {
t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "")
cfg := &Config{CredentialJSON: []byte(`{"type":"service_account"}`)}
assert.True(t, isAuth(cfg.options()))
}

func TestIsAuth_applicationDefaultCredentials(t *testing.T) {
credFile := filepath.Join(t.TempDir(), "adc.json")
const saJSON = `{
"type": "service_account",
"project_id": "test",
"private_key_id": "key-id",
"private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIBOgIBAAJBALRiLoqFqmQvQ2s3WCAS23iR4fa9XgInC4NNRLTaPaJoV1XW5l0C\nAwEAAQJAZG7q2B9WQHq+8sL0Z3uGJm6n8f5WQHq+8sL0Z3uGJm6n8f5WQHq+8sL0Z\n3uGJm6n8f5WQHq+8sL0Z3uGJm6n8f5WQHq+8sL0Z3uGJm6n8f5WQHq+8sL0Z3uG\nJm6n8f5WQHq+8sL0Z3uGJm6n8f5Q==\n-----END RSA PRIVATE KEY-----\n",
"client_email": "test@test.iam.gserviceaccount.com",
"client_id": "123456789",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test%40test.iam.gserviceaccount.com"
}`
require.NoError(t, os.WriteFile(credFile, []byte(saJSON), 0600))
t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", credFile)

assert.True(t, isAuth(nil), "isAuth must detect GOOGLE_APPLICATION_CREDENTIALS when client options are empty")
assert.True(t, isAuth([]option.ClientOption{}))
}

func TestIsAuth_noExplicitCredentials(t *testing.T) {
t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "")
if isAuth(nil) {
t.Skip("application default credentials available without GOOGLE_APPLICATION_CREDENTIALS in this environment")
}
assert.False(t, isAuth(nil))
assert.False(t, isAuth([]option.ClientOption{}))
}
33 changes: 33 additions & 0 deletions integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//go:build integration

package bigquery

import (
"net/url"
"os"
"testing"

"github.com/stretchr/testify/require"
)

// TestIntegrationParseDSN_credURLOp loads a real 1Password secret through ParseDSN.
//
// Run manually (not from CI or Cursor):
//
// op signin
// export OP_INTEGRATION_REF='op://Private/viant-e2e.json/notesPlain'
// go test . -tags=integration -run TestIntegrationParseDSN_credURLOp -count=1 -v
func TestIntegrationParseDSN_credURLOp(t *testing.T) {
ref := os.Getenv("OP_INTEGRATION_REF")
if ref == "" {
t.Skip("set OP_INTEGRATION_REF to an op:// secret reference")
}

dsn := "bigquery://viant-e2e/mdp?credURL=" + url.QueryEscape(ref)
cfg, err := ParseDSN(dsn)
require.NoError(t, err)
require.True(t, cfg.hasCred())
require.NotEmpty(t, cfg.CredentialJSON)
require.True(t, isAuth(cfg.options()))
t.Logf("read %d bytes via credURL", len(cfg.CredentialJSON))
}
8 changes: 8 additions & 0 deletions testdata/fake-op.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh
# Test double for the 1Password CLI. Used by unit tests so `op read` never
# touches a real 1Password session or prompts for access.
if [ "$1" != "read" ]; then
echo "fake-op: unsupported command: $1" >&2
exit 1
fi
printf '%s' '{"type":"service_account","project_id":"test"}'