@causa/workspace-google 0.2.0 → 0.3.1

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
@@ -1 +1,116 @@
1
1
  # `@causa/workspace-google` module
2
+
3
+ This repository contains the source code for the `@causa/workspace-google` Causa module. It provides many GCP-related utilities and implementations for `cs` commands. For more information about the Causa CLI `cs`, checkout [its repository](https://github.com/causa-io/cli).
4
+
5
+ ## ➕ Requirements
6
+
7
+ The Google module requires [Docker](https://www.docker.com/) in order to run local emulators of GCP services.
8
+
9
+ Although not required, the [`gcloud`](https://cloud.google.com/sdk/gcloud) CLI might be useful, e.g. to set up credentials that will be used by the Causa Google module.
10
+
11
+ ## 🎉 Installation
12
+
13
+ Add `@causa/workspace-google` to your Causa configuration in `causa.modules`.
14
+
15
+ ## 🔧 Configuration
16
+
17
+ For all the Google-related configuration in your Causa files, look at [the schema for the `GoogleConfiguration`](./src/configurations/google.ts).
18
+
19
+ ## ✨ Supported project types and commands
20
+
21
+ ### Project types
22
+
23
+ The following Causa `project.type`s are supported:
24
+
25
+ - `serviceContainer`, with `google.cloudRun` as the `serviceContainer.platform`. This will ensure the built Docker images are pushed to the repository set in `google.cloudRun.dockerRepository`.
26
+ - `serverlessFunctions`, with `google.cloudFunctions` as the `serverlessFunctions.platform`. This will push functions archives to the Cloud Storage bucket set in `google.cloudFunctions.archivesStorageLocation`.
27
+
28
+ ### Emulators
29
+
30
+ The following emulators are implemented:
31
+
32
+ - `google.firebaseStorage`: The Firebase Storage emulator from the Firebase tools. It supports setting the corresponding security rules. See the `cs google firebaseStorage mergeRules` documentation for more details.
33
+ - `google.firestore`: The Firestore emulator from the `gcloud` tools. If supports setting the corresponding security rules. See the `cs google firestore mergeRules` documentation for more details.
34
+ - `google.identityPlatform`: The Identity Platform (Firebase Auth) emulator from the Firebase tools.
35
+ - `google.pubSub`: The Pub/Sub emulator from the `gcloud` tools. It automatically creates the topics for all event topics found in the Causa workspace. `events.broker` must be set to `google.pubSub` for this.
36
+ - `google.spanner`: The Spanner emulator. It automatically creates all the Spanner databases defined in the Causa workspace, and sets up their DDLs. See the `google.spanner` [configuration](./src/configurations/google.ts) for more details.
37
+
38
+ ### Secrets backend
39
+
40
+ This module implements the `google.secretManager` secret backend, allowing fetching secrets from the Google Secret Manager service. Here are some example of how secrets with the `google.secretManager` backend should be defined:
41
+
42
+ ```yaml
43
+ secrets:
44
+ simpleSecret:
45
+ id: simple-secret
46
+ secretWithProject:
47
+ id: projects/gcp-project/secrets/my-secret
48
+ secretWithVersion:
49
+ id: projects/gcp-project/secrets/my-secret/versions/12
50
+ ```
51
+
52
+ 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.
53
+
54
+ ## 🔨 Custom `google` commands
55
+
56
+ This modules adds a new command to the CLI: `cs google`. Here is the list of subcommands that are exposed.
57
+
58
+ ### App Check
59
+
60
+ The `cs google appCheck genToken` command generates an App Check token, which can be used to authenticate calls to APIs that are protected by Firebase App Check.
61
+
62
+ A token is generated for a specific Firebase application. This can be set using the `-a, --app <app>` argument. If it is not set, any Firebase application will be selected automatically using the Firebase API.
63
+
64
+ An App Check token must be signed using an admin service account in the same project as the app. Firebase automatically creates such an account when it is initialized from a GCP project. This command can take care of automatically finding this account. However, if you want to save on API calls, the email for any service account with Firebase admin permissions can be set in `google.firebase.adminServiceAccount`.
65
+
66
+ ### Enable services
67
+
68
+ The `cs google enableServices` command will enable all the GCP services defined in `google.services`. This command is also exposed as an infrastructure processors, under the name `GoogleServicesEnable`.
69
+
70
+ ### Firebase Storage
71
+
72
+ The `cs google firebaseStorage mergeRules` command merges several Firebase security rules files together into a single file that can be used as configuration for both the Firebase Storage emulator, and the Firebase Storage production service.
73
+
74
+ Input files are found using the glob patterns defined in `google.firebaseStorage.securityRuleFiles`. Input files should not include the header, i.e. they should defined what is **inside**:
75
+
76
+ ```
77
+ rules_version = '2';
78
+
79
+ service firebase.storage {
80
+ match /b/{bucket}/o {
81
+ // Only include what is inside those brackets.
82
+ }
83
+ }
84
+ ```
85
+
86
+ The output Firebase security rules file can be set in `google.firebaseStorage.securityRuleFile`.
87
+
88
+ ### Firestore
89
+
90
+ The `cs google firestore mergeRules` command merges several Firebase security rules files together into a single file that can be used as configuration for both the Firestore emulator, and the Firestore production service.
91
+
92
+ This command is extremely similar to `cs google firebaseStorage mergeRules`. Input files are defined in `google.firestore.securityRuleFiles`, and the output file is defined in `google.firestore.securityRuleFile`.
93
+
94
+ ### Identity Platform
95
+
96
+ The `cs google identityPlatform genToken` command generates an Identity Platform (formerly Firebase Auth) ID token, which can be used to authenticate calls to API protected by Identity Platform.
97
+
98
+ This command is similar to `cs google appCheck genToken` in that it requires a service account with Firebase admin permissions to sign the token. See the corresponding command for more information.
99
+
100
+ ## 🧱 Infrastructure processors
101
+
102
+ The Google module provides several infrastructure processors, which can be used to set up the Causa workspace before running infrastructure-related operations.
103
+
104
+ ### `GoogleFirebaseStorageMergeRules`
105
+
106
+ [GoogleFirebaseStorageMergeRules](./src/functions/google-firebase-storage-merge-rules.ts) is the same underlying function as the `cs google firebaseStorage mergeRules` command. It allows preparing the Firebase Storage security rules before possibly deploying them along with the infrastructure. See the corresponding command for more details.
107
+
108
+ ### `GoogleFirestoreMergeRules`
109
+
110
+ [GoogleFirestoreMergeRules](./src/functions/google-firestore-merge-rules.ts) is the same underlying function as the `cs google firestore mergeRules` command. It allows preparing the Firestore security rules before possibly deploying them along with the infrastructure. See the corresponding command for more details.
111
+
112
+ ### `GoogleServicesEnable`
113
+
114
+ [GoogleServicesEnable](./src/functions/google-services-enable.ts) is the same underlying function as the `cs google enableServices` command. It enables GCP services before preparing or deploying the infrastructure.
115
+
116
+ Although infrastructure as code tools usually expose this feature as well (e.g. the [`google_project_service`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_service) Terraform resource), it might be more convenient to enable all the required services before running those tools. It avoids having to define dependencies between the services and all the actual resources being deployed.
@@ -5,35 +5,35 @@ export type GoogleConfiguration = {
5
5
  /**
6
6
  * Configuration for everything Google / GCP-related.
7
7
  */
8
- google?: {
8
+ readonly google?: {
9
9
  /**
10
10
  * The ID of the default GCP project used when performing operations.
11
11
  */
12
- project?: string;
12
+ readonly project?: string;
13
13
  /**
14
14
  * The ID of a local GCP project (which does not exist on GCP).
15
15
  * This is for example used by emulators. It should usually start with `demo-` to be compatible with some emulators.
16
16
  */
17
- localProject?: string;
17
+ readonly localProject?: string;
18
18
  /**
19
19
  * The list of GCP services on which the project depends.
20
20
  * This is useful to automatically enable those services in the GCP project.
21
21
  */
22
- services?: string[];
22
+ readonly services?: string[];
23
23
  /**
24
24
  * Configuration for the `gcloud` command.
25
25
  * This applies to Dockerized calls to `gcloud` using the official `gcloud` Docker image.
26
26
  */
27
- gcloud?: {
27
+ readonly gcloud?: {
28
28
  /**
29
29
  * The version of the `gcloud` command.
30
30
  */
31
- version?: string;
31
+ readonly version?: string;
32
32
  };
33
33
  /**
34
34
  * Configuration for the Firebase project.
35
35
  */
36
- firebase?: {
36
+ readonly firebase?: {
37
37
  /**
38
38
  * An existing service account that can be used to sign Identity Platform custom token.
39
39
  * Generating custom tokens by signing them using end-user credentials does not work with Identity Platform.
@@ -42,156 +42,174 @@ export type GoogleConfiguration = {
42
42
  * If this is not set and is needed by an operation, an attempt will be made to find this service account in the
43
43
  * current `google.project`.
44
44
  */
45
- adminServiceAccount?: string;
45
+ readonly adminServiceAccount?: string;
46
46
  /**
47
47
  * The Firebase API key used by clients. Used to perform requests to Firebase as an end user.
48
48
  * This can be found in the GCP or Firebase Console.
49
49
  * If this is not set and is needed by an operation, an attempt will be made to find the key automatically using
50
50
  * the API.
51
51
  */
52
- apiKey?: string;
52
+ readonly apiKey?: string;
53
53
  /**
54
54
  * The Firebase app ID used by clients.
55
55
  * This can be found in the Firebase Console. Each platform (Android, iOS, Web) has its own app ID.
56
56
  * If this is not set and is needed by an operation, an attempt will be made to find the first eligible app ID
57
57
  * using the Firebase API.
58
58
  */
59
- appId?: string;
59
+ readonly appId?: string;
60
60
  /**
61
61
  * The domain name for the project.
62
62
  * If not set, this default to `<GCP project>.firebaseapp.com`.
63
63
  */
64
- authDomain?: string;
64
+ readonly authDomain?: string;
65
65
  /**
66
66
  * Configuration for Firebase tools (the CLI).
67
67
  * This applies to Dockerized calls to the `firebase` CLI.
68
68
  */
69
- tools?: {
69
+ readonly tools?: {
70
70
  /**
71
71
  * The version of the CLI to use.
72
72
  */
73
- version?: string;
73
+ readonly version?: string;
74
74
  };
75
75
  };
76
76
  /**
77
77
  * Configuration for the Secret Manager service.
78
78
  */
79
- secretManager?: {
79
+ readonly secretManager?: {
80
80
  /**
81
81
  * The ID of the default GCP project referenced when fetching secrets.
82
82
  */
83
- project?: string;
83
+ readonly project?: string;
84
84
  };
85
85
  /**
86
86
  * Configuration for the Firebase Storage service.
87
87
  */
88
- firebaseStorage?: {
88
+ readonly firebaseStorage?: {
89
89
  /**
90
90
  * Configuration for the emulator.
91
91
  */
92
- emulator?: {
92
+ readonly emulator?: {
93
93
  /**
94
94
  * The name of the local Docker container running the emulator.
95
95
  */
96
- containerName?: string;
96
+ readonly containerName?: string;
97
97
  };
98
98
  /**
99
99
  * A list of glob patterns to find files in the workspace defining Firebase Storage security rules.
100
100
  */
101
- securityRuleFiles?: string[];
101
+ readonly securityRuleFiles?: string[];
102
102
  /**
103
103
  * The file path, relative to the workspace root, where the merged security rules file is written.
104
104
  */
105
- securityRuleFile?: string;
105
+ readonly securityRuleFile?: string;
106
106
  };
107
107
  /**
108
108
  * Configuration for the Identity Platform (formerly Firebase Auth) service.
109
109
  */
110
- identityPlatform?: {
110
+ readonly identityPlatform?: {
111
111
  /**
112
112
  * Configuration for the emulator.
113
113
  */
114
- emulator?: {
114
+ readonly emulator?: {
115
115
  /**
116
116
  * The name of the local Docker container running the emulator.
117
117
  */
118
- containerName?: string;
118
+ readonly containerName?: string;
119
119
  };
120
120
  };
121
121
  /**
122
122
  * Configuration for the Firestore service.
123
123
  */
124
- firestore?: {
124
+ readonly firestore?: {
125
125
  /**
126
126
  * Configuration for the emulator.
127
127
  */
128
- emulator?: {
128
+ readonly emulator?: {
129
129
  /**
130
130
  * The name of the local Docker container running the emulator.
131
131
  */
132
- containerName?: string;
132
+ readonly containerName?: string;
133
133
  };
134
134
  /**
135
135
  * A list of glob patterns to find files in the workspace defining Firestore security rules.
136
136
  */
137
- securityRuleFiles?: string[];
137
+ readonly securityRuleFiles?: string[];
138
138
  /**
139
139
  * The file path, relative to the workspace root, where the merged security rules file is written.
140
140
  */
141
- securityRuleFile?: string;
141
+ readonly securityRuleFile?: string;
142
142
  };
143
143
  /**
144
144
  * Configuration for the Pub/Sub service.
145
145
  */
146
- pubSub?: {
146
+ readonly pubSub?: {
147
147
  /**
148
148
  * Configuration for the emulator.
149
149
  */
150
- emulator?: {
150
+ readonly emulator?: {
151
151
  /**
152
152
  * The name of the local Docker container running the emulator.
153
153
  */
154
- containerName?: string;
154
+ readonly containerName?: string;
155
155
  };
156
156
  };
157
157
  /**
158
158
  * Configuration for the Spanner service.
159
159
  */
160
- spanner?: {
160
+ readonly spanner?: {
161
161
  /**
162
162
  * Configuration for the emulator.
163
163
  */
164
- emulator?: {
164
+ readonly emulator?: {
165
165
  /**
166
166
  * The name of the local Docker container running the emulator.
167
167
  */
168
- containerName?: string;
168
+ readonly containerName?: string;
169
169
  /**
170
170
  * The version of the Spanner emulator Docker image to use.
171
171
  */
172
- version?: string;
172
+ readonly version?: string;
173
173
  /**
174
174
  * The name of the Spanner instance that will automatically be created in the emulator.
175
175
  */
176
- instanceName?: string;
176
+ readonly instanceName?: string;
177
177
  };
178
178
  /**
179
179
  * Defines how DDLs are found for the Spanner databases in the workspace.
180
180
  */
181
- ddls?: {
181
+ readonly ddls?: {
182
182
  /**
183
183
  * The format string using groups from the regular expression used to make the database names.
184
184
  */
185
- format?: string;
185
+ readonly format?: string;
186
186
  /**
187
187
  * A list of glob patterns used to find SQL files containing DDL statements for the Spanner databases.
188
188
  */
189
- globs?: string[];
189
+ readonly globs?: string[];
190
190
  /**
191
191
  * The regular expression used to extract groups from the SQL file paths.
192
192
  */
193
- regularExpression?: string;
193
+ readonly regularExpression?: string;
194
194
  };
195
195
  };
196
+ /**
197
+ * Configuration for Cloud Functions.
198
+ */
199
+ readonly cloudFunctions?: {
200
+ /**
201
+ * The Cloud Storage URI where Cloud Functions archives should be uploaded.
202
+ */
203
+ readonly archivesStorageLocation?: string;
204
+ };
205
+ /**
206
+ * Configuration for Cloud Run containers.
207
+ */
208
+ readonly cloudRun?: {
209
+ /**
210
+ * The Docker repository where Cloud Run containers should be uploaded.
211
+ */
212
+ readonly dockerRepository?: string;
213
+ };
196
214
  };
197
215
  };
@@ -98,9 +98,7 @@ export class EmulatorStartForSpanner extends EmulatorStart {
98
98
  context.logger.info(`🗃️ Creating Spanner emulator database '${database.id}'.`);
99
99
  // Discarding `DROP TABLE` statements as the emulator does not seem to handle them properly.
100
100
  const ddls = database.ddls.filter((statement) => !statement.toUpperCase().startsWith('DROP TABLE'));
101
- const [_, operation] = await instance.createDatabase(database.id, {
102
- schema: ddls,
103
- });
101
+ const operation = (await instance.createDatabase(database.id, { schema: ddls }))[1];
104
102
  await operation.promise();
105
103
  }));
106
104
  return databases.length === 0
@@ -21,7 +21,7 @@ const TOKEN_TTL = 3600;
21
21
  /**
22
22
  * Generates a new AppCheck token.
23
23
  */
24
- let GoogleAppCheckGenerateToken = class GoogleAppCheckGenerateToken extends WorkspaceFunction {
24
+ export 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,4 +64,3 @@ 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
- let GoogleFirebaseStorageMergeRules = class GoogleFirebaseStorageMergeRules extends WorkspaceFunction {
30
+ export let GoogleFirebaseStorageMergeRules = class GoogleFirebaseStorageMergeRules extends WorkspaceFunction {
31
31
  tearDown;
32
32
  async _call(context) {
33
33
  if (this.tearDown) {
@@ -69,4 +69,3 @@ 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
- let GoogleFirestoreMergeRules = class GoogleFirestoreMergeRules extends WorkspaceFunction {
30
+ export let GoogleFirestoreMergeRules = class GoogleFirestoreMergeRules extends WorkspaceFunction {
31
31
  tearDown;
32
32
  async _call(context) {
33
33
  if (this.tearDown) {
@@ -69,4 +69,3 @@ 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 './google-identity-pla
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
- let GoogleIdentityPlatformGenerateToken = class GoogleIdentityPlatformGenerateToken extends WorkspaceFunction {
24
+ export 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,4 +71,3 @@ 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
- let GoogleServicesEnable = class GoogleServicesEnable extends WorkspaceFunction {
23
+ export let GoogleServicesEnable = class GoogleServicesEnable extends WorkspaceFunction {
24
24
  tearDown;
25
25
  async _call(context) {
26
26
  if (this.tearDown) {
@@ -72,4 +72,3 @@ 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 };
@@ -15,8 +15,10 @@ import { GoogleIdentityPlatformGenerateCustomToken } from './google-identity-pla
15
15
  import { GoogleIdentityPlatformGenerateToken } from './google-identity-platform-generate-token.js';
16
16
  import { GoogleServicesEnable } from './google-services-enable.js';
17
17
  import { GoogleSpannerListDatabases } from './google-spanner-list-databases.js';
18
- import { ProjectGetArtefactDestinationForServiceContainer } from './project-get-artefact-destination-service-container.js';
18
+ import { ProjectGetArtefactDestinationForCloudFunctions } from './project-get-artefact-destination-cloud-functions.js';
19
+ import { ProjectGetArtefactDestinationForCloudRun } from './project-get-artefact-destination-cloud-run.js';
20
+ import { ProjectPushArtefactForCloudFunctions } from './project-push-artefact-cloud-functions.js';
19
21
  import { SecretFetchForGoogleSecretManager } from './secret-fetch-secret-manager.js';
20
22
  export function registerFunctions(context) {
21
- context.registerFunctionImplementations(EmulatorStartForFirebaseStorage, EmulatorStartForFirestore, EmulatorStartForIdentityPlatform, EmulatorStartForPubSub, EmulatorStartForSpanner, EmulatorStopForFirebaseStorage, EmulatorStopForFirestore, EmulatorStopForIdentityPlatform, EmulatorStopForPubSub, EmulatorStopForSpanner, GoogleAppCheckGenerateToken, GoogleFirebaseStorageMergeRules, GoogleFirestoreMergeRules, GoogleIdentityPlatformGenerateCustomToken, GoogleIdentityPlatformGenerateToken, GoogleServicesEnable, GoogleSpannerListDatabases, ProjectGetArtefactDestinationForServiceContainer, SecretFetchForGoogleSecretManager);
23
+ context.registerFunctionImplementations(EmulatorStartForFirebaseStorage, EmulatorStartForFirestore, EmulatorStartForIdentityPlatform, EmulatorStartForPubSub, EmulatorStartForSpanner, EmulatorStopForFirebaseStorage, EmulatorStopForFirestore, EmulatorStopForIdentityPlatform, EmulatorStopForPubSub, EmulatorStopForSpanner, GoogleAppCheckGenerateToken, GoogleFirebaseStorageMergeRules, GoogleFirestoreMergeRules, GoogleIdentityPlatformGenerateCustomToken, GoogleIdentityPlatformGenerateToken, GoogleServicesEnable, GoogleSpannerListDatabases, ProjectGetArtefactDestinationForCloudFunctions, ProjectGetArtefactDestinationForCloudRun, ProjectPushArtefactForCloudFunctions, SecretFetchForGoogleSecretManager);
22
24
  }
@@ -0,0 +1,11 @@
1
+ import { WorkspaceContext } from '@causa/workspace';
2
+ import { ProjectGetArtefactDestination } from '@causa/workspace-core';
3
+ /**
4
+ * Implements the {@link ProjectGetArtefactDestination} function for Cloud Functions projects.
5
+ * Cloud Functions archives are stored in a Google Cloud Storage bucket, the destination of which must be
6
+ * defined in the `google.cloudFunctions.archivesStorageLocation` configuration.
7
+ */
8
+ export declare class ProjectGetArtefactDestinationForCloudFunctions extends ProjectGetArtefactDestination {
9
+ _call(context: WorkspaceContext): Promise<string>;
10
+ _supports(context: WorkspaceContext): boolean;
11
+ }
@@ -0,0 +1,20 @@
1
+ import { ProjectGetArtefactDestination, } from '@causa/workspace-core';
2
+ /**
3
+ * Implements the {@link ProjectGetArtefactDestination} function for Cloud Functions projects.
4
+ * Cloud Functions archives are stored in a Google Cloud Storage bucket, the destination of which must be
5
+ * defined in the `google.cloudFunctions.archivesStorageLocation` configuration.
6
+ */
7
+ export class ProjectGetArtefactDestinationForCloudFunctions extends ProjectGetArtefactDestination {
8
+ async _call(context) {
9
+ const projectName = context.getOrThrow('project.name');
10
+ const archivesStorageLocation = context
11
+ .asConfiguration()
12
+ .getOrThrow('google.cloudFunctions.archivesStorageLocation');
13
+ return `${archivesStorageLocation}/${projectName}/${this.tag}.zip`;
14
+ }
15
+ _supports(context) {
16
+ const conf = context.asConfiguration();
17
+ return (conf.get('project.type') === 'serverlessFunctions' &&
18
+ conf.get('serverlessFunctions.platform') === 'google.cloudFunctions');
19
+ }
20
+ }
@@ -4,7 +4,7 @@ import { ProjectGetArtefactDestination } from '@causa/workspace-core';
4
4
  * Implements the {@link ProjectGetArtefactDestination} function for Cloud Run services.
5
5
  * The destination Docker repository is expected to be defined in `google.cloudRun.dockerRepository`.
6
6
  */
7
- export declare class ProjectGetArtefactDestinationForServiceContainer extends ProjectGetArtefactDestination {
7
+ export declare class ProjectGetArtefactDestinationForCloudRun extends ProjectGetArtefactDestination {
8
8
  _call(context: WorkspaceContext): Promise<string>;
9
9
  _supports(context: WorkspaceContext): boolean;
10
10
  }
@@ -3,10 +3,12 @@ import { ProjectGetArtefactDestination, } from '@causa/workspace-core';
3
3
  * Implements the {@link ProjectGetArtefactDestination} function for Cloud Run services.
4
4
  * The destination Docker repository is expected to be defined in `google.cloudRun.dockerRepository`.
5
5
  */
6
- export class ProjectGetArtefactDestinationForServiceContainer extends ProjectGetArtefactDestination {
6
+ export class ProjectGetArtefactDestinationForCloudRun extends ProjectGetArtefactDestination {
7
7
  async _call(context) {
8
8
  const projectName = context.getOrThrow('project.name');
9
- const dockerRepository = context.getOrThrow('google.cloudRun.dockerRepository');
9
+ const dockerRepository = context
10
+ .asConfiguration()
11
+ .getOrThrow('google.cloudRun.dockerRepository');
10
12
  return `${dockerRepository}/${projectName}:${this.tag}`;
11
13
  }
12
14
  _supports(context) {
@@ -0,0 +1,11 @@
1
+ import { WorkspaceContext } from '@causa/workspace';
2
+ import { ProjectPushArtefact } from '@causa/workspace-core';
3
+ /**
4
+ * Implements the {@link ProjectPushArtefact} function for Cloud Functions projects.
5
+ * This copies the local archive of the Cloud Functions project to the Google Cloud Storage URI set in
6
+ * {@link ProjectPushArtefact.destination}.
7
+ */
8
+ export declare class ProjectPushArtefactForCloudFunctions extends ProjectPushArtefact {
9
+ _call(context: WorkspaceContext): Promise<string>;
10
+ _supports(context: WorkspaceContext): boolean;
11
+ }
@@ -0,0 +1,31 @@
1
+ import { ArtefactAlreadyExistsError, ProjectPushArtefact, } from '@causa/workspace-core';
2
+ import { rm } from 'fs/promises';
3
+ import { CloudStorageService } from '../services/index.js';
4
+ /**
5
+ * Implements the {@link ProjectPushArtefact} function for Cloud Functions projects.
6
+ * This copies the local archive of the Cloud Functions project to the Google Cloud Storage URI set in
7
+ * {@link ProjectPushArtefact.destination}.
8
+ */
9
+ export class ProjectPushArtefactForCloudFunctions extends ProjectPushArtefact {
10
+ async _call(context) {
11
+ context.logger.info(`🚚 Pushing Cloud Functions archive to Cloud Storage.`);
12
+ const storageService = context.service(CloudStorageService);
13
+ const destination = storageService.getFileFromGsUri(this.destination);
14
+ if (!this.overwrite) {
15
+ const [exists] = await destination.exists();
16
+ if (exists) {
17
+ throw new ArtefactAlreadyExistsError(this.destination);
18
+ }
19
+ }
20
+ await destination.bucket.upload(this.artefact, { destination });
21
+ context.logger.info(`🚚 Successfully pushed archive to '${this.destination}'.`);
22
+ context.logger.debug(`🔥 Removing local artefact '${this.artefact}'.`);
23
+ await rm(this.artefact);
24
+ return this.destination;
25
+ }
26
+ _supports(context) {
27
+ const conf = context.asConfiguration();
28
+ return (conf.get('project.type') === 'serverlessFunctions' &&
29
+ conf.get('serverlessFunctions.platform') === 'google.cloudFunctions');
30
+ }
31
+ }
@@ -4,3 +4,5 @@ export { FirebaseEmulatorService } from './firebase-emulator.js';
4
4
  export { GcloudEmulatorService } from './gcloud-emulator.js';
5
5
  export { GoogleApisService } from './google-apis.js';
6
6
  export { GoogleSecretManagerService } from './secret-manager.js';
7
+ export * from './storage.errors.js';
8
+ export { CloudStorageService } from './storage.js';
@@ -4,3 +4,5 @@ export { FirebaseEmulatorService } from './firebase-emulator.js';
4
4
  export { GcloudEmulatorService } from './gcloud-emulator.js';
5
5
  export { GoogleApisService } from './google-apis.js';
6
6
  export { GoogleSecretManagerService } from './secret-manager.js';
7
+ export * from './storage.errors.js';
8
+ export { CloudStorageService } from './storage.js';
@@ -13,9 +13,9 @@ export class GoogleSecretManagerService {
13
13
  */
14
14
  client;
15
15
  constructor(context) {
16
+ const conf = context.asConfiguration();
16
17
  this.defaultProject =
17
- context.get('google.secretManager.project') ??
18
- context.get('google.project');
18
+ conf.get('google.secretManager.project') ?? conf.get('google.project');
19
19
  this.client = new SecretManagerServiceClient();
20
20
  }
21
21
  }
@@ -0,0 +1,33 @@
1
+ import { File, Storage } from '@google-cloud/storage';
2
+ /**
3
+ * A service for interacting with Google Cloud Storage.
4
+ * It exposes a singleton instance of the Google Cloud Storage client.
5
+ */
6
+ export declare class CloudStorageService {
7
+ readonly storage: Storage;
8
+ constructor();
9
+ /**
10
+ * Parses a Google Cloud Storage URI into a bucket and path.
11
+ * Cloud Storage URIs are of the form `gs://<bucket>/<path>`.
12
+ *
13
+ * @param uri The Google Cloud Storage URI.
14
+ * @returns The name of the Google Cloud Storage bucket and the path within the bucket.
15
+ */
16
+ parseGsUri(uri: string): {
17
+ /**
18
+ * The name of the Google Cloud Storage bucket.
19
+ */
20
+ readonly bucket: string;
21
+ /**
22
+ * The path within the bucket.
23
+ */
24
+ readonly path: string;
25
+ };
26
+ /**
27
+ * Parses a Google Cloud Storage URI into a {@link File}.
28
+ *
29
+ * @param uri The Google Cloud Storage URI.
30
+ * @returns The {@link File} at the given URI.
31
+ */
32
+ getFileFromGsUri(uri: string): File;
33
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * An error thrown when the Cloud Storage URI is invalid.
3
+ * Cloud Storage URIs are of the form `gs://<bucket>/<path>`.
4
+ */
5
+ export declare class InvalidCloudStorageUriError extends Error {
6
+ readonly uri: string;
7
+ constructor(uri: string);
8
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * An error thrown when the Cloud Storage URI is invalid.
3
+ * Cloud Storage URIs are of the form `gs://<bucket>/<path>`.
4
+ */
5
+ export class InvalidCloudStorageUriError extends Error {
6
+ uri;
7
+ constructor(uri) {
8
+ super(`Invalid Google Cloud Storage URI: '${uri}'.`);
9
+ this.uri = uri;
10
+ }
11
+ }
@@ -0,0 +1,36 @@
1
+ import { Storage } from '@google-cloud/storage';
2
+ import { InvalidCloudStorageUriError } from './storage.errors.js';
3
+ /**
4
+ * A service for interacting with Google Cloud Storage.
5
+ * It exposes a singleton instance of the Google Cloud Storage client.
6
+ */
7
+ export class CloudStorageService {
8
+ storage;
9
+ constructor() {
10
+ this.storage = new Storage();
11
+ }
12
+ /**
13
+ * Parses a Google Cloud Storage URI into a bucket and path.
14
+ * Cloud Storage URIs are of the form `gs://<bucket>/<path>`.
15
+ *
16
+ * @param uri The Google Cloud Storage URI.
17
+ * @returns The name of the Google Cloud Storage bucket and the path within the bucket.
18
+ */
19
+ parseGsUri(uri) {
20
+ const match = uri.match(/^gs:\/\/([^/]+)\/(.*)$/);
21
+ if (!match) {
22
+ throw new InvalidCloudStorageUriError(uri);
23
+ }
24
+ return { bucket: match[1], path: match[2] };
25
+ }
26
+ /**
27
+ * Parses a Google Cloud Storage URI into a {@link File}.
28
+ *
29
+ * @param uri The Google Cloud Storage URI.
30
+ * @returns The {@link File} at the given URI.
31
+ */
32
+ getFileFromGsUri(uri) {
33
+ const { bucket, path } = this.parseGsUri(uri);
34
+ return this.storage.bucket(bucket).file(path);
35
+ }
36
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@causa/workspace-google",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
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",
@@ -29,18 +29,19 @@
29
29
  "test:cov": "npm run test -- --coverage"
30
30
  },
31
31
  "dependencies": {
32
- "@causa/cli": ">= 0.3.3 < 1.0.0",
33
- "@causa/workspace": ">= 0.7.0 < 1.0.0",
34
- "@causa/workspace-core": ">= 0.4.0 < 1.0.0",
32
+ "@causa/cli": ">= 0.4.0 < 1.0.0",
33
+ "@causa/workspace": ">= 0.10.0 < 1.0.0",
34
+ "@causa/workspace-core": ">= 0.7.0 < 1.0.0",
35
35
  "@google-cloud/apikeys": "^0.2.2",
36
36
  "@google-cloud/iam-credentials": "^2.0.4",
37
- "@google-cloud/pubsub": "^3.6.0",
37
+ "@google-cloud/pubsub": "^3.7.1",
38
38
  "@google-cloud/secret-manager": "^4.2.2",
39
39
  "@google-cloud/service-usage": "^2.2.2",
40
- "@google-cloud/spanner": "^6.10.0",
40
+ "@google-cloud/spanner": "^6.11.0",
41
+ "@google-cloud/storage": "^6.11.0",
41
42
  "class-validator": "^0.14.0",
42
- "firebase": "^9.22.0",
43
- "firebase-admin": "^11.8.0",
43
+ "firebase": "^9.22.2",
44
+ "firebase-admin": "^11.9.0",
44
45
  "globby": "^13.1.4",
45
46
  "google-gax": "^3.6.0",
46
47
  "googleapis": "^118.0.0",
@@ -48,18 +49,19 @@
48
49
  },
49
50
  "devDependencies": {
50
51
  "@tsconfig/node18": "^2.0.1",
51
- "@types/jest": "^29.5.1",
52
- "@types/node": "^18.16.14",
53
- "@types/uuid": "^9.0.1",
52
+ "@types/jest": "^29.5.2",
53
+ "@types/node": "^18.16.16",
54
+ "@types/uuid": "^9.0.2",
55
+ "@typescript-eslint/eslint-plugin": "^5.59.9",
54
56
  "copyfiles": "^2.4.1",
55
- "eslint": "^8.41.0",
57
+ "eslint": "^8.42.0",
56
58
  "eslint-config-prettier": "^8.8.0",
57
59
  "eslint-plugin-prettier": "^4.2.1",
58
60
  "jest": "^29.5.0",
59
- "jest-extended": "^3.2.4",
61
+ "jest-extended": "^4.0.0",
60
62
  "rimraf": "^5.0.1",
61
63
  "ts-jest": "^29.1.0",
62
64
  "ts-node": "^10.9.1",
63
- "typescript": "^5.0.4"
65
+ "typescript": "^5.1.3"
64
66
  }
65
67
  }