@malloydata/db-bigquery 0.0.431 → 0.0.432

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
- import type { Job, PagedResponse, Query, QueryResultsOptions, RowMetadata } from '@google-cloud/bigquery';
1
+ import type { BigQueryOptions, Job, PagedResponse, Query, QueryResultsOptions, RowMetadata } from '@google-cloud/bigquery';
2
2
  import type bigquery from '@google-cloud/bigquery/build/src/types';
3
3
  import type { ResourceStream } from '@google-cloud/paginator';
4
- import type { Connection, ConnectionConfig, ConnectionParameterValue, MalloyQueryData, PersistSQLResults, QueryData, QueryRecord, QueryOptionsReader, QueryRunStats, RunSQLOptions, StreamingConnection, TableSourceDef, SQLSourceDef, SQLSourceRequest } from '@malloydata/malloy';
4
+ import type { Connection, ConnectionConfig, ConnectionConfigEntry, ConnectionParameterValue, MalloyQueryData, PersistSQLResults, QueryData, QueryRecord, QueryOptionsReader, QueryRunStats, RunSQLOptions, StreamingConnection, TableSourceDef, SQLSourceDef, SQLSourceRequest } from '@malloydata/malloy';
5
5
  import type { TableMetadata } from '@malloydata/malloy/connection';
6
6
  import { BaseConnection } from '@malloydata/malloy/connection';
