@causa/workspace-google 0.5.0 → 0.6.0

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.
package/README.md CHANGED
@@ -57,6 +57,14 @@ secrets:
57
57
 
58
58
  When the GCP project is not specified in the secret ID, it is inferred from `google.secretManager.project`, or `google.project` (in this order). This allows defining the GCP project a single time if needed.
59
59
 
60
+ A second secret backend, `google.accessToken`, does not fetch secrets from a source but rather returns a GCP access token, which can be used to access Google services:
61
+
62
+ ```yaml
63
+ secrets:
64
+ gcpAccessToken:
65
+ backend: google.accessToken
66
+ ```
67
+
60
68
  ## 🔨 Custom `google` commands
61
69
 
62
70
  This modules adds a new command to the CLI: `cs google`. Here is the list of subcommands that are exposed.
@@ -22,9 +22,17 @@ export declare class EmulatorStartForSpanner extends EmulatorStart {
22
22
  * @returns The configuration for the Spanner emulator.
23
23
  */
24
24
  private startSpannerEmulator;
25
+ /**
26
+ * Creates the Spanner instance and databases within the emulator.
27
+ *
28
+ * @param context The {@link WorkspaceContext}.
29
+ * @returns The configuration for the created instance and databases.
30
+ */
31
+ private initializeEmulator;
25
32
  /**
26
33
  * Creates a local instance within the Spanner emulator.
27
34
  *
35
+ * @param spanner The {@link Spanner} client.
28
36
  * @param context The {@link WorkspaceContext}.
29
37
  * @returns The created Spanner {@link Instance} and the corresponding client configuration.
30
38
  */
@@ -27,10 +27,9 @@ export class EmulatorStartForSpanner extends EmulatorStart {
27
27
  return {};
28
28
  }
29
29
  const emulatorConf = await this.startSpannerEmulator(context);
30
- const { instance, instanceConf } = await this.createInstance(context);
31
- const databaseConf = await this.createDatabases(instance, context);
30
+ const instanceAndDatabaseConf = await this.initializeEmulator(context);
32
31
  context.logger.info('🗃️ Successfully initialized Spanner emulator.');
33
- return { ...emulatorConf, ...instanceConf, ...databaseConf };
32
+ return { ...emulatorConf, ...instanceAndDatabaseConf };
34
33
  }
35
34
  /**
36
35
  * Starts the Spanner emulator.
@@ -61,22 +60,35 @@ export class EmulatorStartForSpanner extends EmulatorStart {
61
60
  };
62
61
  }
63
62
  /**
64
- * Creates a local instance within the Spanner emulator.
63
+ * Creates the Spanner instance and databases within the emulator.
65
64
  *
66
65
  * @param context The {@link WorkspaceContext}.
67
- * @returns The created Spanner {@link Instance} and the corresponding client configuration.
66
+ * @returns The configuration for the created instance and databases.
68
67
  */
