@causa/workspace-google 0.4.0 → 0.5.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 +6 -0
- package/dist/backfilling/bigquery.d.ts +53 -0
- package/dist/backfilling/bigquery.js +99 -0
- package/dist/backfilling/index.d.ts +2 -0
- package/dist/backfilling/index.js +2 -0
- package/dist/backfilling/pubsub.d.ts +34 -0
- package/dist/backfilling/pubsub.js +63 -0
- package/dist/configurations/google.d.ts +9 -0
- package/dist/functions/emulator/index.d.ts +10 -0
- package/dist/functions/emulator/index.js +10 -0
- package/dist/functions/{emulator-start-firebase-storage.js → emulator/start-firebase-storage.js} +4 -4
- package/dist/functions/{emulator-start-firestore.js → emulator/start-firestore.js} +3 -3
- package/dist/functions/{emulator-start-identity-platform.js → emulator/start-identity-platform.js} +3 -3
- package/dist/functions/{emulator-start-pubsub.js → emulator/start-pubsub.js} +3 -3
- package/dist/functions/{emulator-start-spanner.js → emulator/start-spanner.js} +3 -3
- package/dist/functions/{emulator-stop-firebase-storage.js → emulator/stop-firebase-storage.js} +1 -1
- package/dist/functions/{emulator-stop-firestore.js → emulator/stop-firestore.js} +1 -1
- package/dist/functions/{emulator-stop-identity-platform.js → emulator/stop-identity-platform.js} +1 -1
- package/dist/functions/{emulator-stop-pubsub.js → emulator/stop-pubsub.js} +1 -1
- package/dist/functions/{emulator-stop-spanner.js → emulator/stop-spanner.js} +1 -1
- package/dist/functions/event-topic/broker-create-topic-pubsub.d.ts +11 -0
- package/dist/functions/event-topic/broker-create-topic-pubsub.js +27 -0
- package/dist/functions/event-topic/broker-create-trigger-cloud-run.d.ts +11 -0
- package/dist/functions/event-topic/broker-create-trigger-cloud-run.js +38 -0
- package/dist/functions/event-topic/broker-delete-topic-pubsub.d.ts +10 -0
- package/dist/functions/event-topic/broker-delete-topic-pubsub.js +26 -0
- package/dist/functions/event-topic/broker-delete-trigger-resource-cloud-run-invoker-role.d.ts +11 -0
- package/dist/functions/event-topic/broker-delete-trigger-resource-cloud-run-invoker-role.js +29 -0
- package/dist/functions/event-topic/broker-delete-trigger-resource-pubsub-subscription.d.ts +10 -0
- package/dist/functions/event-topic/broker-delete-trigger-resource-pubsub-subscription.js +31 -0
- package/dist/functions/event-topic/broker-delete-trigger-resource-service-account.d.ts +11 -0
- package/dist/functions/event-topic/broker-delete-trigger-resource-service-account.js +28 -0
- package/dist/functions/event-topic/broker-get-topic-id-pubsub.d.ts +10 -0
- package/dist/functions/event-topic/broker-get-topic-id-pubsub.js +16 -0
- package/dist/functions/event-topic/broker-publish-events-google.d.ts +17 -0
- package/dist/functions/event-topic/broker-publish-events-google.js +45 -0
- package/dist/functions/event-topic/index.d.ts +8 -0
- package/dist/functions/event-topic/index.js +8 -0
- package/dist/functions/{google-app-check-generate-token.js → google-app-check/generate-token.js} +2 -2
- package/dist/functions/google-app-check/index.d.ts +1 -0
- package/dist/functions/google-app-check/index.js +1 -0
- package/dist/functions/google-firebase-storage/index.d.ts +1 -0
- package/dist/functions/google-firebase-storage/index.js +1 -0
- package/dist/functions/{google-firebase-storage-merge-rules.d.ts → google-firebase-storage/merge-rules.d.ts} +1 -1
- package/dist/functions/{google-firebase-storage-merge-rules.js → google-firebase-storage/merge-rules.js} +2 -2
- package/dist/functions/google-firestore/index.d.ts +1 -0
- package/dist/functions/google-firestore/index.js +1 -0
- package/dist/functions/{google-firestore-merge-rules.d.ts → google-firestore/merge-rules.d.ts} +1 -1
- package/dist/functions/{google-firestore-merge-rules.js → google-firestore/merge-rules.js} +2 -2
- package/dist/functions/{google-identity-platform-generate-custom-token.js → google-identity-platform/generate-custom-token.js} +1 -1
- package/dist/functions/{google-identity-platform-generate-token.js → google-identity-platform/generate-token.js} +3 -3
- package/dist/functions/google-identity-platform/index.d.ts +2 -0
- package/dist/functions/google-identity-platform/index.js +2 -0
- package/dist/functions/google-pubsub/index.d.ts +1 -0
- package/dist/functions/google-pubsub/index.js +1 -0
- package/dist/functions/{google-services-enable.d.ts → google-services/enable.d.ts} +1 -1
- package/dist/functions/{google-services-enable.js → google-services/enable.js} +1 -1
- package/dist/functions/google-services/index.d.ts +1 -0
- package/dist/functions/google-services/index.js +1 -0
- package/dist/functions/google-spanner/index.d.ts +2 -0
- package/dist/functions/google-spanner/index.js +2 -0
- package/dist/functions/{google-spanner-write-databases.js → google-spanner/write-databases.js} +1 -1
- package/dist/functions/index.js +12 -24
- package/dist/functions/project/index.d.ts +3 -0
- package/dist/functions/project/index.js +3 -0
- package/dist/functions/{project-push-artefact-cloud-functions.js → project/push-artefact-cloud-functions.js} +1 -1
- package/dist/functions/{secret-fetch-secret-manager.js → secret/fetch-secret-manager.js} +1 -1
- package/dist/functions/secret/index.d.ts +1 -0
- package/dist/functions/secret/index.js +1 -0
- package/dist/services/bigquery.d.ts +16 -0
- package/dist/services/bigquery.js +19 -0
- package/dist/services/cloud-run-pubsub-trigger.d.ts +101 -0
- package/dist/services/cloud-run-pubsub-trigger.js +177 -0
- package/dist/services/cloud-run.d.ts +35 -0
- package/dist/services/cloud-run.js +72 -0
- package/dist/services/iam.d.ts +43 -0
- package/dist/services/iam.js +65 -0
- package/dist/services/index.d.ts +6 -0
- package/dist/services/index.js +6 -0
- package/dist/services/pubsub.d.ts +41 -0
- package/dist/services/pubsub.js +62 -0
- package/dist/services/resource-manager.d.ts +18 -0
- package/dist/services/resource-manager.js +35 -0
- package/package.json +16 -12
- /package/dist/functions/{emulator-start-firebase-storage.d.ts → emulator/start-firebase-storage.d.ts} +0 -0
- /package/dist/functions/{emulator-start-firestore.d.ts → emulator/start-firestore.d.ts} +0 -0
- /package/dist/functions/{emulator-start-identity-platform.d.ts → emulator/start-identity-platform.d.ts} +0 -0
- /package/dist/functions/{emulator-start-pubsub.d.ts → emulator/start-pubsub.d.ts} +0 -0
- /package/dist/functions/{emulator-start-spanner.d.ts → emulator/start-spanner.d.ts} +0 -0
- /package/dist/functions/{emulator-stop-firebase-storage.d.ts → emulator/stop-firebase-storage.d.ts} +0 -0
- /package/dist/functions/{emulator-stop-firestore.d.ts → emulator/stop-firestore.d.ts} +0 -0
- /package/dist/functions/{emulator-stop-identity-platform.d.ts → emulator/stop-identity-platform.d.ts} +0 -0
- /package/dist/functions/{emulator-stop-pubsub.d.ts → emulator/stop-pubsub.d.ts} +0 -0
- /package/dist/functions/{emulator-stop-spanner.d.ts → emulator/stop-spanner.d.ts} +0 -0
- /package/dist/functions/{google-app-check-generate-token.d.ts → google-app-check/generate-token.d.ts} +0 -0
- /package/dist/functions/{google-identity-platform-generate-custom-token.d.ts → google-identity-platform/generate-custom-token.d.ts} +0 -0
- /package/dist/functions/{google-identity-platform-generate-token.d.ts → google-identity-platform/generate-token.d.ts} +0 -0
- /package/dist/functions/{google-pubsub-write-topics.d.ts → google-pubsub/write-topics.d.ts} +0 -0
- /package/dist/functions/{google-pubsub-write-topics.js → google-pubsub/write-topics.js} +0 -0
- /package/dist/functions/{google-spanner-list-databases.d.ts → google-spanner/list-databases.d.ts} +0 -0
- /package/dist/functions/{google-spanner-list-databases.js → google-spanner/list-databases.js} +0 -0
- /package/dist/functions/{google-spanner-write-databases.d.ts → google-spanner/write-databases.d.ts} +0 -0
- /package/dist/functions/{project-get-artefact-destination-cloud-functions.d.ts → project/get-artefact-destination-cloud-functions.d.ts} +0 -0
- /package/dist/functions/{project-get-artefact-destination-cloud-functions.js → project/get-artefact-destination-cloud-functions.js} +0 -0
- /package/dist/functions/{project-get-artefact-destination-cloud-run.d.ts → project/get-artefact-destination-cloud-run.d.ts} +0 -0
- /package/dist/functions/{project-get-artefact-destination-cloud-run.js → project/get-artefact-destination-cloud-run.js} +0 -0
- /package/dist/functions/{project-push-artefact-cloud-functions.d.ts → project/push-artefact-cloud-functions.d.ts} +0 -0
- /package/dist/functions/{secret-fetch-secret-manager.d.ts → secret/fetch-secret-manager.d.ts} +0 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { EventTopicTriggerCreationError } from '@causa/workspace-core';
|
|
2
|
+
import { Subscription } from '@google-cloud/pubsub';
|
|
3
|
+
import { randomBytes } from 'crypto';
|
|
4
|
+
import { CloudRunService } from './cloud-run.js';
|
|
5
|
+
import { IamService } from './iam.js';
|
|
6
|
+
import { PubSubService } from './pubsub.js';
|
|
7
|
+
/**
|
|
8
|
+
* A service that manages (creates) Pub/Sub triggers pointing to Cloud Run services.
|
|
9
|
+
* This is used for backfilling operations. The reason it is a service is to persist temporary service accounts and IAM
|
|
10
|
+
* grants, such that they can be reused within the same backfilling operation.
|
|
11
|
+
*/
|
|
12
|
+
export class CloudRunPubSubTriggerService {
|
|
13
|
+
/**
|
|
14
|
+
* The service managing Cloud Run resources.
|
|
15
|
+
*/
|
|
16
|
+
cloudRunService;
|
|
17
|
+
/**
|
|
18
|
+
* The service managing Pub/Sub resources.
|
|
19
|
+
*/
|
|
20
|
+
pubSubService;
|
|
21
|
+
/**
|
|
22
|
+
* The service managing IAM permissions.
|
|
23
|
+
*/
|
|
24
|
+
iamService;
|
|
25
|
+
/**
|
|
26
|
+
* A set of IAM bindings that have already been created.
|
|
27
|
+
* This is used to avoid creating the same bindings multiple times within a single backfill operation.
|
|
28
|
+
* An IAM binding allows the service account used by Pub/Sub for a single backfill operation to invoke a Cloud Run
|
|
29
|
+
* service. The format of strings in this set is the one used to reference bindings during deletion as well, i.e.
|
|
30
|
+
* `projects/<projectId>/locations/<location>/services/<name>/invokerBindings/<serviceAccountEmail>`.
|
|
31
|
+
*/
|
|
32
|
+
invokerBindingIds = new Set();
|
|
33
|
+
/**
|
|
34
|
+
* A map of backfill IDs to promises resolving to the service account email used by Pub/Sub to invoke Cloud Run
|
|
35
|
+
* services for the backfill.
|
|
36
|
+
*/
|
|
37
|
+
backfillInvokerServiceAccounts = {};
|
|
38
|
+
/**
|
|
39
|
+
* The logger used by this service.
|
|
40
|
+
*/
|
|
41
|
+
logger;
|
|
42
|
+
/**
|
|
43
|
+
* The ID of the GCP project in which resources should be created.
|
|
44
|
+
*/
|
|
45
|
+
projectId;
|
|
46
|
+
constructor(context) {
|
|
47
|
+
this.cloudRunService = context.service(CloudRunService);
|
|
48
|
+
this.pubSubService = context.service(PubSubService);
|
|
49
|
+
this.iamService = context.service(IamService);
|
|
50
|
+
this.logger = context.logger;
|
|
51
|
+
this.projectId = context
|
|
52
|
+
.asConfiguration()
|
|
53
|
+
.getOrThrow('google.project');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Creates a service account that should be used by Pub/Sub to invoke Cloud Run services for a given backfill
|
|
57
|
+
* operation.
|
|
58
|
+
*
|
|
59
|
+
* @param backfillId The ID of the backfilling operation.
|
|
60
|
+
* @returns The service account email that should be used by Pub/Sub when pushing messages to Cloud Run services,
|
|
61
|
+
* along with the corresponding resource ID that should be deleted as part of the backfill cleaning.
|
|
62
|
+
*/
|
|
63
|
+
async createBackfillInvokerServiceAccount(backfillId) {
|
|
64
|
+
const serviceAccountId = `backfill-pubsub-${backfillId}`;
|
|
65
|
+
const bindings = await this.pubSubService.getPushServiceAccountBindings();
|
|
66
|
+
this.logger.info(`🛂 Creating Pub/Sub backfilling service account '${serviceAccountId}'.`);
|
|
67
|
+
const serviceAccount = await this.iamService.createServiceAccount(this.projectId, serviceAccountId, { bindings });
|
|
68
|
+
return {
|
|
69
|
+
serviceAccountEmail: serviceAccount.email ?? '',
|
|
70
|
+
resourceId: serviceAccount.name ?? null,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Gets or creates a service account that should be used by Pub/Sub to invoke Cloud Run services for a given backfill
|
|
75
|
+
* operation.
|
|
76
|
+
*
|
|
77
|
+
* @param backfillId The ID of the backfilling operation.
|
|
78
|
+
* @returns The service account email that should be used by Pub/Sub when pushing messages to Cloud Run services.
|
|
79
|
+
* If `resourceId` is not `null`, it should be added to the list of resources to delete as part of the backfill
|
|
80
|
+
* cleaning.
|
|
81
|
+
*/
|
|
82
|
+
async getBackfillInvokerServiceAccount(backfillId) {
|
|
83
|
+
const existingServiceAccountPromise = this.backfillInvokerServiceAccounts[backfillId];
|
|
84
|
+
if (existingServiceAccountPromise) {
|
|
85
|
+
return {
|
|
86
|
+
serviceAccountEmail: await existingServiceAccountPromise,
|
|
87
|
+
resourceId: null,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const creationPromise = this.createBackfillInvokerServiceAccount(backfillId);
|
|
91
|
+
this.backfillInvokerServiceAccounts[backfillId] = creationPromise.then((result) => result.serviceAccountEmail);
|
|
92
|
+
return await creationPromise;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Grants the invoker role to a service account for a given Cloud Run service.
|
|
96
|
+
* This is used to allow Pub/Sub to invoke the Cloud Run service.
|
|
97
|
+
* If a resource ID string is returned, it should be added to the list of resources to delete as part of the backfill
|
|
98
|
+
* cleaning.
|
|
99
|
+
*
|
|
100
|
+
* @param serviceId The ID of the Cloud Run service.
|
|
101
|
+
* @param pubSubServiceAccount The service account email for which the invoker role should be granted.
|
|
102
|
+
* @returns The resource ID of the IAM binding that was created, or `null` if the binding already existed.
|
|
103
|
+
*/
|
|
104
|
+
async grantPubSubInvokerRole(serviceId, pubSubServiceAccount) {
|
|
105
|
+
const invokerBindingId = `${serviceId}/invokerBindings/${pubSubServiceAccount}`;
|
|
106
|
+
if (this.invokerBindingIds.has(invokerBindingId)) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
this.invokerBindingIds.add(invokerBindingId);
|
|
110
|
+
this.logger.info(`🛂 Granting invoker IAM role to backfilling service account '${pubSubServiceAccount}' for Cloud Run service '${serviceId}'.`);
|
|
111
|
+
await this.cloudRunService.addInvokerBinding(serviceId, pubSubServiceAccount);
|
|
112
|
+
return invokerBindingId;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Creates a Pub/Sub subscription that will push messages to a Cloud Run service.
|
|
116
|
+
*
|
|
117
|
+
* @param backfillId The ID of the backfilling operation.
|
|
118
|
+
* @param topicId The ID of the Pub/Sub topic to subscribe to.
|
|
119
|
+
* @param serviceId The ID of the Cloud Run service to invoke.
|
|
120
|
+
* @param path The HTTP endpoint of the Cloud Run service to invoke.
|
|
121
|
+
* @param serviceAccountEmail The service account email that should be used by Pub/Sub when pushing messages to the
|
|
122
|
+
* Cloud Run service.
|
|
123
|
+
* @returns The ID of the Pub/Sub subscription that was created. It should be added to the list of resources to delete
|
|
124
|
+
* as part of the backfill cleaning.
|
|
125
|
+
*/
|
|
126
|
+
async createSubscription(backfillId, topicId, serviceId, path, serviceAccountEmail) {
|
|
127
|
+
const serviceUri = await this.cloudRunService.getServiceUri(serviceId);
|
|
128
|
+
const pushEndpoint = `${serviceUri}${path}`;
|
|
129
|
+
const subscriptionId = Subscription.formatName_(this.projectId, `backfill-${backfillId}-${randomBytes(3).toString('hex')}`);
|
|
130
|
+
this.logger.info(`📫 Creating Pub/Sub subscription '${subscriptionId}' for Cloud Run service '${serviceId}'.`);
|
|
131
|
+
await this.pubSubService.pubSub.createSubscription(topicId, subscriptionId, {
|
|
132
|
+
pushConfig: { pushEndpoint, oidcToken: { serviceAccountEmail } },
|
|
133
|
+
expirationPolicy: {},
|
|
134
|
+
ackDeadlineSeconds: 60 * 10,
|
|
135
|
+
retryPolicy: {
|
|
136
|
+
minimumBackoff: { seconds: 1, nanos: 0 },
|
|
137
|
+
maximumBackoff: { seconds: 60, nanos: 0 },
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
return subscriptionId;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Creates a Pub/Sub trigger (push subscription) pointing to a Cloud Run service.
|
|
144
|
+
* This requires several resources, such as:
|
|
145
|
+
* - A service account that should be used by Pub/Sub to invoke Cloud Run services for the backfill.
|
|
146
|
+
* - An IAM binding allowing the service account to invoke the Cloud Run service.
|
|
147
|
+
* - A Pub/Sub subscription that will push messages to the Cloud Run service.
|
|
148
|
+
* The IDs of these resources are returned, and should be deleted as part of the backfill cleaning.
|
|
149
|
+
*
|
|
150
|
+
* @param backfillId The ID of the backfilling operation.
|
|
151
|
+
* @param topicId The ID of the Pub/Sub topic to subscribe to.
|
|
152
|
+
* @param serviceId The ID of the Cloud Run service to invoke.
|
|
153
|
+
* @param path The HTTP endpoint of the Cloud Run service to invoke.
|
|
154
|
+
* @returns The IDs of the resources that were created. They should be added to the list of resources to delete as
|
|
155
|
+
* part of the backfill cleaning.
|
|
156
|
+
*/
|
|
157
|
+
async create(backfillId, topicId, serviceId, path) {
|
|
158
|
+
const resourceIds = [];
|
|
159
|
+
try {
|
|
160
|
+
const pubSubInvokerServiceAccount = await this.getBackfillInvokerServiceAccount(backfillId);
|
|
161
|
+
if (pubSubInvokerServiceAccount.resourceId) {
|
|
162
|
+
resourceIds.push(pubSubInvokerServiceAccount.resourceId);
|
|
163
|
+
}
|
|
164
|
+
const { serviceAccountEmail } = pubSubInvokerServiceAccount;
|
|
165
|
+
const iamResourceId = await this.grantPubSubInvokerRole(serviceId, serviceAccountEmail);
|
|
166
|
+
if (iamResourceId) {
|
|
167
|
+
resourceIds.push(iamResourceId);
|
|
168
|
+
}
|
|
169
|
+
const subscriptionId = await this.createSubscription(backfillId, topicId, serviceId, path, serviceAccountEmail);
|
|
170
|
+
resourceIds.push(subscriptionId);
|
|
171
|
+
return resourceIds;
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
throw new EventTopicTriggerCreationError(error, resourceIds);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ServicesClient } from '@google-cloud/run';
|
|
2
|
+
/**
|
|
3
|
+
* A service to manage Cloud Run services.
|
|
4
|
+
*/
|
|
5
|
+
export declare class CloudRunService {
|
|
6
|
+
/**
|
|
7
|
+
* The Cloud Run client.
|
|
8
|
+
*/
|
|
9
|
+
readonly servicesClient: ServicesClient;
|
|
10
|
+
constructor();
|
|
11
|
+
/**
|
|
12
|
+
* Retrieves the URI at which a Cloud Run service is available and can be requested.
|
|
13
|
+
*
|
|
14
|
+
* @param serviceId The ID of the Cloud Run service.
|
|
15
|
+
* This should be in the format `projects/<projectId>/locations/<location>/services/<name>`.
|
|
16
|
+
* @returns The URI at which the service is available.
|
|
17
|
+
*/
|
|
18
|
+
getServiceUri(serviceId: string): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Allows a service account to call a Cloud Run service by editing the IAM policy bindings.
|
|
21
|
+
*
|
|
22
|
+
* @param serviceId The ID of the Cloud Run service.
|
|
23
|
+
* This should be in the format `projects/<projectId>/locations/<location>/services/<name>`.
|
|
24
|
+
* @param serviceAccountEmail The email of the service account that should be allowed to call the service.
|
|
25
|
+
*/
|
|
26
|
+
addInvokerBinding(serviceId: string, serviceAccountEmail: string): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Removes a service account from the list of allowed invokers of a Cloud Run service.
|
|
29
|
+
*
|
|
30
|
+
* @param serviceId The ID of the Cloud Run service.
|
|
31
|
+
* This should be in the format `projects/<projectId>/locations/<location>/services/<name>`.
|
|
32
|
+
* @param serviceAccountEmail The email of the service account that should be removed from the allowed invokers.
|
|
33
|
+
*/
|
|
34
|
+
removeInvokerBinding(serviceId: string, serviceAccountEmail: string): Promise<void>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ServicesClient } from '@google-cloud/run';
|
|
2
|
+
/**
|
|
3
|
+
* The role used to allow a service account to call a Cloud Run service.
|
|
4
|
+
*/
|
|
5
|
+
const INVOKER_ROLE = 'roles/run.invoker';
|
|
6
|
+
/**
|
|
7
|
+
* A service to manage Cloud Run services.
|
|
8
|
+
*/
|
|
9
|
+
export class CloudRunService {
|
|
10
|
+
/**
|
|
11
|
+
* The Cloud Run client.
|
|
12
|
+
*/
|
|
13
|
+
servicesClient;
|
|
14
|
+
constructor() {
|
|
15
|
+
this.servicesClient = new ServicesClient();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Retrieves the URI at which a Cloud Run service is available and can be requested.
|
|
19
|
+
*
|
|
20
|
+
* @param serviceId The ID of the Cloud Run service.
|
|
21
|
+
* This should be in the format `projects/<projectId>/locations/<location>/services/<name>`.
|
|
22
|
+
* @returns The URI at which the service is available.
|
|
23
|
+
*/
|
|
24
|
+
async getServiceUri(serviceId) {
|
|
25
|
+
const [service] = await this.servicesClient.getService({ name: serviceId });
|
|
26
|
+
return service.uri ?? '';
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Allows a service account to call a Cloud Run service by editing the IAM policy bindings.
|
|
30
|
+
*
|
|
31
|
+
* @param serviceId The ID of the Cloud Run service.
|
|
32
|
+
* This should be in the format `projects/<projectId>/locations/<location>/services/<name>`.
|
|
33
|
+
* @param serviceAccountEmail The email of the service account that should be allowed to call the service.
|
|
34
|
+
*/
|
|
35
|
+
async addInvokerBinding(serviceId, serviceAccountEmail) {
|
|
36
|
+
const [policy] = await this.servicesClient.getIamPolicy({
|
|
37
|
+
resource: serviceId,
|
|
38
|
+
});
|
|
39
|
+
const members = [`serviceAccount:${serviceAccountEmail}`];
|
|
40
|
+
const binding = { role: INVOKER_ROLE, members };
|
|
41
|
+
policy.bindings = [...(policy.bindings ?? []), binding];
|
|
42
|
+
await this.servicesClient.setIamPolicy({
|
|
43
|
+
resource: serviceId,
|
|
44
|
+
policy,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Removes a service account from the list of allowed invokers of a Cloud Run service.
|
|
49
|
+
*
|
|
50
|
+
* @param serviceId The ID of the Cloud Run service.
|
|
51
|
+
* This should be in the format `projects/<projectId>/locations/<location>/services/<name>`.
|
|
52
|
+
* @param serviceAccountEmail The email of the service account that should be removed from the allowed invokers.
|
|
53
|
+
*/
|
|
54
|
+
async removeInvokerBinding(serviceId, serviceAccountEmail) {
|
|
55
|
+
const [policy] = await this.servicesClient.getIamPolicy({
|
|
56
|
+
resource: serviceId,
|
|
57
|
+
});
|
|
58
|
+
policy.bindings = (policy.bindings ?? []).flatMap((binding) => {
|
|
59
|
+
if (binding.role !== INVOKER_ROLE || binding.condition) {
|
|
60
|
+
return binding;
|
|
61
|
+
}
|
|
62
|
+
// `includes` ensures that deleted service accounts (in the form of `deleted:serviceAccount:...`) are also removed
|
|
63
|
+
// from the list.
|
|
64
|
+
binding.members = binding.members?.filter((member) => !member.includes(`serviceAccount:${serviceAccountEmail}`));
|
|
65
|
+
return binding.members?.length ? binding : [];
|
|
66
|
+
});
|
|
67
|
+
await this.servicesClient.setIamPolicy({
|
|
68
|
+
resource: serviceId,
|
|
69
|
+
policy,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { WorkspaceContext } from '@causa/workspace';
|
|
2
|
+
import { iam_v1 } from 'googleapis';
|
|
3
|
+
/**
|
|
4
|
+
* A service for interacting with the IAM API.
|
|
5
|
+
*/
|
|
6
|
+
export declare class IamService {
|
|
7
|
+
/**
|
|
8
|
+
* The underlying {@link GoogleApisService} used to get the IAM client.
|
|
9
|
+
*/
|
|
10
|
+
private readonly googleApisService;
|
|
11
|
+
/**
|
|
12
|
+
* A promise that resolves to the singleton IAM client.
|
|
13
|
+
*/
|
|
14
|
+
private iam;
|
|
15
|
+
constructor(context: WorkspaceContext);
|
|
16
|
+
/**
|
|
17
|
+
* Gets or creates the singleton IAM client.
|
|
18
|
+
*
|
|
19
|
+
* @returns The singleton IAM client.
|
|
20
|
+
*/
|
|
21
|
+
private getIam;
|
|
22
|
+
/**
|
|
23
|
+
* Creates a service account, optionally setting the IAM policy bindings for it.
|
|
24
|
+
*
|
|
25
|
+
* @param projectId The ID of the project in which to create the service account.
|
|
26
|
+
* @param accountId The ID of the service account to create.
|
|
27
|
+
* @param options Additional options when creating the account.
|
|
28
|
+
* @returns The created service account.
|
|
29
|
+
*/
|
|
30
|
+
createServiceAccount(projectId: string, accountId: string, options?: {
|
|
31
|
+
/**
|
|
32
|
+
* The IAM policy bindings to set for the service account after its creation.
|
|
33
|
+
*/
|
|
34
|
+
bindings?: iam_v1.Schema$Binding[];
|
|
35
|
+
}): Promise<iam_v1.Schema$ServiceAccount>;
|
|
36
|
+
/**
|
|
37
|
+
* Deletes a service account.
|
|
38
|
+
*
|
|
39
|
+
* @param name The full resource name of the service account to delete, in the format
|
|
40
|
+
* `projects/<projectId>/serviceAccounts/<accountId>`.
|
|
41
|
+
*/
|
|
42
|
+
deleteServiceAccount(name: string): Promise<void>;
|
|
43
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { GoogleApisService } from './google-apis.js';
|
|
2
|
+
/**
|
|
3
|
+
* A service for interacting with the IAM API.
|
|
4
|
+
*/
|
|
5
|
+
export class IamService {
|
|
6
|
+
/**
|
|
7
|
+
* The underlying {@link GoogleApisService} used to get the IAM client.
|
|
8
|
+
*/
|
|
9
|
+
googleApisService;
|
|
10
|
+
/**
|
|
11
|
+
* A promise that resolves to the singleton IAM client.
|
|
12
|
+
*/
|
|
13
|
+
iam;
|
|
14
|
+
constructor(context) {
|
|
15
|
+
this.googleApisService = context.service(GoogleApisService);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Gets or creates the singleton IAM client.
|
|
19
|
+
*
|
|
20
|
+
* @returns The singleton IAM client.
|
|
21
|
+
*/
|
|
22
|
+
async getIam() {
|
|
23
|
+
if (!this.iam) {
|
|
24
|
+
this.iam = this.googleApisService.getClient('iam', 'v1', {});
|
|
25
|
+
}
|
|
26
|
+
return await this.iam;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Creates a service account, optionally setting the IAM policy bindings for it.
|
|
30
|
+
*
|
|
31
|
+
* @param projectId The ID of the project in which to create the service account.
|
|
32
|
+
* @param accountId The ID of the service account to create.
|
|
33
|
+
* @param options Additional options when creating the account.
|
|
34
|
+
* @returns The created service account.
|
|
35
|
+
*/
|
|
36
|
+
async createServiceAccount(projectId, accountId, options = {}) {
|
|
37
|
+
const iam = await this.getIam();
|
|
38
|
+
const { data: serviceAccount } = await iam.projects.serviceAccounts.create({
|
|
39
|
+
name: `projects/${projectId}`,
|
|
40
|
+
requestBody: { accountId },
|
|
41
|
+
});
|
|
42
|
+
if (options.bindings) {
|
|
43
|
+
const resource = serviceAccount.name ?? '';
|
|
44
|
+
const { data: policy } = await iam.projects.serviceAccounts.getIamPolicy({
|
|
45
|
+
resource,
|
|
46
|
+
});
|
|
47
|
+
policy.bindings = [...(policy.bindings ?? []), ...options.bindings];
|
|
48
|
+
await iam.projects.serviceAccounts.setIamPolicy({
|
|
49
|
+
resource,
|
|
50
|
+
requestBody: { policy },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return serviceAccount;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Deletes a service account.
|
|
57
|
+
*
|
|
58
|
+
* @param name The full resource name of the service account to delete, in the format
|
|
59
|
+
* `projects/<projectId>/serviceAccounts/<accountId>`.
|
|
60
|
+
*/
|
|
61
|
+
async deleteServiceAccount(name) {
|
|
62
|
+
const iam = await this.getIam();
|
|
63
|
+
await iam.projects.serviceAccounts.delete({ name });
|
|
64
|
+
}
|
|
65
|
+
}
|
package/dist/services/index.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
export { BigQueryService } from './bigquery.js';
|
|
2
|
+
export { CloudRunPubSubTriggerService } from './cloud-run-pubsub-trigger.js';
|
|
3
|
+
export { CloudRunService } from './cloud-run.js';
|
|
1
4
|
export * from './firebase-app.errors.js';
|
|
2
5
|
export { FirebaseAppService, FirebaseAppType } from './firebase-app.js';
|
|
3
6
|
export { FirebaseEmulatorService } from './firebase-emulator.js';
|
|
4
7
|
export { GcloudEmulatorService } from './gcloud-emulator.js';
|
|
5
8
|
export { GoogleApisService } from './google-apis.js';
|
|
9
|
+
export { IamService } from './iam.js';
|
|
10
|
+
export { PubSubService } from './pubsub.js';
|
|
11
|
+
export { ResourceManagerService } from './resource-manager.js';
|
|
6
12
|
export { GoogleSecretManagerService } from './secret-manager.js';
|
|
7
13
|
export * from './storage.errors.js';
|
|
8
14
|
export { CloudStorageService } from './storage.js';
|
package/dist/services/index.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
export { BigQueryService } from './bigquery.js';
|
|
2
|
+
export { CloudRunPubSubTriggerService } from './cloud-run-pubsub-trigger.js';
|
|
3
|
+
export { CloudRunService } from './cloud-run.js';
|
|
1
4
|
export * from './firebase-app.errors.js';
|
|
2
5
|
export { FirebaseAppService } from './firebase-app.js';
|
|
3
6
|
export { FirebaseEmulatorService } from './firebase-emulator.js';
|
|
4
7
|
export { GcloudEmulatorService } from './gcloud-emulator.js';
|
|
5
8
|
export { GoogleApisService } from './google-apis.js';
|
|
9
|
+
export { IamService } from './iam.js';
|
|
10
|
+
export { PubSubService } from './pubsub.js';
|
|
11
|
+
export { ResourceManagerService } from './resource-manager.js';
|
|
6
12
|
export { GoogleSecretManagerService } from './secret-manager.js';
|
|
7
13
|
export * from './storage.errors.js';
|
|
8
14
|
export { CloudStorageService } from './storage.js';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { WorkspaceContext } from '@causa/workspace';
|
|
2
|
+
import { PubSub } from '@google-cloud/pubsub';
|
|
3
|
+
import { iam_v1 } from 'googleapis';
|
|
4
|
+
/**
|
|
5
|
+
* A service for managing Pub/Sub resources
|
|
6
|
+
*/
|
|
7
|
+
export declare class PubSubService {
|
|
8
|
+
/**
|
|
9
|
+
* The Pub/Sub client.
|
|
10
|
+
*/
|
|
11
|
+
readonly pubSub: PubSub;
|
|
12
|
+
/**
|
|
13
|
+
* The resource manager service used to get the project number.
|
|
14
|
+
*/
|
|
15
|
+
private readonly resourceManagerService;
|
|
16
|
+
/**
|
|
17
|
+
* The ID of the GCP project in which to create Pub/Sub resources.
|
|
18
|
+
*/
|
|
19
|
+
readonly projectId: string;
|
|
20
|
+
constructor(context: WorkspaceContext);
|
|
21
|
+
/**
|
|
22
|
+
* The cached promise that resolves to the GCP service account used by Pub/Sub.
|
|
23
|
+
*/
|
|
24
|
+
private gcpServiceAccountPromise;
|
|
25
|
+
/**
|
|
26
|
+
* Constructs the email of the GCP-owned service account used by Pub/Sub.
|
|
27
|
+
* This can be used to grant roles to the service account, e.g. for push subscriptions.
|
|
28
|
+
*
|
|
29
|
+
* @returns The email of the GCP service account used by Pub/Sub.
|
|
30
|
+
*/
|
|
31
|
+
getGcpServiceAccount(): Promise<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Constructs the IAM policy bindings that should be added to a service account meant to be used by push
|
|
34
|
+
* subscriptions.
|
|
35
|
+
* Those bindings allow the GCP-owned Pub/Sub service account to authenticate as the service account when pushing
|
|
36
|
+
* messages.
|
|
37
|
+
*
|
|
38
|
+
* @returns The bindings.
|
|
39
|
+
*/
|
|
40
|
+
getPushServiceAccountBindings(): Promise<iam_v1.Schema$Binding[]>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { PubSub } from '@google-cloud/pubsub';
|
|
2
|
+
import { ResourceManagerService } from './resource-manager.js';
|
|
3
|
+
/**
|
|
4
|
+
* A service for managing Pub/Sub resources
|
|
5
|
+
*/
|
|
6
|
+
export class PubSubService {
|
|
7
|
+
/**
|
|
8
|
+
* The Pub/Sub client.
|
|
9
|
+
*/
|
|
10
|
+
pubSub;
|
|
11
|
+
/**
|
|
12
|
+
* The resource manager service used to get the project number.
|
|
13
|
+
*/
|
|
14
|
+
resourceManagerService;
|
|
15
|
+
/**
|
|
16
|
+
* The ID of the GCP project in which to create Pub/Sub resources.
|
|
17
|
+
*/
|
|
18
|
+
projectId;
|
|
19
|
+
constructor(context) {
|
|
20
|
+
this.pubSub = new PubSub();
|
|
21
|
+
this.resourceManagerService = context.service(ResourceManagerService);
|
|
22
|
+
this.projectId = context
|
|
23
|
+
.asConfiguration()
|
|
24
|
+
.getOrThrow('google.project');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The cached promise that resolves to the GCP service account used by Pub/Sub.
|
|
28
|
+
*/
|
|
29
|
+
gcpServiceAccountPromise;
|
|
30
|
+
/**
|
|
31
|
+
* Constructs the email of the GCP-owned service account used by Pub/Sub.
|
|
32
|
+
* This can be used to grant roles to the service account, e.g. for push subscriptions.
|
|
33
|
+
*
|
|
34
|
+
* @returns The email of the GCP service account used by Pub/Sub.
|
|
35
|
+
*/
|
|
36
|
+
async getGcpServiceAccount() {
|
|
37
|
+
if (!this.gcpServiceAccountPromise) {
|
|
38
|
+
this.gcpServiceAccountPromise = (async () => {
|
|
39
|
+
const projectNumber = await this.resourceManagerService.getProjectNumber(this.projectId);
|
|
40
|
+
return `service-${projectNumber}@gcp-sa-pubsub.iam.gserviceaccount.com`;
|
|
41
|
+
})();
|
|
42
|
+
}
|
|
43
|
+
return await this.gcpServiceAccountPromise;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Constructs the IAM policy bindings that should be added to a service account meant to be used by push
|
|
47
|
+
* subscriptions.
|
|
48
|
+
* Those bindings allow the GCP-owned Pub/Sub service account to authenticate as the service account when pushing
|
|
49
|
+
* messages.
|
|
50
|
+
*
|
|
51
|
+
* @returns The bindings.
|
|
52
|
+
*/
|
|
53
|
+
async getPushServiceAccountBindings() {
|
|
54
|
+
const pubSubGcpServiceAccount = await this.getGcpServiceAccount();
|
|
55
|
+
return [
|
|
56
|
+
{
|
|
57
|
+
role: 'roles/iam.serviceAccountTokenCreator',
|
|
58
|
+
members: [`serviceAccount:${pubSubGcpServiceAccount}`],
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ProjectsClient } from '@google-cloud/resource-manager';
|
|
2
|
+
/**
|
|
3
|
+
* A service for interacting with the GCP Resource Manager API, handling organizations, folders, and projects.
|
|
4
|
+
*/
|
|
5
|
+
export declare class ResourceManagerService {
|
|
6
|
+
/**
|
|
7
|
+
* The client used to manage GCP projects.
|
|
8
|
+
*/
|
|
9
|
+
readonly projectsClient: ProjectsClient;
|
|
10
|
+
constructor();
|
|
11
|
+
/**
|
|
12
|
+
* Retrieves the project number for a given GCP project ID.
|
|
13
|
+
*
|
|
14
|
+
* @param projectId The ID of the GCP project.
|
|
15
|
+
* @returns The number of the GCP project.
|
|
16
|
+
*/
|
|
17
|
+
getProjectNumber(projectId: string): Promise<string>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ProjectsClient } from '@google-cloud/resource-manager';
|
|
2
|
+
/**
|
|
3
|
+
* A service for interacting with the GCP Resource Manager API, handling organizations, folders, and projects.
|
|
4
|
+
*/
|
|
5
|
+
export class ResourceManagerService {
|
|
6
|
+
/**
|
|
7
|
+
* The client used to manage GCP projects.
|
|
8
|
+
*/
|
|
9
|
+
projectsClient;
|
|
10
|
+
constructor() {
|
|
11
|
+
this.projectsClient = new ProjectsClient();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Retrieves the project number for a given GCP project ID.
|
|
15
|
+
*
|
|
16
|
+
* @param projectId The ID of the GCP project.
|
|
17
|
+
* @returns The number of the GCP project.
|
|
18
|
+
*/
|
|
19
|
+
async getProjectNumber(projectId) {
|
|
20
|
+
const [projects] = await this.projectsClient.searchProjects({
|
|
21
|
+
query: `projectId:${projectId}`,
|
|
22
|
+
pageSize: 1,
|
|
23
|
+
});
|
|
24
|
+
if (projects.length < 1) {
|
|
25
|
+
throw new Error(`Could not find GCP project '${projectId}'.`);
|
|
26
|
+
}
|
|
27
|
+
const [project] = projects;
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
29
|
+
const [projectsConst, projectNumber] = project.name?.split('/') ?? [];
|
|
30
|
+
if (!projectNumber) {
|
|
31
|
+
throw new Error(`Failed to parse invalid project name '${project.name}'.`);
|
|
32
|
+
}
|
|
33
|
+
return projectNumber;
|
|
34
|
+
}
|
|
35
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@causa/workspace-google",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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,34 +31,38 @@
|
|
|
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.
|
|
34
|
+
"@causa/workspace-core": ">= 0.14.1 < 1.0.0",
|
|
35
35
|
"@google-cloud/apikeys": "^0.2.2",
|
|
36
|
+
"@google-cloud/bigquery": "^7.1.1",
|
|
36
37
|
"@google-cloud/iam-credentials": "^2.0.4",
|
|
37
|
-
"@google-cloud/pubsub": "^
|
|
38
|
+
"@google-cloud/pubsub": "^4.0.0",
|
|
39
|
+
"@google-cloud/resource-manager": "^4.3.0",
|
|
40
|
+
"@google-cloud/run": "^0.6.0",
|
|
38
41
|
"@google-cloud/secret-manager": "^4.2.2",
|
|
39
42
|
"@google-cloud/service-usage": "^2.2.2",
|
|
40
|
-
"@google-cloud/spanner": "^6.
|
|
41
|
-
"@google-cloud/storage": "^
|
|
42
|
-
"@grpc/grpc-js": "
|
|
43
|
+
"@google-cloud/spanner": "^6.15.0",
|
|
44
|
+
"@google-cloud/storage": "^7.0.0",
|
|
45
|
+
"@grpc/grpc-js": "~1.8.21",
|
|
43
46
|
"class-validator": "^0.14.0",
|
|
44
47
|
"firebase": "^10.1.0",
|
|
45
48
|
"firebase-admin": "^11.10.1",
|
|
46
49
|
"globby": "^13.2.2",
|
|
47
50
|
"googleapis": "^123.0.0",
|
|
51
|
+
"pino": "^8.15.0",
|
|
48
52
|
"uuid": "^9.0.0"
|
|
49
53
|
},
|
|
50
54
|
"devDependencies": {
|
|
51
55
|
"@tsconfig/node18": "^18.2.0",
|
|
52
56
|
"@types/jest": "^29.5.3",
|
|
53
|
-
"@types/node": "^18.17.
|
|
57
|
+
"@types/node": "^18.17.2",
|
|
54
58
|
"@types/uuid": "^9.0.2",
|
|
55
|
-
"@typescript-eslint/eslint-plugin": "^6.2.
|
|
59
|
+
"@typescript-eslint/eslint-plugin": "^6.2.1",
|
|
56
60
|
"copyfiles": "^2.4.1",
|
|
57
|
-
"eslint": "^8.
|
|
58
|
-
"eslint-config-prettier": "^8.
|
|
61
|
+
"eslint": "^8.46.0",
|
|
62
|
+
"eslint-config-prettier": "^8.10.0",
|
|
59
63
|
"eslint-plugin-prettier": "^5.0.0",
|
|
60
|
-
"jest": "^29.6.
|
|
61
|
-
"jest-extended": "^4.0.
|
|
64
|
+
"jest": "^29.6.2",
|
|
65
|
+
"jest-extended": "^4.0.1",
|
|
62
66
|
"rimraf": "^5.0.1",
|
|
63
67
|
"ts-jest": "^29.1.1",
|
|
64
68
|
"ts-node": "^10.9.1",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/dist/functions/{emulator-stop-firebase-storage.d.ts → emulator/stop-firebase-storage.d.ts}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|