7
7
  export interface BigQueryManagerOptions {
@@ -17,6 +17,12 @@ interface CredentialBody {
17
17
  client_email?: string;
18
18
  private_key?: string;
19
19
  }
20
+ /**
21
+ * A caller-supplied credential. Anything Malloy cannot build from config text
22
+ * — impersonation, workload identity federation, a proxied or test credential
23
+ * — arrives as one of these, from a host overlay.
24
+ */
25
+ type AuthClient = BigQueryOptions['authClient'];
20
26
  interface BigQueryConnectionConfiguration {
21
27
  /** This ID is used for Bigquery Table Normalization */
22
28
  projectId?: string;
@@ -28,6 +34,7 @@ interface BigQueryConnectionConfiguration {
28
34
  credentials?: CredentialBody | {
29
35
  [key: string]: ConnectionParameterValue;
30
36
  };
37
+ authClient?: AuthClient;
31
38
  setupSQL?: string;
32
39
  }
33
40
  interface BigQueryConnectionOptions extends ConnectionConfig {
@@ -39,6 +46,13 @@ interface BigQueryConnectionOptions extends ConnectionConfig {
39
46
  };
40
47
  /** The key file's contents, as a JSON string or base64-encoded JSON. */
41
48
  serviceAccountKeyJson?: string;
49
+ authClient?: AuthClient;
50
+ /**
51
+ * The connection's entry as written, before overlay resolution. Set by the
52
+ * registered factory; needed only so `getDigest` can identify which auth
53
+ * client this connection got. See `authIdentityOf`.
54
+ */
55
+ rawConfigData?: ConnectionConfigEntry;
42
56
  location?: string;
43
57
  maximumBytesBilled?: string;
44
58
  timeoutMs?: string;
@@ -93,6 +107,7 @@ export declare class BigQueryConnection extends BaseConnection implements Connec
93
107
  private config;
94
108
  private location?;
95
109
  private setupSQL;
110
+ private authIdentity;
96
111
  constructor(option: BigQueryConnectionOptions, queryOptions?: QueryOptionsReader);
97
112
  constructor(name: string, queryOptions?: QueryOptionsReader, config?: BigQueryConnectionConfiguration);
98
113
  get dialectName(): string;
@@ -44,6 +44,47 @@ const googleCommon = __importStar(require("@google-cloud/common"));
44
44
  const gaxios_1 = require("gaxios");
45
45
  const malloy_1 = require("@malloydata/malloy");
46
46
  const connection_1 = require("@malloydata/malloy/connection");
47
+ /**
48
+ * What to hash for the auth client, given the connection's unresolved config.
49
+ *
50
+ * An `AuthClient` is a live object with nothing stable to hash, so the digest
51
+ * uses the reference the config named it by — `{tenantAuth: "acme"}`. Without
52
+ * it, two connections against the same project holding different impersonated
53
+ * identities produce the same digest, and so the same BuildIDs, and one
54
+ * tenant is served rows persisted for another.
55
+ *
56
+ * The digest is therefore only as distinguishing as the reference path: an
57
+ * overlay whose path doesn't vary by identity leaves them indistinguishable
58
+ * here too.
59
+ */
60
+ function authIdentityOf(rawConfigData) {
61
+ const reference = rawConfigData === null || rawConfigData === void 0 ? void 0 : rawConfigData['authClient'];
62
+ return reference === undefined ? undefined : JSON.stringify(reference);
63
+ }
64
+ /**
65
+ * An `authClient` and a service account key are two answers to one question,
66
+ * and the SDK does not treat them as competing: `GoogleAuth` caches the
67
+ * `authClient` and never consults `credentials` or `keyFilename` again
68
+ * (`google-auth-library`, `googleauth.js`: `cachedCredential = opts.authClient`).
69
+ * A config carrying both therefore runs entirely on the auth client while the
70
+ * key sits there looking live, and whoever wrote it believes the wrong
71
+ * identity is executing their queries. Refuse it instead.
72
+ */
73
+ function rejectCompetingCredentials(name, config) {
74
+ if (config.authClient === undefined)
75
+ return;
76
+ const alsoSet = [
77
+ config.credentials !== undefined ? 'a service account key' : undefined,
78
+ config.serviceAccountKeyPath !== undefined
79
+ ? 'serviceAccountKeyPath'
80
+ : undefined,
81
+ ].filter(what => what !== undefined);
82
+ if (alsoSet.length === 0)
83
+ return;
84
+ throw new Error(`Connection "${name}" sets authClient and also ${alsoSet.join(' and ')}. ` +
85
+ 'An authClient replaces the credential entirely — the key would be ' +
86
+ 'ignored — so supply one or the other.');
87
+ }
47
88
  function isJsonObject(value) {
48
89
  return typeof value === 'object' && value !== null && !Array.isArray(value);
49
90
  }
@@ -364,9 +405,11 @@ class BigQueryConnection extends connection_1.BaseConnection {
364
405
  else {
365
406
  // Every key-bearing property is destructured out of `args`, so a key
366
407
  // never lands in `this.config` — only the credentials object the SDK
367
- // needs does.
368
- const { name, client_email, private_key, serviceAccountKey, serviceAccountKeyJson, ...args } = arg;
408
+ // needs does. `rawConfigData` comes out for the same reason: it is
409
+ // read once, for the digest, and is not connection config.
410
+ const { name, client_email, private_key, serviceAccountKey, serviceAccountKeyJson, rawConfigData, ...args } = arg;
369
411
  this.name = name;
412
+ this.authIdentity = authIdentityOf(rawConfigData);
370
413
  config = args;
371
414
  // Trimmed before it is looked at: a value that came through a here-doc,
372
415
  // a `$(cat key.json)`, or a secret-store copy tends to carry a trailing
@@ -386,10 +429,12 @@ class BigQueryConnection extends connection_1.BaseConnection {
386
429
  };
387
430
  }
388
431
  }
432
+ rejectCompetingCredentials(this.name, config);
389
433
  this.bigQuery = new bigquery_1.BigQuery({
390
434
  userAgent: `Malloy/${malloy_1.Malloy.version}`,
391
435
  keyFilename: config.serviceAccountKeyPath,
392
436
  credentials: config.credentials,
437
+ authClient: config.authClient,
393
438
  projectId: config.billingProjectId,
394
439
  });
395
440
  // record project ID because for unclear reasons we have to modify the project ID on the SDK when
@@ -433,7 +478,11 @@ class BigQueryConnection extends connection_1.BaseConnection {
433
478
  return true;
434
479
  }
435
480
  getDigest() {
436
- return (0, malloy_1.makeDigest)('bigquery', this.projectId, this.setupSQL);
481
+ // Appended only when an auth client is in play, so connections that don't
482
+ // use one keep the digests — and so the persisted tables — they have now.
483
+ return this.authIdentity === undefined
484
+ ? (0, malloy_1.makeDigest)('bigquery', this.projectId, this.setupSQL)
485
+ : (0, malloy_1.makeDigest)('bigquery', this.projectId, this.setupSQL, this.authIdentity);
437
486
  }
438
487
  get supportsNesting() {
439
488
  return true;
package/dist/index.js CHANGED
@@ -11,8 +11,11 @@ const malloy_1 = require("@malloydata/malloy");
11
11
  const bigquery_connection_2 = require("./bigquery_connection");
12
12
  (0, malloy_1.registerConnectionType)('bigquery', {
13
13
  displayName: 'BigQuery',
14
- factory: async (config) => {
15
- return new bigquery_connection_2.BigQueryConnection(config);
14
+ factory: async (config, rawConfigData) => {
15
+ // rawConfigData rides in the options bag so `getDigest` can tell which
16
+ // auth client this connection was given; the constructor keeps it out of
17
+ // the retained config.
18
+ return new bigquery_connection_2.BigQueryConnection({ ...config, rawConfigData });
16
19
  },
17
20
  properties: [
18
21
  {
@@ -48,6 +51,25 @@ const bigquery_connection_2 = require("./bigquery_connection");
48
51
  'JSON (detected automatically), for supplying the key from an ' +
49
52
  'environment variable or secret manager rather than from disk.',
50
53
  },
54
+ // A google-auth-library AuthClient, for credentials Malloy cannot build
55
+ // from config text: impersonation, workload identity federation, a proxied
56
+ // or test credential. The config file names an overlay, the host registers
57
+ // it, and the value never appears in the file — which is what `opaque` plus
58
+ // `source: 'overlay'` say. `mustHaveValue` is the safety half: an overlay
59
+ // that isn't registered would otherwise drop the property and quietly fall
60
+ // back to ambient credentials.
61
+ //
62
+ // When one is supplied, the SDK resolves the project id through it rather
63
+ // than through ambient credentials, so set billingProjectId alongside.
64
+ {
65
+ name: 'authClient',
66
+ displayName: 'Auth Client',
67
+ type: 'opaque',
68
+ source: 'overlay',
69
+ mustHaveValue: true,
70
+ optional: true,
71
+ description: 'Not settable from the UI; supplied by the host.',
72
+ },
51
73
  {
52
74
  name: 'location',
53
75
  displayName: 'Location',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/db-bigquery",
3
- "version": "0.0.431",
3
+ "version": "0.0.432",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,7 +26,7 @@
26
26
  "@google-cloud/bigquery": "7.9.4",
27
27
  "@google-cloud/common": "5.0.2",
28
28
  "@google-cloud/paginator": "5.0.2",
29
- "@malloydata/malloy": "0.0.431",
29
+ "@malloydata/malloy": "0.0.432",
30
30
  "gaxios": "^4.2.0"
31
31
  }
32
32
  }