@causa/workspace-google 1.3.1 → 1.4.0-beta.2
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 +23 -0
- package/dist/functions/event-topic/create-backfill-source-bigquery.call.js +2 -2
- package/dist/functions/event-topic/query-events-bigquery.call.js +2 -2
- package/dist/functions/google-app-check/generate-token.js +3 -1
- package/dist/functions/google-firebase-storage/merge-rules.js +3 -1
- package/dist/functions/google-firestore/generate-typescript-decorators.js +2 -1
- package/dist/functions/google-firestore/merge-rules.js +3 -1
- package/dist/functions/google-identity-platform/generate-custom-token.js +7 -2
- package/dist/functions/google-pubsub/index.d.ts +1 -1
- package/dist/functions/google-pubsub/index.js +1 -1
- package/dist/functions/google-pubsub/write-topics.d.ts +7 -0
- package/dist/functions/google-pubsub/write-topics.js +17 -3
- package/dist/functions/google-services/enable.js +3 -1
- package/dist/functions/google-spanner/generate-typescript-decorators.js +2 -1
- package/dist/functions/google-spanner/list-databases.d.ts +1 -1
- package/dist/functions/google-spanner/write-databases.js +7 -2
- package/dist/functions/graph/fetch-environment-metric-series.call.d.ts +3 -0
- package/dist/functions/graph/fetch-environment-metric-series.call.js +107 -0
- package/dist/functions/graph/fetch-environment-metric-series.d.ts +23 -0
- package/dist/functions/graph/fetch-environment-metric-series.js +40 -0
- package/dist/functions/graph/get-environment-provider.d.ts +11 -0
- package/dist/functions/graph/get-environment-provider.js +20 -0
- package/dist/functions/graph/index.d.ts +3 -0
- package/dist/functions/graph/index.js +3 -0
- package/dist/functions/graph/list-rules.d.ts +11 -0
- package/dist/functions/graph/list-rules.js +16 -0
- package/dist/functions/index.js +2 -1
- package/dist/graph/cloud-run.d.ts +98 -0
- package/dist/graph/cloud-run.js +102 -0
- package/dist/graph/environment/fetchers/cloud-monitoring-alerts.d.ts +6 -0
- package/dist/graph/environment/fetchers/cloud-monitoring-alerts.js +106 -0
- package/dist/graph/environment/fetchers/cloud-monitoring.d.ts +5 -0
- package/dist/graph/environment/fetchers/cloud-monitoring.js +13 -0
- package/dist/graph/environment/fetchers/cloud-run.d.ts +5 -0
- package/dist/graph/environment/fetchers/cloud-run.js +88 -0
- package/dist/graph/environment/fetchers/cloud-scheduler.d.ts +5 -0
- package/dist/graph/environment/fetchers/cloud-scheduler.js +95 -0
- package/dist/graph/environment/fetchers/console-links.d.ts +14 -0
- package/dist/graph/environment/fetchers/console-links.js +132 -0
- package/dist/graph/environment/fetchers/error-reporting.d.ts +5 -0
- package/dist/graph/environment/fetchers/error-reporting.js +98 -0
- package/dist/graph/environment/fetchers/index.d.ts +5 -0
- package/dist/graph/environment/fetchers/index.js +17 -0
- package/dist/graph/environment/index.d.ts +9 -0
- package/dist/graph/environment/index.js +9 -0
- package/dist/graph/environment/load-balancer-backends.d.ts +82 -0
- package/dist/graph/environment/load-balancer-backends.js +126 -0
- package/dist/graph/environment/monitoring-alerts.d.ts +60 -0
- package/dist/graph/environment/monitoring-alerts.js +67 -0
- package/dist/graph/environment/monitoring-catalog.d.ts +95 -0
- package/dist/graph/environment/monitoring-catalog.js +356 -0
- package/dist/graph/environment/monitoring-mappings.d.ts +133 -0
- package/dist/graph/environment/monitoring-mappings.js +97 -0
- package/dist/graph/environment/monitoring-query.d.ts +116 -0
- package/dist/graph/environment/monitoring-query.js +314 -0
- package/dist/graph/environment/monitoring-values.d.ts +41 -0
- package/dist/graph/environment/monitoring-values.js +119 -0
- package/dist/graph/environment/resource-resolvers.d.ts +7 -0
- package/dist/graph/environment/resource-resolvers.js +57 -0
- package/dist/graph/environment/resources.d.ts +45 -0
- package/dist/graph/environment/resources.js +49 -0
- package/dist/graph/ids.d.ts +35 -0
- package/dist/graph/ids.js +44 -0
- package/dist/graph/index.d.ts +7 -0
- package/dist/graph/index.js +7 -0
- package/dist/graph/rules/configuration/enqueues-assumed-from-google-task-trigger.d.ts +6 -0
- package/dist/graph/rules/configuration/enqueues-assumed-from-google-task-trigger.js +39 -0
- package/dist/graph/rules/configuration/firestore.d.ts +11 -0
- package/dist/graph/rules/configuration/firestore.js +93 -0
- package/dist/graph/rules/configuration/index.d.ts +5 -0
- package/dist/graph/rules/configuration/index.js +11 -0
- package/dist/graph/rules/configuration/service-container.d.ts +13 -0
- package/dist/graph/rules/configuration/service-container.js +123 -0
- package/dist/graph/rules/terraform/api-router.d.ts +15 -0
- package/dist/graph/rules/terraform/api-router.js +117 -0
- package/dist/graph/rules/terraform/cloud-run-cron-triggers.d.ts +11 -0
- package/dist/graph/rules/terraform/cloud-run-cron-triggers.js +67 -0
- package/dist/graph/rules/terraform/cloud-run-grants.d.ts +16 -0
- package/dist/graph/rules/terraform/cloud-run-grants.js +118 -0
- package/dist/graph/rules/terraform/cloud-run-module.d.ts +6 -0
- package/dist/graph/rules/terraform/cloud-run-module.js +29 -0
- package/dist/graph/rules/terraform/cloud-run-pubsub-triggers.d.ts +14 -0
- package/dist/graph/rules/terraform/cloud-run-pubsub-triggers.js +103 -0
- package/dist/graph/rules/terraform/cloud-run-services.d.ts +12 -0
- package/dist/graph/rules/terraform/cloud-run-services.js +61 -0
- package/dist/graph/rules/terraform/cloud-run-tasks-triggers.d.ts +14 -0
- package/dist/graph/rules/terraform/cloud-run-tasks-triggers.js +97 -0
- package/dist/graph/rules/terraform/event-topics.d.ts +18 -0
- package/dist/graph/rules/terraform/event-topics.js +193 -0
- package/dist/graph/rules/terraform/index.d.ts +5 -0
- package/dist/graph/rules/terraform/index.js +13 -0
- package/dist/graph/rules/terraform/spanner-databases.d.ts +17 -0
- package/dist/graph/rules/terraform/spanner-databases.js +180 -0
- package/dist/graph/spanner.d.ts +106 -0
- package/dist/graph/spanner.js +118 -0
- package/dist/graph/terraform-modules.d.ts +87 -0
- package/dist/graph/terraform-modules.js +107 -0
- package/package.json +30 -29
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { ServiceContainerConfiguration } from '@causa/workspace-core';
|
|
2
|
+
import { GraphExtractionFact, type GraphContext, type GraphFactOutput, type GraphTemplatePart, type WorkspaceProject } from '@causa/workspace-core/graph';
|
|
3
|
+
import { type TerraformModuleBlock } from '@causa/workspace-terraform';
|
|
4
|
+
/**
|
|
5
|
+
* A trigger defined in the `serviceContainer.triggers` configuration.
|
|
6
|
+
*/
|
|
7
|
+
type ServiceContainerTrigger = NonNullable<NonNullable<ServiceContainerConfiguration['serviceContainer']>['triggers']>[string];
|
|
8
|
+
/**
|
|
9
|
+
* The source of the Causa Terraform module deploying a service container to Cloud Run.
|
|
10
|
+
*/
|
|
11
|
+
export declare const CLOUD_RUN_MODULE_SOURCE = "causa-io/service-container-cloud-run/google";
|
|
12
|
+
/**
|
|
13
|
+
* A block of the Cloud Run module, i.e. a deployed service, with the module's logic evaluated from the block's
|
|
14
|
+
* arguments and the project's configuration.
|
|
15
|
+
*/
|
|
16
|
+
export type CloudRunService = {
|
|
17
|
+
/**
|
|
18
|
+
* The Terraform module block defining the Cloud Run service.
|
|
19
|
+
*/
|
|
20
|
+
readonly block: TerraformModuleBlock;
|
|
21
|
+
/**
|
|
22
|
+
* The project whose configuration file the block reads.
|
|
23
|
+
*/
|
|
24
|
+
readonly project: WorkspaceProject;
|
|
25
|
+
/**
|
|
26
|
+
* The name of the service, `coalesce(var.name, project.name)`.
|
|
27
|
+
*/
|
|
28
|
+
readonly name: string;
|
|
29
|
+
/**
|
|
30
|
+
* The ID of the `service` node.
|
|
31
|
+
*/
|
|
32
|
+
readonly id: string;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the service's HTTP endpoints are exposed through the API router (`enable_public_http_endpoints`, `false` by
|
|
35
|
+
* default).
|
|
36
|
+
*/
|
|
37
|
+
readonly enablePublicHttpEndpoints: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Whether the block grants the publisher role on the project's `outputs.eventTopics` (`set_pubsub_permissions`,
|
|
40
|
+
* defaulting to `set_iam_permissions`).
|
|
41
|
+
*/
|
|
42
|
+
readonly setPubsubPermissions: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Whether the block grants access to the Spanner databases of the project's outputs (`set_spanner_permissions`,
|
|
45
|
+
* defaulting to `set_iam_permissions`).
|
|
46
|
+
*/
|
|
47
|
+
readonly setSpannerPermissions: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Whether the block grants access to the Firestore database for the collections of the project's outputs
|
|
50
|
+
* (`set_firestore_permissions`, defaulting to `set_iam_permissions`).
|
|
51
|
+
*/
|
|
52
|
+
readonly setFirestorePermissions: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Whether the block grants the enqueuer role on the queues of the project's task triggers (`set_tasks_permissions`,
|
|
55
|
+
* defaulting to `set_iam_permissions`).
|
|
56
|
+
*/
|
|
57
|
+
readonly setTasksPermissions: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The triggers the module turns into Pub/Sub push subscriptions, keyed by name: `event` or `google.pubSub` triggers
|
|
60
|
+
* with an HTTP endpoint, not disabled.
|
|
61
|
+
*/
|
|
62
|
+
readonly pubsubTriggers: ReadonlyMap<string, ServiceContainerTrigger>;
|
|
63
|
+
/**
|
|
64
|
+
* The triggers the module turns into Cloud Tasks queues, keyed by name. A disabled trigger still has its * queue,
|
|
65
|
+
* paused.
|
|
66
|
+
*/
|
|
67
|
+
readonly tasksTriggers: ReadonlyMap<string, ServiceContainerTrigger>;
|
|
68
|
+
/**
|
|
69
|
+
* The triggers the module turns into Cloud Scheduler jobs, keyed by name. A disabled trigger still has its job,
|
|
70
|
+
* paused.
|
|
71
|
+
*/
|
|
72
|
+
readonly cronTriggers: ReadonlyMap<string, ServiceContainerTrigger>;
|
|
73
|
+
/**
|
|
74
|
+
* The GCP project part of resource names.
|
|
75
|
+
*/
|
|
76
|
+
readonly projectPart: GraphTemplatePart;
|
|
77
|
+
/**
|
|
78
|
+
* The location part of resource names, shared by the service, its queues, and its jobs.
|
|
79
|
+
*/
|
|
80
|
+
readonly locationPart: GraphTemplatePart;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* The blocks of the Cloud Run module, evaluated against the configuration of the projects they deploy.
|
|
84
|
+
* Blocks whose `configuration_file` does not name a project of the workspace cannot be mirrored, and are reported.
|
|
85
|
+
*/
|
|
86
|
+
export declare class CloudRunFact extends GraphExtractionFact<CloudRunService[]> {
|
|
87
|
+
compute(graph: GraphContext): Promise<GraphFactOutput<CloudRunService[]>>;
|
|
88
|
+
/**
|
|
89
|
+
* Evaluates a Cloud Run module block, mirroring the defaults of the module.
|
|
90
|
+
*
|
|
91
|
+
* @param block The module block.
|
|
92
|
+
* @param project The project whose configuration file the block reads.
|
|
93
|
+
* @param warnings The warnings of the fact, to which the ones about the block are added.
|
|
94
|
+
* @returns The {@link CloudRunService}.
|
|
95
|
+
*/
|
|
96
|
+
private evaluate;
|
|
97
|
+
}
|
|
98
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { GraphExtractionFact, ProjectsFact, serviceId, } from '@causa/workspace-core/graph';
|
|
2
|
+
import { modulesWithSource, TerraformProjectsFact, } from '@causa/workspace-terraform';
|
|
3
|
+
import { argumentOrConfiguration, projectOfConfigurationFile, } from './terraform-modules.js';
|
|
4
|
+
/**
|
|
5
|
+
* The source of the Causa Terraform module deploying a service container to Cloud Run.
|
|
6
|
+
*/
|
|
7
|
+
export const CLOUD_RUN_MODULE_SOURCE = 'causa-io/service-container-cloud-run/google';
|
|
8
|
+
/**
|
|
9
|
+
* The major version of the Cloud Run module whose behavior is mirrored (trigger filters, IAM grants, resource names).
|
|
10
|
+
*/
|
|
11
|
+
const MIRRORED_CLOUD_RUN_MAJOR = 1;
|
|
12
|
+
/**
|
|
13
|
+
* The blocks of the Cloud Run module, evaluated against the configuration of the projects they deploy.
|
|
14
|
+
* Blocks whose `configuration_file` does not name a project of the workspace cannot be mirrored, and are reported.
|
|
15
|
+
*/
|
|
16
|
+
export class CloudRunFact extends GraphExtractionFact {
|
|
17
|
+
async compute(graph) {
|
|
18
|
+
const [terraformProjects, projects] = await Promise.all([
|
|
19
|
+
graph.get(TerraformProjectsFact),
|
|
20
|
+
graph.get(ProjectsFact),
|
|
21
|
+
]);
|
|
22
|
+
const services = [];
|
|
23
|
+
const warnings = [];
|
|
24
|
+
for (const block of modulesWithSource(terraformProjects, CLOUD_RUN_MODULE_SOURCE)) {
|
|
25
|
+
const project = projectOfConfigurationFile(block.arguments['configuration_file'], projects);
|
|
26
|
+
if (!project) {
|
|
27
|
+
warnings.push({
|
|
28
|
+
message: "The Cloud Run module block is skipped, as its 'configuration_file' does not name a project of the workspace.",
|
|
29
|
+
sources: [block.declaration],
|
|
30
|
+
});
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
services.push(this.evaluate(block, project, warnings));
|
|
34
|
+
}
|
|
35
|
+
return { value: services, warnings };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Evaluates a Cloud Run module block, mirroring the defaults of the module.
|
|
39
|
+
*
|
|
40
|
+
* @param block The module block.
|
|
41
|
+
* @param project The project whose configuration file the block reads.
|
|
42
|
+
* @param warnings The warnings of the fact, to which the ones about the block are added.
|
|
43
|
+
* @returns The {@link CloudRunService}.
|
|
44
|
+
*/
|
|
45
|
+
evaluate(block, project, warnings) {
|
|
46
|
+
const sources = [block.declaration];
|
|
47
|
+
const major = Number(block.version?.split('.')[0]);
|
|
48
|
+
if (major !== MIRRORED_CLOUD_RUN_MAJOR) {
|
|
49
|
+
warnings.push({
|
|
50
|
+
message: `The Cloud Run module version '${block.version ?? 'unset'}' is not the one being mirrored (${MIRRORED_CLOUD_RUN_MAJOR}.x). Resources are extracted as for that version.`,
|
|
51
|
+
sources,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const argument = (name, fallback) => {
|
|
55
|
+
const value = block.arguments[name];
|
|
56
|
+
if (value === undefined) {
|
|
57
|
+
return fallback;
|
|
58
|
+
}
|
|
59
|
+
if (!('value' in value)) {
|
|
60
|
+
const assumed = typeof fallback === 'string' ? fallback : JSON.stringify(fallback);
|
|
61
|
+
warnings.push({
|
|
62
|
+
message: `The argument '${name}' is computed (${value.expression}). Assuming ${assumed}.`,
|
|
63
|
+
sources,
|
|
64
|
+
});
|
|
65
|
+
return fallback;
|
|
66
|
+
}
|
|
67
|
+
return (value.value ?? fallback);
|
|
68
|
+
};
|
|
69
|
+
const name = argument('name', project.name) || project.name;
|
|
70
|
+
const enableTriggers = argument('enable_triggers', false);
|
|
71
|
+
const triggers = Object.entries({
|
|
72
|
+
...(project.context
|
|
73
|
+
.asConfiguration()
|
|
74
|
+
.get('serviceContainer.triggers', { unsafe: true }) ?? {}),
|
|
75
|
+
...argument('triggers', {}),
|
|
76
|
+
}).filter(([, t]) => t.endpoint?.type === 'http');
|
|
77
|
+
const ofTypes = (enabled, types, excludeDisabled = false) => new Map(enabled
|
|
78
|
+
? triggers.filter(([, t]) => types.includes(t.type ?? '') &&
|
|
79
|
+
!(excludeDisabled && t.enabled === false))
|
|
80
|
+
: []);
|
|
81
|
+
const setIamPermissions = argument('set_iam_permissions', true);
|
|
82
|
+
return {
|
|
83
|
+
block,
|
|
84
|
+
project,
|
|
85
|
+
name,
|
|
86
|
+
id: serviceId(project.directory, name),
|
|
87
|
+
enablePublicHttpEndpoints: argument('enable_public_http_endpoints', false),
|
|
88
|
+
setPubsubPermissions: argument('set_pubsub_permissions', setIamPermissions),
|
|
89
|
+
setSpannerPermissions: argument('set_spanner_permissions', setIamPermissions),
|
|
90
|
+
setFirestorePermissions: argument('set_firestore_permissions', setIamPermissions),
|
|
91
|
+
setTasksPermissions: argument('set_tasks_permissions', setIamPermissions),
|
|
92
|
+
pubsubTriggers: ofTypes(argument('enable_pubsub_triggers', enableTriggers), ['event', 'google.pubSub'], true),
|
|
93
|
+
tasksTriggers: ofTypes(argument('enable_tasks_triggers', enableTriggers), ['task', 'google.task', 'google.tasks']),
|
|
94
|
+
cronTriggers: ofTypes(argument('enable_cron_triggers', enableTriggers), [
|
|
95
|
+
'cron',
|
|
96
|
+
'google.scheduler',
|
|
97
|
+
]),
|
|
98
|
+
projectPart: argumentOrConfiguration(block, 'gcp_project_id', 'google.project', warnings),
|
|
99
|
+
locationPart: argumentOrConfiguration(block, 'location', 'google.cloudRun.location', warnings),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type GraphEnvironmentFetcher } from '@causa/workspace-core';
|
|
2
|
+
/**
|
|
3
|
+
* Reads the alerts open at the end of the evaluation window from Cloud Monitoring, and attaches them to the nodes of
|
|
4
|
+
* their resources.
|
|
5
|
+
*/
|
|
6
|
+
export declare const googleCloudMonitoringAlerts: GraphEnvironmentFetcher;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { GraphAlertSeverity, } from '@causa/workspace-core';
|
|
2
|
+
import { alertLabelValue, listOpenAlerts, } from '../monitoring-alerts.js';
|
|
3
|
+
import { MONITORING_RESOURCE_MAPPINGS } from '../monitoring-catalog.js';
|
|
4
|
+
import { findMonitoredNode, prepareMonitoringMapping, readMonitoringResourceKey, } from '../monitoring-mappings.js';
|
|
5
|
+
import { googleResources } from '../resources.js';
|
|
6
|
+
import { consoleUrl } from './console-links.js';
|
|
7
|
+
/**
|
|
8
|
+
* The severities of alerts, keyed by the severity of their policy.
|
|
9
|
+
*/
|
|
10
|
+
const SEVERITIES = {
|
|
11
|
+
CRITICAL: GraphAlertSeverity.Critical,
|
|
12
|
+
ERROR: GraphAlertSeverity.Error,
|
|
13
|
+
WARNING: GraphAlertSeverity.Warning,
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Converts an alert returned by the Alerts API to a {@link GraphAlert}.
|
|
17
|
+
*
|
|
18
|
+
* @param alert The alert.
|
|
19
|
+
* @param project The GCP project from which the alert was listed.
|
|
20
|
+
* @returns The alert.
|
|
21
|
+
*/
|
|
22
|
+
function toGraphAlert(alert, project) {
|
|
23
|
+
const severity = SEVERITIES[alert.policy?.severity ?? ''];
|
|
24
|
+
const id = alert.name?.split('/').at(-1);
|
|
25
|
+
return {
|
|
26
|
+
title: alert.policy?.displayName,
|
|
27
|
+
...(severity ? { severity } : {}),
|
|
28
|
+
openedAt: new Date(alert.openTime),
|
|
29
|
+
...(id
|
|
30
|
+
? { url: consoleUrl(['monitoring', 'alerting', 'alerts', id], project) }
|
|
31
|
+
: {}),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Describes the resource of an alert in a warning.
|
|
36
|
+
*
|
|
37
|
+
* @param resource The resource of the alert.
|
|
38
|
+
* @returns The description.
|
|
39
|
+
*/
|
|
40
|
+
function describeResource(resource) {
|
|
41
|
+
if (!resource) {
|
|
42
|
+
return 'a resource of unknown type';
|
|
43
|
+
}
|
|
44
|
+
const labels = Object.entries(resource.labels)
|
|
45
|
+
.filter(([name]) => name !== 'value' && name !== 'monitored_resource')
|
|
46
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
47
|
+
.map(([name, value]) => `${name}=${value}`);
|
|
48
|
+
return `the '${resource.type}' resource (${labels.join(', ')})`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Reads the alerts open at the end of the evaluation window from Cloud Monitoring, and attaches them to the nodes of
|
|
52
|
+
* their resources.
|
|
53
|
+
*/
|
|
54
|
+
export const googleCloudMonitoringAlerts = {
|
|
55
|
+
name: 'googleCloudMonitoringAlerts',
|
|
56
|
+
description: "The `alerts` open at the end of the evaluation window, read from Cloud Monitoring in the projects of the graph's resources, and attached to the nodes of their resources. Alerts that cannot be attached to a node are added to the environment.",
|
|
57
|
+
fetch: async (environment) => {
|
|
58
|
+
const projects = [
|
|
59
|
+
...new Set(googleResources(environment).map(({ segments }) => segments.projects)),
|
|
60
|
+
]
|
|
61
|
+
.filter((p) => !!p)
|
|
62
|
+
.sort();
|
|
63
|
+
const warnings = [];
|
|
64
|
+
const listed = await Promise.all(projects.map(async (project) => {
|
|
65
|
+
try {
|
|
66
|
+
return await listOpenAlerts(environment.context, project, environment.at);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
warnings.push({
|
|
70
|
+
message: `Failed to list the alerts in project '${project}': ${error.message ?? error}`,
|
|
71
|
+
});
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}));
|
|
75
|
+
const alerts = listed.flat();
|
|
76
|
+
if (alerts.length === 0) {
|
|
77
|
+
return { warnings };
|
|
78
|
+
}
|
|
79
|
+
// Only the mappings that may apply to an alert are prepared, as preparing a mapping may require listing resources,
|
|
80
|
+
// e.g. the backends of load balancers.
|
|
81
|
+
const candidates = (await Promise.all(MONITORING_RESOURCE_MAPPINGS.filter((mapping) => alerts.some(({ resource }) => resource?.type === mapping.monitoredResource &&
|
|
82
|
+
readMonitoringResourceKey(mapping, resource.project, (label) => alertLabelValue(resource, label)))).map((mapping) => prepareMonitoringMapping(environment, mapping)))).filter((m) => !!m);
|
|
83
|
+
const nodes = {};
|
|
84
|
+
const unattached = [];
|
|
85
|
+
for (const { alert, project, resource } of alerts) {
|
|
86
|
+
const graphAlert = toGraphAlert(alert, project);
|
|
87
|
+
const { node } = resource
|
|
88
|
+
? findMonitoredNode(environment, candidates.filter(({ mapping }) => mapping.monitoredResource === resource.type), resource.project, (label) => alertLabelValue(resource, label))
|
|
89
|
+
: {};
|
|
90
|
+
if (node) {
|
|
91
|
+
nodes[node.id] ??= { alerts: [] };
|
|
92
|
+
nodes[node.id].alerts.push(graphAlert);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
unattached.push(graphAlert);
|
|
96
|
+
warnings.push({
|
|
97
|
+
message: `The alert of policy '${graphAlert.title}' on ${describeResource(resource)} could not be attached to a node, and is added to the environment.`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
nodes,
|
|
102
|
+
...(unattached.length > 0 ? { alerts: unattached } : {}),
|
|
103
|
+
warnings,
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { MONITORING_METRICS } from '../monitoring-catalog.js';
|
|
2
|
+
import { queryPointInTimeMetrics } from '../monitoring-query.js';
|
|
3
|
+
/**
|
|
4
|
+
* Reads the metrics of nodes from Cloud Monitoring, over the evaluation window.
|
|
5
|
+
*/
|
|
6
|
+
export const googleCloudMonitoring = {
|
|
7
|
+
name: 'googleCloudMonitoring',
|
|
8
|
+
description: 'The metrics of Cloud Run services, API routers, Pub/Sub topics and subscriptions, Cloud Tasks queues, and Spanner, Firestore, and BigQuery databases, read from Cloud Monitoring.',
|
|
9
|
+
fetch: async (environment) => {
|
|
10
|
+
const { nodes, bucketLayouts, warnings } = await queryPointInTimeMetrics(environment, MONITORING_METRICS);
|
|
11
|
+
return { nodes, bucketLayouts, warnings };
|
|
12
|
+
},
|
|
13
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ServiceDeploymentStatus, } from '@causa/workspace-core';
|
|
2
|
+
import { googleResources } from '../resources.js';
|
|
3
|
+
/**
|
|
4
|
+
* The deployment status for each state of the `Ready` terminal condition of a Cloud Run service.
|
|
5
|
+
* States are listed both by name and by number, as clients may return either.
|
|
6
|
+
*/
|
|
7
|
+
const DEPLOYMENT_STATUSES = {
|
|
8
|
+
CONDITION_PENDING: ServiceDeploymentStatus.Deploying,
|
|
9
|
+
1: ServiceDeploymentStatus.Deploying,
|
|
10
|
+
CONDITION_RECONCILING: ServiceDeploymentStatus.Deploying,
|
|
11
|
+
2: ServiceDeploymentStatus.Deploying,
|
|
12
|
+
CONDITION_FAILED: ServiceDeploymentStatus.Failed,
|
|
13
|
+
3: ServiceDeploymentStatus.Failed,
|
|
14
|
+
CONDITION_SUCCEEDED: ServiceDeploymentStatus.Deployed,
|
|
15
|
+
4: ServiceDeploymentStatus.Deployed,
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Returns what is deployed for a Cloud Run service.
|
|
19
|
+
*
|
|
20
|
+
* @param service The Cloud Run service.
|
|
21
|
+
* @returns The deployment.
|
|
22
|
+
*/
|
|
23
|
+
function toDeployment(service) {
|
|
24
|
+
const image = service.template?.containers?.[0]?.image ?? undefined;
|
|
25
|
+
const updateSeconds = service.updateTime?.seconds;
|
|
26
|
+
const updatedAt = updateSeconds
|
|
27
|
+
? new Date(Number(updateSeconds.toString()) * 1000)
|
|
28
|
+
: undefined;
|
|
29
|
+
const { state, message } = service.terminalCondition ?? {};
|
|
30
|
+
const status = state ? DEPLOYMENT_STATUSES[state] : undefined;
|
|
31
|
+
return {
|
|
32
|
+
...(image ? { image } : {}),
|
|
33
|
+
...(updatedAt ? { updatedAt } : {}),
|
|
34
|
+
...(status ? { status } : {}),
|
|
35
|
+
...(status && message ? { message } : {}),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Reads the deployments of Cloud Run services.
|
|
40
|
+
*/
|
|
41
|
+
export const googleCloudRun = {
|
|
42
|
+
name: 'googleCloudRun',
|
|
43
|
+
description: 'The `deployment` of services deployed on Cloud Run: the container image, the last update time, and the status of the latest deployment from the `Ready` condition. The deployment is the current one, with a warning when the service was updated after the end of the evaluation window.',
|
|
44
|
+
fetch: async (environment) => {
|
|
45
|
+
const services = googleResources(environment, 'run.googleapis.com/Service', 'service');
|
|
46
|
+
if (services.length === 0) {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
const { CloudRunService } = await import('../../../services/cloud-run.js');
|
|
50
|
+
const { servicesClient } = environment.context.service(CloudRunService);
|
|
51
|
+
// Calls on a gax client crash the process when credentials cannot be loaded, while `initialize` rejects.
|
|
52
|
+
await servicesClient.initialize();
|
|
53
|
+
const byLocation = Map.groupBy(services, ({ segments }) => `projects/${segments.projects}/locations/${segments.locations}`);
|
|
54
|
+
const nodes = {};
|
|
55
|
+
const warnings = [];
|
|
56
|
+
await Promise.all([...byLocation].map(async ([parent, serviceNodes]) => {
|
|
57
|
+
let deployed;
|
|
58
|
+
try {
|
|
59
|
+
[deployed] = await servicesClient.listServices({ parent });
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
warnings.push({
|
|
63
|
+
message: `Failed to list the Cloud Run services in '${parent}': ${error.message ?? error}`,
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const byName = new Map(deployed.map((s) => [s.name, s]));
|
|
68
|
+
for (const { node, name } of serviceNodes) {
|
|
69
|
+
const service = byName.get(name);
|
|
70
|
+
if (!service) {
|
|
71
|
+
warnings.push({
|
|
72
|
+
message: `The Cloud Run service '${name}' of '${node.id}' does not exist.`,
|
|
73
|
+
});
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const deployment = toDeployment(service);
|
|
77
|
+
const { updatedAt } = deployment;
|
|
78
|
+
if (updatedAt && updatedAt > environment.at) {
|
|
79
|
+
warnings.push({
|
|
80
|
+
message: `The Cloud Run service '${name}' of '${node.id}' was updated at ${updatedAt.toISOString()}, after the end of the evaluation window. Its deployment is the current one.`,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
nodes[node.id] = { data: { deployment } };
|
|
84
|
+
}
|
|
85
|
+
}));
|
|
86
|
+
return { nodes, warnings };
|
|
87
|
+
},
|
|
88
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { ScheduledJobAttemptStatus, } from '@causa/workspace-core';
|
|
2
|
+
import { googleResources } from '../resources.js';
|
|
3
|
+
/**
|
|
4
|
+
* Returns the data of a scheduled job.
|
|
5
|
+
* The attempt succeeded if its status code is `OK`. The status message is kept, or the name of the code if the attempt
|
|
6
|
+
* failed without a message.
|
|
7
|
+
*
|
|
8
|
+
* @param job The Cloud Scheduler job.
|
|
9
|
+
* @param statusCodes The gRPC status codes, which are passed as `google-gax` is imported dynamically.
|
|
10
|
+
* @returns The data of the node.
|
|
11
|
+
*/
|
|
12
|
+
function toData(job, statusCodes) {
|
|
13
|
+
const code = job.status?.code ?? statusCodes.OK;
|
|
14
|
+
const succeeded = code === statusCodes.OK;
|
|
15
|
+
const message = job.status?.message ||
|
|
16
|
+
(succeeded ? undefined : (statusCodes[code] ?? `${code}`));
|
|
17
|
+
return {
|
|
18
|
+
...(job.lastAttemptTime
|
|
19
|
+
? {
|
|
20
|
+
lastAttempt: {
|
|
21
|
+
time: new Date(job.lastAttemptTime),
|
|
22
|
+
status: succeeded
|
|
23
|
+
? ScheduledJobAttemptStatus.Succeeded
|
|
24
|
+
: ScheduledJobAttemptStatus.Failed,
|
|
25
|
+
...(message ? { message } : {}),
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
: {}),
|
|
29
|
+
...(job.scheduleTime && job.state === 'ENABLED'
|
|
30
|
+
? { nextRun: new Date(job.scheduleTime) }
|
|
31
|
+
: {}),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reads the last attempt and next run of Cloud Scheduler jobs.
|
|
36
|
+
*/
|
|
37
|
+
export const googleCloudScheduler = {
|
|
38
|
+
name: 'googleCloudScheduler',
|
|
39
|
+
description: 'The `lastAttempt` and `nextRun` of Cloud Scheduler jobs. Paused jobs have no next run. They are the current ones, with a warning when the job was attempted after the end of the evaluation window.',
|
|
40
|
+
fetch: async (environment) => {
|
|
41
|
+
const jobs = googleResources(environment, 'cloudscheduler.googleapis.com/Job', 'scheduledJob');
|
|
42
|
+
const byLocation = Map.groupBy(jobs, ({ segments }) => `projects/${segments.projects}/locations/${segments.locations}`);
|
|
43
|
+
if (byLocation.size === 0) {
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
const [{ GoogleApisService }, { Status }] = await Promise.all([
|
|
47
|
+
import('../../../services/google-apis.js'),
|
|
48
|
+
import('google-gax'),
|
|
49
|
+
]);
|
|
50
|
+
const client = await environment.context
|
|
51
|
+
.service(GoogleApisService)
|
|
52
|
+
.getClient('cloudscheduler', 'v1', {});
|
|
53
|
+
const nodes = {};
|
|
54
|
+
const warnings = [];
|
|
55
|
+
await Promise.all([...byLocation].map(async ([parent, jobNodes]) => {
|
|
56
|
+
const deployed = new Map();
|
|
57
|
+
try {
|
|
58
|
+
let pageToken;
|
|
59
|
+
do {
|
|
60
|
+
const { data } = await client.projects.locations.jobs.list({
|
|
61
|
+
parent,
|
|
62
|
+
pageSize: 500,
|
|
63
|
+
pageToken,
|
|
64
|
+
});
|
|
65
|
+
(data.jobs ?? []).forEach((j) => j.name && deployed.set(j.name, j));
|
|
66
|
+
pageToken = data.nextPageToken ?? undefined;
|
|
67
|
+
} while (pageToken);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
warnings.push({
|
|
71
|
+
message: `Failed to list the Cloud Scheduler jobs in '${parent}': ${error.message ?? error}`,
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
for (const { node, name } of jobNodes) {
|
|
76
|
+
const job = deployed.get(name);
|
|
77
|
+
if (!job) {
|
|
78
|
+
warnings.push({
|
|
79
|
+
message: `The Cloud Scheduler job '${name}' of '${node.id}' does not exist.`,
|
|
80
|
+
});
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const data = toData(job, Status);
|
|
84
|
+
const time = data.lastAttempt?.time;
|
|
85
|
+
if (time && time > environment.at) {
|
|
86
|
+
warnings.push({
|
|
87
|
+
message: `The Cloud Scheduler job '${name}' of '${node.id}' was last attempted at ${time.toISOString()}, after the end of the evaluation window. Its last attempt and next run are the current ones.`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
nodes[node.id] = { data };
|
|
91
|
+
}
|
|
92
|
+
}));
|
|
93
|
+
return { nodes, warnings };
|
|
94
|
+
},
|
|
95
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { GraphEnvironmentFetcher } from '@causa/workspace-core';
|
|
2
|
+
/**
|
|
3
|
+
* Builds the URL of a page of the Google Cloud console.
|
|
4
|
+
*
|
|
5
|
+
* @param segments The segments of the path of the page, which are encoded.
|
|
6
|
+
* @param project The GCP project.
|
|
7
|
+
* @param query Additional query parameters.
|
|
8
|
+
* @returns The URL.
|
|
9
|
+
*/
|
|
10
|
+
export declare function consoleUrl(segments: string[], project: string, query?: Record<string, string>): string;
|
|
11
|
+
/**
|
|
12
|
+
* Builds links to the Google Cloud console for the nodes' resources.
|
|
13
|
+
*/
|
|
14
|
+
export declare const googleConsoleLinks: GraphEnvironmentFetcher;
|