69
- async createInstance(context) {
70
- const instanceName = context
71
- .asConfiguration()
72
- .get('google.spanner.emulator.instanceName') ?? 'local';
73
- context.logger.info(`🗃️ Creating Spanner emulator instance '${instanceName}'.`);
68
+ async initializeEmulator(context) {
74
69
  const spanner = new Spanner({
75
70
  servicePath: '127.0.0.1',
76
71
  port: SPANNER_GRPC_PORT,
77
72
  projectId: getLocalGcpProject(context),
78
73
  sslCreds: credentials.createInsecure(),
79
74
  });
75
+ const { instance, instanceConf } = await this.createInstance(spanner, context);
76
+ const databaseConf = await this.createDatabases(instance, context);
77
+ spanner.close();
78
+ return { ...instanceConf, ...databaseConf };
79
+ }
80
+ /**
81
+ * Creates a local instance within the Spanner emulator.
82
+ *
83
+ * @param spanner The {@link Spanner} client.
84
+ * @param context The {@link WorkspaceContext}.
85
+ * @returns The created Spanner {@link Instance} and the corresponding client configuration.
86
+ */
87
+ async createInstance(spanner, context) {
88
+ const instanceName = context
89
+ .asConfiguration()
90
+ .get('google.spanner.emulator.instanceName') ?? 'local';
91
+ context.logger.info(`🗃️ Creating Spanner emulator instance '${instanceName}'.`);
80
92
  const [instance, operation] = await spanner.createInstance(instanceName, {
81
93
  config: 'emulator-config',
82
94
  displayName: instanceName,
@@ -98,8 +110,11 @@ export class EmulatorStartForSpanner extends EmulatorStart {
98
110
  context.logger.info(`🗃️ Creating Spanner emulator database '${database.id}'.`);
99
111
  // Discarding `DROP TABLE` statements as the emulator does not seem to handle them properly.
100
112
  const ddls = database.ddls.filter((statement) => !statement.toUpperCase().startsWith('DROP TABLE'));
101
- const operation = (await instance.createDatabase(database.id, { schema: ddls }))[1];
113
+ const [db, operation] = await instance.createDatabase(database.id, {
114
+ schema: ddls,
115
+ });
102
116
  await operation.promise();
117
+ await db.close();
103
118
  }));
104
119
  return databases.length === 0
105
120
  ? {}
@@ -21,7 +21,7 @@ const TOKEN_TTL = 3600;
21
21
  /**
22
22
  * Generates a new AppCheck token.
23
23
  */
24
- export let GoogleAppCheckGenerateToken = class GoogleAppCheckGenerateToken extends WorkspaceFunction {
24
+ let GoogleAppCheckGenerateToken = class GoogleAppCheckGenerateToken extends WorkspaceFunction {
25
25
  /**
26
26
  * The ID of the Firebase app for which the token will be generated.
27
27
  * If `undefined`, a Firebase app ID will be found in the configuration or using the API.
@@ -64,3 +64,4 @@ If the Firebase app ID is not specified, it will:
64
64
  outputFn: (token) => console.log(token),
65
65
  })
66
66
  ], GoogleAppCheckGenerateToken);
67
+ export { GoogleAppCheckGenerateToken };
@@ -27,7 +27,7 @@ const DEFAULT_FIREBASE_STORAGE_SECURITY_RULE_FILE = 'storage.rules';
27
27
  * Returns a configuration with `google.firebaseStorage.securityRuleFile` set, such that the function can be used as a
28
28
  * processor.
29
29
  */
30
- export let GoogleFirebaseStorageMergeRules = class GoogleFirebaseStorageMergeRules extends WorkspaceFunction {
30
+ let GoogleFirebaseStorageMergeRules = class GoogleFirebaseStorageMergeRules extends WorkspaceFunction {
31
31
  tearDown;
32
32
  async _call(context) {
33
33
  if (this.tearDown) {
@@ -69,3 +69,4 @@ Input files are looked for in the workspace using the globs defined in google.fi
69
69
  outputFn: ({ securityRuleFile }) => console.log(securityRuleFile),
70
70
  })
71
71
  ], GoogleFirebaseStorageMergeRules);
72
+ export { GoogleFirebaseStorageMergeRules };
@@ -27,7 +27,7 @@ const DEFAULT_FIRESTORE_SECURITY_RULE_FILE = 'firestore.rules';
27
27
  * Returns a configuration with `google.firestore.securityRuleFile` set, such that the function can be used as a
28
28
  * processor.
29
29
  */
30
- export let GoogleFirestoreMergeRules = class GoogleFirestoreMergeRules extends WorkspaceFunction {
30
+ let GoogleFirestoreMergeRules = class GoogleFirestoreMergeRules extends WorkspaceFunction {
31
31
  tearDown;
32
32
  async _call(context) {
33
33
  if (this.tearDown) {
@@ -69,3 +69,4 @@ Input files are looked for in the workspace using the globs defined in google.fi
69
69
  outputFn: ({ securityRuleFile }) => console.log(securityRuleFile),
70
70
  })
71
71
  ], GoogleFirestoreMergeRules);
72
+ export { GoogleFirestoreMergeRules };
@@ -21,7 +21,7 @@ import { GoogleIdentityPlatformGenerateCustomToken } from './generate-custom-tok
21
21
  * For this function to succeed, the `google.project` should be set, which usually means setting the environment in the
22
22
  * context. Also `google.firebase` children can be used to configure (and speed up) how tokens are generated.
23
23
  */
24
- export let GoogleIdentityPlatformGenerateToken = class GoogleIdentityPlatformGenerateToken extends WorkspaceFunction {
24
+ let GoogleIdentityPlatformGenerateToken = class GoogleIdentityPlatformGenerateToken extends WorkspaceFunction {
25
25
  /**
26
26
  * The ID of the user for which the token will be generated.
27
27
  */
@@ -71,3 +71,4 @@ Optional custom claims can be included in the signed token.`,
71
71
  outputFn: (token) => console.log(token),
72
72
  })
73
73
  ], GoogleIdentityPlatformGenerateToken);
74
+ export { GoogleIdentityPlatformGenerateToken };
@@ -20,7 +20,7 @@ const MAX_SERVICE_BATCH = 20;
20
20
  /**
21
21
  * Enables GCP services defined in `google.services` for the GCP project defined in `google.project`.
22
22
  */
23
- export let GoogleServicesEnable = class GoogleServicesEnable extends WorkspaceFunction {
23
+ let GoogleServicesEnable = class GoogleServicesEnable extends WorkspaceFunction {
24
24
  tearDown;
25
25
  async _call(context) {
26
26
  if (this.tearDown) {
@@ -72,3 +72,4 @@ They will be enabled in the 'google.project' GCP project.`,
72
72
  outputFn: ({ services }) => console.log(services.join('\n')),
73
73
  })
74
74
  ], GoogleServicesEnable);
75
+ export { GoogleServicesEnable };
@@ -8,7 +8,7 @@ import { GooglePubSubWriteTopics } from './google-pubsub/index.js';
8
8
  import { GoogleServicesEnable } from './google-services/index.js';
9
9
  import { GoogleSpannerListDatabases, GoogleSpannerWriteDatabases, } from './google-spanner/index.js';
10
10
  import { ProjectGetArtefactDestinationForCloudFunctions, ProjectGetArtefactDestinationForCloudRun, ProjectPushArtefactForCloudFunctions, } from './project/index.js';
11
- import { SecretFetchForGoogleSecretManager } from './secret/index.js';
11
+ import { SecretFetchForGoogleAccessToken, SecretFetchForGoogleSecretManager, } from './secret/index.js';
12
12
  export function registerFunctions(context) {
13
- context.registerFunctionImplementations(EmulatorStartForFirebaseStorage, EmulatorStartForFirestore, EmulatorStartForIdentityPlatform, EmulatorStartForPubSub, EmulatorStartForSpanner, EmulatorStopForFirebaseStorage, EmulatorStopForFirestore, EmulatorStopForIdentityPlatform, EmulatorStopForPubSub, EmulatorStopForSpanner, EventTopicBrokerCreateTopicForPubSub, EventTopicBrokerCreateTriggerForCloudRun, EventTopicBrokerDeleteTopicForPubSub, EventTopicBrokerDeleteTriggerResourceForCloudRunInvokerRole, EventTopicBrokerDeleteTriggerResourceForPubSubSubscription, EventTopicBrokerDeleteTriggerResourceForServiceAccount, EventTopicBrokerGetTopicIdForPubSub, EventTopicBrokerPublishEventsForGoogle, GoogleAppCheckGenerateToken, GoogleFirebaseStorageMergeRules, GoogleFirestoreMergeRules, GoogleIdentityPlatformGenerateCustomToken, GoogleIdentityPlatformGenerateToken, GooglePubSubWriteTopics, GoogleServicesEnable, GoogleSpannerListDatabases, GoogleSpannerWriteDatabases, ProjectGetArtefactDestinationForCloudFunctions, ProjectGetArtefactDestinationForCloudRun, ProjectPushArtefactForCloudFunctions, SecretFetchForGoogleSecretManager);
13
+ context.registerFunctionImplementations(EmulatorStartForFirebaseStorage, EmulatorStartForFirestore, EmulatorStartForIdentityPlatform, EmulatorStartForPubSub, EmulatorStartForSpanner, EmulatorStopForFirebaseStorage, EmulatorStopForFirestore, EmulatorStopForIdentityPlatform, EmulatorStopForPubSub, EmulatorStopForSpanner, EventTopicBrokerCreateTopicForPubSub, EventTopicBrokerCreateTriggerForCloudRun, EventTopicBrokerDeleteTopicForPubSub, EventTopicBrokerDeleteTriggerResourceForCloudRunInvokerRole, EventTopicBrokerDeleteTriggerResourceForPubSubSubscription, EventTopicBrokerDeleteTriggerResourceForServiceAccount, EventTopicBrokerGetTopicIdForPubSub, EventTopicBrokerPublishEventsForGoogle, GoogleAppCheckGenerateToken, GoogleFirebaseStorageMergeRules, GoogleFirestoreMergeRules, GoogleIdentityPlatformGenerateCustomToken, GoogleIdentityPlatformGenerateToken, GooglePubSubWriteTopics, GoogleServicesEnable, GoogleSpannerListDatabases, GoogleSpannerWriteDatabases, ProjectGetArtefactDestinationForCloudFunctions, ProjectGetArtefactDestinationForCloudRun, ProjectPushArtefactForCloudFunctions, SecretFetchForGoogleAccessToken, SecretFetchForGoogleSecretManager);
14
14
  }
@@ -0,0 +1,18 @@
1
+ import { SecretFetch, WorkspaceContext } from '@causa/workspace';
2
+ /**
3
+ * An error thrown when the auth client does not return a token.
4
+ */
5
+ export declare class AuthClientResponseError extends Error {
6
+ constructor();
7
+ }
8
+ /**
9
+ * Implements {@link SecretFetch} to retrieve a Google access token.
10
+ * This backend does not require any configuration, as it does not fetch a specific secret and can only return a single
11
+ * value. A Google access token can be used to authenticate with other Google services. The returned token is configured
12
+ * with the current user (or service account)'s credentials, and for the configured GCP project (if any).
13
+ * The backend ID is `google.accessToken`.
14
+ */
15
+ export declare class SecretFetchForGoogleAccessToken extends SecretFetch {
16
+ _call(context: WorkspaceContext): Promise<string>;
17
+ _supports(): boolean;
18
+ }
@@ -0,0 +1,30 @@
1
+ import { SecretFetch } from '@causa/workspace';
2
+ import { GoogleApisService } from '../../services/index.js';
3
+ /**
4
+ * An error thrown when the auth client does not return a token.
5
+ */
6
+ export class AuthClientResponseError extends Error {
7
+ constructor() {
8
+ super('Failed to retrieve the Google access token from the auth client response.');
9
+ }
10
+ }
11
+ /**
12
+ * Implements {@link SecretFetch} to retrieve a Google access token.
13
+ * This backend does not require any configuration, as it does not fetch a specific secret and can only return a single
14
+ * value. A Google access token can be used to authenticate with other Google services. The returned token is configured
15
+ * with the current user (or service account)'s credentials, and for the configured GCP project (if any).
16
+ * The backend ID is `google.accessToken`.
17
+ */
18
+ export class SecretFetchForGoogleAccessToken extends SecretFetch {
19
+ async _call(context) {
20
+ const authClient = await context.service(GoogleApisService).getAuthClient();
21
+ const { token } = await authClient.getAccessToken();
22
+ if (!token) {
23
+ throw new AuthClientResponseError();
24
+ }
25
+ return token;
26
+ }
27
+ _supports() {
28
+ return this.backend === 'google.accessToken';
29
+ }
30
+ }
@@ -1 +1,2 @@
1
+ export { SecretFetchForGoogleAccessToken } from './fetch-access-token.js';
1
2
  export { SecretFetchForGoogleSecretManager } from './fetch-secret-manager.js';
@@ -1 +1,2 @@
1
+ export { SecretFetchForGoogleAccessToken } from './fetch-access-token.js';
1
2
  export { SecretFetchForGoogleSecretManager } from './fetch-secret-manager.js';
@@ -1,4 +1,5 @@
1
1
  import { WorkspaceContext } from '@causa/workspace';
2
+ import { AuthClient } from 'google-auth-library';
2
3
  import { GoogleApis } from 'googleapis';
3
4
  import { ApiClient, OptionsOfApiClient } from './google-apis.types.js';
4
5
  /**
@@ -9,7 +10,7 @@ export declare class GoogleApisService {
9
10
  /**
10
11
  * The GCP project ID read from the {@link WorkspaceContext} configuration.
11
12
  */
12
- readonly projectId: string;
13
+ readonly projectId: string | undefined;
13
14
  constructor(context: WorkspaceContext);
14
15
  /**
15
16
  * The promise returning the `JSONClient` configured with the {@link GoogleApisService.projectId}.
@@ -20,7 +21,7 @@ export declare class GoogleApisService {
20
21
  *
21
22
  * @returns The auth client.
22
23
  */
23
- getAuthClient(): Promise<any>;
24
+ getAuthClient(): Promise<AuthClient>;
24
25
  /**
25
26
  * Creates a new client for one of Google's APIs.
26
27
  * Authentication is automatically configured.
@@ -9,8 +9,9 @@ export class GoogleApisService {
9
9
  */
10
10
  projectId;
11
11
  constructor(context) {
12
- const googleConf = context.asConfiguration();
13
- this.projectId = googleConf.getOrThrow('google.project');
12
+ this.projectId = context
13
+ .asConfiguration()
14
+ .get('google.project');
14
15
  }
15
16
  /**
16
17
  * The promise returning the `JSONClient` configured with the {@link GoogleApisService.projectId}.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@causa/workspace-google",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "The Causa workspace module providing many functionalities related to GCP and its services.",
5
5
  "repository": "github:causa-io/workspace-module-google",
6
6
  "license": "ISC",
@@ -31,41 +31,42 @@
31
31
  "dependencies": {
32
32
  "@causa/cli": ">= 0.4.0 < 1.0.0",
33
33
  "@causa/workspace": ">= 0.12.0 < 1.0.0",
34
- "@causa/workspace-core": ">= 0.14.1 < 1.0.0",
35
- "@google-cloud/apikeys": "^0.2.2",
36
- "@google-cloud/bigquery": "^7.1.1",
37
- "@google-cloud/iam-credentials": "^2.0.4",
38
- "@google-cloud/pubsub": "^4.0.0",
39
- "@google-cloud/resource-manager": "^4.3.0",
40
- "@google-cloud/run": "^0.6.0",
41
- "@google-cloud/secret-manager": "^4.2.2",
42
- "@google-cloud/service-usage": "^2.2.2",
43
- "@google-cloud/spanner": "^6.15.0",
44
- "@google-cloud/storage": "^7.0.0",
45
- "@grpc/grpc-js": "~1.8.21",
34
+ "@causa/workspace-core": ">= 0.16.0 < 1.0.0",
35
+ "@google-cloud/apikeys": "^1.0.1",
36
+ "@google-cloud/bigquery": "^7.3.0",
37
+ "@google-cloud/iam-credentials": "^3.0.1",
38
+ "@google-cloud/pubsub": "^4.0.6",
39
+ "@google-cloud/resource-manager": "^5.0.1",
40
+ "@google-cloud/run": "^1.0.1",
41
+ "@google-cloud/secret-manager": "^5.0.1",
42
+ "@google-cloud/service-usage": "^3.1.0",
43
+ "@google-cloud/spanner": "^7.0.0",
44
+ "@google-cloud/storage": "^7.1.0",
45
+ "@grpc/grpc-js": "^1.9.4",
46
46
  "class-validator": "^0.14.0",
47
- "firebase": "^10.1.0",
48
- "firebase-admin": "^11.10.1",
47
+ "firebase": "^10.4.0",
48
+ "firebase-admin": "^11.11.0",
49
49
  "globby": "^13.2.2",
50
- "googleapis": "^123.0.0",
51
- "pino": "^8.15.0",
52
- "uuid": "^9.0.0"
50
+ "google-auth-library": "^9.0.0",
51
+ "googleapis": "^126.0.1",
52
+ "pino": "^8.15.1",
53
+ "uuid": "^9.0.1"
53
54
  },
54
55
  "devDependencies": {
55
- "@tsconfig/node18": "^18.2.0",
56
- "@types/jest": "^29.5.3",
57
- "@types/node": "^18.17.2",
58
- "@types/uuid": "^9.0.2",
59
- "@typescript-eslint/eslint-plugin": "^6.2.1",
56
+ "@tsconfig/node18": "^18.2.2",
57
+ "@types/jest": "^29.5.5",
58
+ "@types/node": "^18.18.0",
59
+ "@types/uuid": "^9.0.4",
60
+ "@typescript-eslint/eslint-plugin": "^6.7.3",
60
61
  "copyfiles": "^2.4.1",
61
- "eslint": "^8.46.0",
62
- "eslint-config-prettier": "^8.10.0",
62
+ "eslint": "^8.50.0",
63
+ "eslint-config-prettier": "^9.0.0",
63
64
  "eslint-plugin-prettier": "^5.0.0",
64
- "jest": "^29.6.2",
65
+ "jest": "^29.7.0",
65
66
  "jest-extended": "^4.0.1",
66
- "rimraf": "^5.0.1",
67
+ "rimraf": "^5.0.5",
67
68
  "ts-jest": "^29.1.1",
68
69
  "ts-node": "^10.9.1",
69
- "typescript": "^5.1.6"
70
+ "typescript": "^5.2.2"
70
71
  }
71
72
  }