Skip to content

Commit dc6567b

Browse files
feat(bigquery): use service_account_base64 to pass credentials (#438)
* feat(bigquery): use service_account_base64 to pass credentials * use `service_account_base64` config that will take precedence over service_account_json in bigquery extractor Co-authored-by: Suhas Karanth <sudo-suhas@users.noreply.github.com>
1 parent e3db53b commit dc6567b

3 files changed

Lines changed: 34 additions & 4 deletions

File tree

plugins/extractors/bigquery/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ source:
1010
table_pattern: gofood.fact_
1111
max_page_size: 100
1212
profile_column: true
13-
credentials_json:
13+
service_account_base64: _________BASE64_ENCODED_SERVICE_ACCOUNT_________________
14+
service_account_json:
1415
{
1516
"type": "service_account",
1617
"private_key_id": "xxxxxxx",
@@ -34,7 +35,8 @@ source:
3435
| Key | Value | Example | Description | |
3536
| :-- | :---- | :------ | :---------- | :-- |
3637
| `project_id` | `string` | `my-project` | BigQuery Project ID | *required* |
37-
| `credentials_json` | `string` | `{"private_key": .., "private_id": ...}` | Service Account in JSON string | *optional* |
38+
| `service_account_base64` | `string` | `____BASE64_ENCODED_SERVICE_ACCOUNT____` | Service Account in base64 encoded string. Takes precedence over `service_account_json` value | *optional* |
39+
| `service_account_json` | `string` | `{"private_key": .., "private_id": ...}` | Service Account in JSON string | *optional* |
3840
| `table_pattern` | `string` | `gofood.fact_` | Regex pattern to filter which bigquery table to scan (whitelist) | *optional* |
3941
| `max_page_size` | `int` | `100` | max page size hint used for fetching datasets/tables/rows from bigquery | *optional* |
4042
| `include_column_profile` | `bool` | `true` | true if you want to profile the column value such min, max, med, avg, top, and freq | *optional* |

plugins/extractors/bigquery/bigquery.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package bigquery
33
import (
44
"context"
55
_ "embed" // used to print the embedded assets
6+
"encoding/base64"
67
"encoding/json"
78
"html/template"
89
"strings"
@@ -30,7 +31,9 @@ var summary string
3031

3132
// Config holds the set of configuration for the bigquery extractor
3233
type Config struct {
33-
ProjectID string `mapstructure:"project_id" validate:"required"`
34+
ProjectID string `mapstructure:"project_id" validate:"required"`
35+
// ServiceAccountBase64 takes precedence over ServiceAccountJSON field
36+
ServiceAccountBase64 string `mapstructure:"service_account_base64"`
3437
ServiceAccountJSON string `mapstructure:"service_account_json"`
3538
MaxPageSize int `mapstructure:"max_page_size"`
3639
TablePattern string `mapstructure:"table_pattern"`
@@ -50,6 +53,9 @@ project_id: google-project-id
5053
table_pattern: gofood.fact_
5154
max_page_size: 100
5255
include_column_profile: true
56+
# Only one of service_account_base64 / service_account_json is needed.
57+
# If both are present, service_account_base64 takes precedence
58+
service_account_base64: ____base64_encoded_service_account____
5359
service_account_json: |-
5460
{
5561
"type": "service_account",
@@ -144,11 +150,20 @@ func (e *Extractor) Extract(ctx context.Context, emit plugins.Emit) (err error)
144150

145151
// Create big query client
146152
func (e *Extractor) createClient(ctx context.Context) (*bigquery.Client, error) {
147-
if e.config.ServiceAccountJSON == "" {
153+
if e.config.ServiceAccountBase64 == "" && e.config.ServiceAccountJSON == "" {
148154
e.logger.Info("credentials are not specified, creating bigquery client using default credentials...")
149155
return bigquery.NewClient(ctx, e.config.ProjectID)
150156
}
151157

158+
if e.config.ServiceAccountBase64 != "" {
159+
serviceAccountJSON, err := base64.StdEncoding.DecodeString(e.config.ServiceAccountBase64)
160+
if err != nil || len(serviceAccountJSON) == 0 {
161+
return nil, errors.Wrap(err, "failed to decode base64 service account")
162+
}
163+
// overwrite ServiceAccountJSON with credentials from ServiceAccountBase64 value
164+
e.config.ServiceAccountJSON = string(serviceAccountJSON)
165+
}
166+
152167
return bigquery.NewClient(ctx, e.config.ProjectID, option.WithCredentialsJSON([]byte(e.config.ServiceAccountJSON)))
153168
}
154169

plugins/extractors/bigquery/bigquery_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,17 @@ func TestInit(t *testing.T) {
3939

4040
assert.NotEqual(t, plugins.InvalidConfigError{}, err)
4141
})
42+
t.Run("should return error if service_account_base64 config is invalid", func(t *testing.T) {
43+
extr := bigquery.New(utils.Logger)
44+
ctx, cancel := context.WithCancel(context.Background())
45+
defer cancel()
46+
err := extr.Init(ctx, plugins.Config{
47+
URNScope: "test-bigquery",
48+
RawConfig: map[string]interface{}{
49+
"project_id": "google-project-id",
50+
"service_account_base64": "----", // invalid
51+
}})
52+
53+
assert.ErrorContains(t, err, "failed to decode base64 service account")
54+
})
4255
}

0 commit comments

Comments
 (0)