@causa/workspace-google 1.4.0-beta.1 → 1.4.0-beta.3
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 +19 -3
- package/dist/functions/event-topic/count-events-bigquery.call.d.ts +3 -0
- package/dist/functions/event-topic/count-events-bigquery.call.js +75 -0
- package/dist/functions/event-topic/count-events-bigquery.d.ts +11 -0
- package/dist/functions/event-topic/count-events-bigquery.js +19 -0
- package/dist/functions/event-topic/create-backfill-source-bigquery.call.js +6 -0
- package/dist/functions/event-topic/create-backfill-source-bigquery.d.ts +1 -1
- package/dist/functions/event-topic/create-backfill-source-bigquery.js +1 -1
- package/dist/functions/event-topic/index.d.ts +1 -0
- package/dist/functions/event-topic/index.js +1 -0
- package/dist/functions/event-topic/query-events-bigquery.call.js +2 -0
- package/dist/functions/event-topic/query-events-bigquery.d.ts +1 -0
- package/dist/functions/event-topic/query-events-bigquery.js +1 -0
- 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/merge-rules.js +3 -1
- package/dist/functions/google-identity-platform/generate-custom-token.js +7 -2
- package/dist/functions/google-pubsub/write-topics.js +7 -2
- package/dist/functions/google-services/enable.js +3 -1
- package/dist/functions/google-spanner/count-records.call.d.ts +16 -0
- package/dist/functions/google-spanner/count-records.call.js +120 -0
- package/dist/functions/google-spanner/count-records.d.ts +15 -0
- package/dist/functions/google-spanner/count-records.js +21 -0
- package/dist/functions/google-spanner/index.d.ts +3 -1
- package/dist/functions/google-spanner/index.js +3 -1
- package/dist/functions/google-spanner/list-databases.d.ts +22 -1
- package/dist/functions/google-spanner/list-databases.js +27 -15
- package/dist/functions/google-spanner/resolve-table.d.ts +25 -0
- package/dist/functions/google-spanner/resolve-table.js +44 -0
- 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 +2 -0
- package/dist/functions/graph/index.js +2 -0
- package/dist/functions/index.js +4 -4
- package/dist/graph/cloud-run.d.ts +2 -2
- package/dist/graph/cloud-run.js +2 -2
- 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/index.d.ts +1 -0
- package/dist/graph/index.js +1 -0
- package/dist/graph/rules/configuration/service-container.js +4 -4
- package/dist/graph/rules/terraform/spanner-databases.js +4 -4
- package/dist/graph/spanner.d.ts +11 -60
- package/dist/graph/spanner.js +25 -69
- package/package.json +24 -24
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { WorkspaceFunction, listFilesAndFormat } from '@causa/workspace';
|
|
2
2
|
import { readFile } from 'fs/promises';
|
|
3
|
+
/**
|
|
4
|
+
* Matches a `CREATE TABLE` DDL statement, capturing the name of the table.
|
|
5
|
+
*/
|
|
6
|
+
const CREATE_TABLE_REGEX = /^CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"']?([A-Za-z_]\w*)/i;
|
|
3
7
|
/**
|
|
4
8
|
* The default format used to infer the ID / name of the database from the format parts.
|
|
5
9
|
*/
|
|
@@ -33,10 +37,14 @@ export class GoogleSpannerListDatabases extends WorkspaceFunction {
|
|
|
33
37
|
}, {});
|
|
34
38
|
return await Promise.all(Object.entries(databaseDdlFiles).map(async ([id, ddlFiles]) => {
|
|
35
39
|
ddlFiles.sort();
|
|
36
|
-
const
|
|
37
|
-
const ddls =
|
|
40
|
+
const statements = (await Promise.all(ddlFiles.map((ddlFile) => this.parseDdlFile(ddlFile)))).flat();
|
|
41
|
+
const ddls = statements.map(({ statement }) => statement);
|
|
42
|
+
const tables = statements.flatMap(({ statement, file, line }) => {
|
|
43
|
+
const name = statement.match(CREATE_TABLE_REGEX)?.[1];
|
|
44
|
+
return name ? [{ name, file, line }] : [];
|
|
45
|
+
});
|
|
38
46
|
this._context.logger.debug(`🗃️ Found Spanner database '${id}' with ${ddlFiles.length} DDL file(s) and ${ddls.length} statement(s).`);
|
|
39
|
-
return { id, ddlFiles, ddls };
|
|
47
|
+
return { id, ddlFiles, ddls, tables };
|
|
40
48
|
}));
|
|
41
49
|
}
|
|
42
50
|
/**
|
|
@@ -44,21 +52,25 @@ export class GoogleSpannerListDatabases extends WorkspaceFunction {
|
|
|
44
52
|
* End-of-the-line comments are removed, and statements are split using semicolons.
|
|
45
53
|
*
|
|
46
54
|
* @param path The path to the SQL file containing DDL statements.
|
|
47
|
-
* @returns The list of DDL statements.
|
|
55
|
+
* @returns The list of DDL statements, along with the file and the line at which each statement starts.
|
|
48
56
|
*/
|
|
49
57
|
async parseDdlFile(path) {
|
|
50
58
|
const rawFile = (await readFile(path)).toString();
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
.
|
|
56
|
-
.
|
|
57
|
-
.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
// Removing comments using the `-- My comment until the end of the line` syntax, which preserves line numbers.
|
|
60
|
+
const withoutComments = rawFile.replace(/--.*$/gm, '');
|
|
61
|
+
let line = 1;
|
|
62
|
+
return withoutComments.split(';').map((rawStatement) => {
|
|
63
|
+
const leadingWhitespace = rawStatement.match(/^\s*/)[0];
|
|
64
|
+
const startLine = line + leadingWhitespace.split('\n').length - 1;
|
|
65
|
+
line += rawStatement.split('\n').length - 1;
|
|
66
|
+
const statement = rawStatement
|
|
67
|
+
// Trimming lines for readability in logs, and joining them into a single line.
|
|
68
|
+
.split('\n')
|
|
69
|
+
.map((statementLine) => statementLine.trim())
|
|
70
|
+
.join(' ')
|
|
71
|
+
.trim();
|
|
72
|
+
return { statement, file: path, line: startLine };
|
|
73
|
+
});
|
|
62
74
|
}
|
|
63
75
|
_supports() {
|
|
64
76
|
return true;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SpannerDatabase, SpannerTable } from './list-databases.js';
|
|
2
|
+
/**
|
|
3
|
+
* The resolution of a table name to the database whose DDL creates it.
|
|
4
|
+
*/
|
|
5
|
+
export type SpannerTableResolution<D extends SpannerDatabase = SpannerDatabase> = {
|
|
6
|
+
readonly kind: 'resolved';
|
|
7
|
+
readonly database: D;
|
|
8
|
+
readonly table: SpannerTable;
|
|
9
|
+
} | {
|
|
10
|
+
readonly kind: 'ambiguous';
|
|
11
|
+
readonly candidates: D[];
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: 'missing';
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Resolves a table name to the database whose DDL creates it. When several databases create a table with that name, the
|
|
17
|
+
* one with DDL files in the domain of the schema is chosen.
|
|
18
|
+
*
|
|
19
|
+
* @param databases The Spanner databases, as listed by `GoogleSpannerListDatabases`.
|
|
20
|
+
* @param table The name of the table.
|
|
21
|
+
* @param schemaFile The absolute path of the file declaring the schema persisted in the table.
|
|
22
|
+
* @param domainPaths The absolute paths of the domains of the workspace.
|
|
23
|
+
* @returns The resolution.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveSpannerTable<D extends SpannerDatabase>(databases: readonly D[], table: string, schemaFile: string, domainPaths: readonly string[]): SpannerTableResolution<D>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { sep } from 'path';
|
|
2
|
+
/**
|
|
3
|
+
* Returns the deepest domain containing a path.
|
|
4
|
+
*
|
|
5
|
+
* @param domainPaths The absolute paths of the domains.
|
|
6
|
+
* @param path The absolute path.
|
|
7
|
+
* @returns The absolute path of the domain, or `undefined` if the path is not part of a domain.
|
|
8
|
+
*/
|
|
9
|
+
function domainOf(domainPaths, path) {
|
|
10
|
+
return domainPaths
|
|
11
|
+
.filter((domain) => path.startsWith(`${domain}${sep}`))
|
|
12
|
+
.sort((a, b) => b.length - a.length)[0];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolves a table name to the database whose DDL creates it. When several databases create a table with that name, the
|
|
16
|
+
* one with DDL files in the domain of the schema is chosen.
|
|
17
|
+
*
|
|
18
|
+
* @param databases The Spanner databases, as listed by `GoogleSpannerListDatabases`.
|
|
19
|
+
* @param table The name of the table.
|
|
20
|
+
* @param schemaFile The absolute path of the file declaring the schema persisted in the table.
|
|
21
|
+
* @param domainPaths The absolute paths of the domains of the workspace.
|
|
22
|
+
* @returns The resolution.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveSpannerTable(databases, table, schemaFile, domainPaths) {
|
|
25
|
+
const candidates = databases.filter((d) => d.tables.some((t) => t.name === table));
|
|
26
|
+
const pick = (database) => ({
|
|
27
|
+
kind: 'resolved',
|
|
28
|
+
database,
|
|
29
|
+
table: database.tables.find((t) => t.name === table),
|
|
30
|
+
});
|
|
31
|
+
if (candidates.length === 1) {
|
|
32
|
+
return pick(candidates[0]);
|
|
33
|
+
}
|
|
34
|
+
if (candidates.length === 0) {
|
|
35
|
+
return { kind: 'missing' };
|
|
36
|
+
}
|
|
37
|
+
const domain = domainOf(domainPaths, schemaFile);
|
|
38
|
+
const inDomain = domain
|
|
39
|
+
? candidates.filter(({ ddlFiles }) => ddlFiles.some((f) => domainOf(domainPaths, f) === domain))
|
|
40
|
+
: [];
|
|
41
|
+
return inDomain.length === 1
|
|
42
|
+
? pick(inDomain[0])
|
|
43
|
+
: { kind: 'ambiguous', candidates };
|
|
44
|
+
}
|
|
@@ -8,6 +8,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
10
|
import { WorkspaceFunction } from '@causa/workspace';
|
|
11
|
+
import { PassArgumentsByReference } from '@causa/workspace/function-registry';
|
|
11
12
|
import { CAUSA_FOLDER } from '@causa/workspace/initialization';
|
|
12
13
|
import { AllowMissing } from '@causa/workspace/validation';
|
|
13
14
|
import { IsBoolean } from 'class-validator';
|
|
@@ -25,7 +26,7 @@ const DEFAULT_DATABASE_CONFIGURATIONS_DIRECTORY = join(CAUSA_FOLDER, 'spanner-da
|
|
|
25
26
|
* database configurations.
|
|
26
27
|
* This function returns a partial configuration, such that it can be used as a processor.
|
|
27
28
|
*/
|
|
28
|
-
|
|
29
|
+
let GoogleSpannerWriteDatabases = class GoogleSpannerWriteDatabases extends WorkspaceFunction {
|
|
29
30
|
tearDown;
|
|
30
31
|
/**
|
|
31
32
|
* Returns the path to the directory where Spanner databases configurations should be written.
|
|
@@ -64,9 +65,13 @@ export class GoogleSpannerWriteDatabases extends WorkspaceFunction {
|
|
|
64
65
|
_supports() {
|
|
65
66
|
return true;
|
|
66
67
|
}
|
|
67
|
-
}
|
|
68
|
+
};
|
|
68
69
|
__decorate([
|
|
69
70
|
IsBoolean(),
|
|
70
71
|
AllowMissing(),
|
|
71
72
|
__metadata("design:type", Boolean)
|
|
72
73
|
], GoogleSpannerWriteDatabases.prototype, "tearDown", void 0);
|
|
74
|
+
GoogleSpannerWriteDatabases = __decorate([
|
|
75
|
+
PassArgumentsByReference()
|
|
76
|
+
], GoogleSpannerWriteDatabases);
|
|
77
|
+
export { GoogleSpannerWriteDatabases };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type GraphMetricSeries } from '@causa/workspace-core';
|
|
2
|
+
import type { GraphFetchEnvironmentMetricSeriesForGoogle } from './fetch-environment-metric-series.js';
|
|
3
|
+
export default function call(this: GraphFetchEnvironmentMetricSeriesForGoogle): Promise<GraphMetricSeries>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { GraphMetricKind, } from '@causa/workspace-core';
|
|
2
|
+
import { GraphEnvironmentContext } from '@causa/workspace-core/graph';
|
|
3
|
+
import { monitoringMetricKind } from '../../graph/environment/monitoring-catalog.js';
|
|
4
|
+
import { MIN_ALIGNMENT_PERIOD, monitoringEnd, queryMonitoringMetric, } from '../../graph/environment/monitoring-query.js';
|
|
5
|
+
import { BucketLayoutRegistry } from '../../graph/environment/monitoring-values.js';
|
|
6
|
+
/**
|
|
7
|
+
* The steps between points from which the default one is chosen, in seconds.
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_STEPS = [
|
|
10
|
+
60, 120, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200, 86400,
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* The maximum number of points in a series when the step is chosen by default.
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_MAX_POINTS = 300;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the default step for a series, as the smallest of {@link DEFAULT_STEPS} that results in at most
|
|
18
|
+
* {@link DEFAULT_MAX_POINTS} points. For longer series, a whole number of days is used.
|
|
19
|
+
*
|
|
20
|
+
* @param duration The duration of the series, in seconds.
|
|
21
|
+
* @returns The step, in seconds.
|
|
22
|
+
*/
|
|
23
|
+
function defaultStep(duration) {
|
|
24
|
+
const minimum = duration / DEFAULT_MAX_POINTS;
|
|
25
|
+
const day = DEFAULT_STEPS[DEFAULT_STEPS.length - 1];
|
|
26
|
+
return (DEFAULT_STEPS.find((step) => step >= minimum) ??
|
|
27
|
+
Math.ceil(minimum / day) * day);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The maximum number of points in a series.
|
|
31
|
+
*/
|
|
32
|
+
const MAX_POINTS = 10000;
|
|
33
|
+
export default async function call() {
|
|
34
|
+
const metric = this.findMetric();
|
|
35
|
+
if (!metric) {
|
|
36
|
+
throw new Error(`The metric '${this.metric}' of node '${this.node}' is not read from Cloud Monitoring.`);
|
|
37
|
+
}
|
|
38
|
+
const { start: requestedStart } = this;
|
|
39
|
+
if (isNaN(this.end.getTime()) || isNaN(requestedStart.getTime())) {
|
|
40
|
+
throw new Error('The start and end of the series must be valid dates.');
|
|
41
|
+
}
|
|
42
|
+
const end = monitoringEnd(this.end);
|
|
43
|
+
const duration = (end.getTime() - requestedStart.getTime()) / 1000;
|
|
44
|
+
const requestedStep = this.step ?? defaultStep(duration);
|
|
45
|
+
if (!Number.isInteger(requestedStep) ||
|
|
46
|
+
requestedStep < MIN_ALIGNMENT_PERIOD) {
|
|
47
|
+
throw new Error(`The step must be a whole number of seconds, of at least ${MIN_ALIGNMENT_PERIOD} seconds.`);
|
|
48
|
+
}
|
|
49
|
+
const requestedCount = Math.floor(duration / requestedStep);
|
|
50
|
+
if (requestedCount < 1 || requestedCount > MAX_POINTS) {
|
|
51
|
+
throw new Error(`The series must contain between 1 and ${MAX_POINTS} points, but contains ${Math.max(requestedCount, 0)}.`);
|
|
52
|
+
}
|
|
53
|
+
// Metrics sampled less often than the step are read with a larger step, and at least one point, as for the
|
|
54
|
+
// point-in-time value.
|
|
55
|
+
const step = Math.max(requestedStep, metric.minPeriod ?? 0);
|
|
56
|
+
const count = step === requestedStep
|
|
57
|
+
? requestedCount
|
|
58
|
+
: Math.max(Math.floor(duration / step), 1);
|
|
59
|
+
if (!this.graph.environment) {
|
|
60
|
+
throw new Error('The graph must be enriched with environment data to fetch a series.');
|
|
61
|
+
}
|
|
62
|
+
const environment = await GraphEnvironmentContext.forEnrichedGraph(this._context, this.graph);
|
|
63
|
+
const layouts = new BucketLayoutRegistry();
|
|
64
|
+
// Warnings, e.g. about time series relating to resources missing from the graph, don't affect the values.
|
|
65
|
+
const { values, groupBy, errors } = await queryMonitoringMetric(environment, metric, { end, period: step, count }, layouts, { nodes: [this.node] });
|
|
66
|
+
if (errors.length > 0) {
|
|
67
|
+
throw new Error(errors.map((e) => e.message).join(' '));
|
|
68
|
+
}
|
|
69
|
+
const points = values.get(this.node) ?? new Array(count).fill(undefined);
|
|
70
|
+
const start = new Date(end.getTime() - count * step * 1000);
|
|
71
|
+
return {
|
|
72
|
+
start,
|
|
73
|
+
step,
|
|
74
|
+
values: points.map((p) => p?.value ?? null),
|
|
75
|
+
...(groupBy
|
|
76
|
+
? {
|
|
77
|
+
groupBy,
|
|
78
|
+
groups: seriesGroups(points, monitoringMetricKind(metric) === GraphMetricKind.Rate ? 0 : null),
|
|
79
|
+
}
|
|
80
|
+
: {}),
|
|
81
|
+
bucketLayouts: layouts.all,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Returns the series of each group, from the points of the series.
|
|
86
|
+
*
|
|
87
|
+
* @param points The points of the series.
|
|
88
|
+
* @param missing The value of a group in a period where the point exists but not the group, e.g. `0` for rates.
|
|
89
|
+
* @returns The series of each group, sorted by key.
|
|
90
|
+
*/
|
|
91
|
+
function seriesGroups(points, missing) {
|
|
92
|
+
const groups = new Map();
|
|
93
|
+
points.forEach((point, index) => {
|
|
94
|
+
for (const { key, value } of point?.groups ?? []) {
|
|
95
|
+
const serialized = JSON.stringify(key);
|
|
96
|
+
const group = groups.get(serialized) ?? {
|
|
97
|
+
key,
|
|
98
|
+
values: points.map((p) => (p ? missing : null)),
|
|
99
|
+
};
|
|
100
|
+
groups.set(serialized, group);
|
|
101
|
+
group.values[index] = value;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return [...groups.entries()]
|
|
105
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
106
|
+
.map(([, group]) => group);
|
|
107
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { GraphFetchEnvironmentMetricSeries, type GraphMetricSeries } from '@causa/workspace-core';
|
|
2
|
+
import { type GraphNodeEntry } from '@causa/workspace-core/graph';
|
|
3
|
+
import { type MonitoringMetric } from '../../graph/environment/monitoring-catalog.js';
|
|
4
|
+
/**
|
|
5
|
+
* Implements {@link GraphFetchEnvironmentMetricSeries} for the metrics read from Cloud Monitoring.
|
|
6
|
+
*/
|
|
7
|
+
export declare class GraphFetchEnvironmentMetricSeriesForGoogle extends GraphFetchEnvironmentMetricSeries {
|
|
8
|
+
_call(): Promise<GraphMetricSeries>;
|
|
9
|
+
/**
|
|
10
|
+
* Returns the requested node, from the graph.
|
|
11
|
+
*
|
|
12
|
+
* @returns The node, or `undefined` if it does not exist.
|
|
13
|
+
*/
|
|
14
|
+
findNode(): GraphNodeEntry | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Returns the Cloud Monitoring metric providing the requested metric of the node.
|
|
17
|
+
*
|
|
18
|
+
* @returns The metric, or `undefined` if the node does not exist, has no resource, or the metric is not read from
|
|
19
|
+
* Cloud Monitoring for it.
|
|
20
|
+
*/
|
|
21
|
+
findMetric(): MonitoringMetric | undefined;
|
|
22
|
+
_supports(): boolean;
|
|
23
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { callDeferred } from '@causa/workspace';
|
|
2
|
+
import { GraphFetchEnvironmentMetricSeries, } from '@causa/workspace-core';
|
|
3
|
+
import { listGraphNodes, } from '@causa/workspace-core/graph';
|
|
4
|
+
import { MONITORING_METRICS, } from '../../graph/environment/monitoring-catalog.js';
|
|
5
|
+
/**
|
|
6
|
+
* Implements {@link GraphFetchEnvironmentMetricSeries} for the metrics read from Cloud Monitoring.
|
|
7
|
+
*/
|
|
8
|
+
export class GraphFetchEnvironmentMetricSeriesForGoogle extends GraphFetchEnvironmentMetricSeries {
|
|
9
|
+
async _call() {
|
|
10
|
+
return await callDeferred(this, import.meta.url);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Returns the requested node, from the graph.
|
|
14
|
+
*
|
|
15
|
+
* @returns The node, or `undefined` if it does not exist.
|
|
16
|
+
*/
|
|
17
|
+
findNode() {
|
|
18
|
+
return listGraphNodes(this.graph).find((n) => n.id === this.node);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Returns the Cloud Monitoring metric providing the requested metric of the node.
|
|
22
|
+
*
|
|
23
|
+
* @returns The metric, or `undefined` if the node does not exist, has no resource, or the metric is not read from
|
|
24
|
+
* Cloud Monitoring for it.
|
|
25
|
+
*/
|
|
26
|
+
findMetric() {
|
|
27
|
+
const node = this.findNode();
|
|
28
|
+
const resource = node?.node.data?.resource;
|
|
29
|
+
if (!node || typeof resource?.id !== 'string') {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return MONITORING_METRICS.find((m) => m.metric === this.metric &&
|
|
33
|
+
m.mapping.nodeType === node.type &&
|
|
34
|
+
m.mapping.resourceType === resource.type &&
|
|
35
|
+
(!m.measures || m.measures(node, this.graph)));
|
|
36
|
+
}
|
|
37
|
+
_supports() {
|
|
38
|
+
return !!this.findMetric();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GraphGetEnvironmentProvider, type GraphEnvironmentProvider } from '@causa/workspace-core';
|
|
2
|
+
/**
|
|
3
|
+
* Implements {@link GraphGetEnvironmentProvider} for Google Cloud.
|
|
4
|
+
* The fetchers read metrics and alerts from Cloud Monitoring, open error groups from Error Reporting as alerts, the
|
|
5
|
+
* state of Cloud Run services and Cloud Scheduler jobs, and build links to the Google Cloud console. Cloud Tasks
|
|
6
|
+
* queues, only known by a prefix of their name, are resolved by listing them.
|
|
7
|
+
*/
|
|
8
|
+
export declare class GraphGetEnvironmentProviderForGoogle extends GraphGetEnvironmentProvider {
|
|
9
|
+
_call(): GraphEnvironmentProvider;
|
|
10
|
+
_supports(): boolean;
|
|
11
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { GraphGetEnvironmentProvider, } from '@causa/workspace-core';
|
|
2
|
+
import { CLOUD_TASKS_QUEUE_RESOLVER, GOOGLE_GRAPH_ENVIRONMENT_FETCHERS, GOOGLE_GRAPH_METRIC_DEFINITIONS, } from '../../graph/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Implements {@link GraphGetEnvironmentProvider} for Google Cloud.
|
|
5
|
+
* The fetchers read metrics and alerts from Cloud Monitoring, open error groups from Error Reporting as alerts, the
|
|
6
|
+
* state of Cloud Run services and Cloud Scheduler jobs, and build links to the Google Cloud console. Cloud Tasks
|
|
7
|
+
* queues, only known by a prefix of their name, are resolved by listing them.
|
|
8
|
+
*/
|
|
9
|
+
export class GraphGetEnvironmentProviderForGoogle extends GraphGetEnvironmentProvider {
|
|
10
|
+
_call() {
|
|
11
|
+
return {
|
|
12
|
+
metrics: GOOGLE_GRAPH_METRIC_DEFINITIONS,
|
|
13
|
+
prefixResolvers: [CLOUD_TASKS_QUEUE_RESOLVER],
|
|
14
|
+
fetchers: GOOGLE_GRAPH_ENVIRONMENT_FETCHERS,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
_supports() {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
}
|
package/dist/functions/index.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { CausaListConfigurationSchemasForGoogle } from './causa/index.js';
|
|
2
2
|
import { EmulatorStartForFirebaseStorage, EmulatorStartForFirestore, EmulatorStartForIdentityPlatform, EmulatorStartForPubSub, EmulatorStartForSpanner, EmulatorStopForFirebaseStorage, EmulatorStopForFirestore, EmulatorStopForIdentityPlatform, EmulatorStopForPubSub, EmulatorStopForSpanner, } from './emulator/index.js';
|
|
3
|
-
import { EventTopicBrokerCreateTopicForPubSub, EventTopicBrokerCreateTriggerForCloudRun, EventTopicBrokerDeleteTopicForPubSub, EventTopicBrokerDeleteTriggerResourceForCloudRunInvokerRole, EventTopicBrokerDeleteTriggerResourceForCloudRunService, EventTopicBrokerDeleteTriggerResourceForPubSubSubscription, EventTopicBrokerDeleteTriggerResourceForServiceAccount, EventTopicBrokerGetTopicIdForPubSub, EventTopicBrokerPublishEventsForGoogle, EventTopicBrokerWaitForProcessingForPubSub, EventTopicCreateBackfillSourceForBigQuery, EventTopicQueryEventsForBigQuery, } from './event-topic/index.js';
|
|
3
|
+
import { EventTopicBrokerCreateTopicForPubSub, EventTopicBrokerCreateTriggerForCloudRun, EventTopicBrokerDeleteTopicForPubSub, EventTopicBrokerDeleteTriggerResourceForCloudRunInvokerRole, EventTopicBrokerDeleteTriggerResourceForCloudRunService, EventTopicBrokerDeleteTriggerResourceForPubSubSubscription, EventTopicBrokerDeleteTriggerResourceForServiceAccount, EventTopicBrokerGetTopicIdForPubSub, EventTopicBrokerPublishEventsForGoogle, EventTopicBrokerWaitForProcessingForPubSub, EventTopicCountEventsForBigQuery, EventTopicCreateBackfillSourceForBigQuery, EventTopicQueryEventsForBigQuery, } from './event-topic/index.js';
|
|
4
4
|
import { GoogleAppCheckGenerateToken } from './google-app-check/index.js';
|
|
5
5
|
import { GoogleFirebaseStorageMergeRules } from './google-firebase-storage/index.js';
|
|
6
6
|
import { DatabaseQueryRecordsForFirestore, GoogleFirestoreMergeRules, ModelGenerateTypeScriptDecoratorsForGoogleFirestore, ModelSchemaExtractDatabaseForGoogleFirestore, } from './google-firestore/index.js';
|
|
7
7
|
import { GoogleIdentityPlatformGenerateCustomToken, GoogleIdentityPlatformGenerateToken, } from './google-identity-platform/index.js';
|
|
8
8
|
import { GooglePubSubWriteTopics } from './google-pubsub/index.js';
|
|
9
9
|
import { GoogleServicesEnable } from './google-services/index.js';
|
|
10
|
-
import { DatabaseQueryRecordsForSpanner, GoogleSpannerListDatabases, GoogleSpannerWriteDatabases, ModelGenerateTypeScriptDecoratorsForGoogleSpanner, ModelSchemaExtractDatabaseForGoogleSpanner, } from './google-spanner/index.js';
|
|
11
|
-
import { GraphListRulesForGoogle } from './graph/index.js';
|
|
10
|
+
import { DatabaseCountRecordsForSpanner, DatabaseQueryRecordsForSpanner, GoogleSpannerListDatabases, GoogleSpannerWriteDatabases, ModelGenerateTypeScriptDecoratorsForGoogleSpanner, ModelSchemaExtractDatabaseForGoogleSpanner, } from './google-spanner/index.js';
|
|
11
|
+
import { GraphFetchEnvironmentMetricSeriesForGoogle, GraphGetEnvironmentProviderForGoogle, GraphListRulesForGoogle, } from './graph/index.js';
|
|
12
12
|
import { ProjectGetArtefactDestinationForCloudFunctions, ProjectGetArtefactDestinationForCloudRun, ProjectPushArtefactForCloudFunctions, } from './project/index.js';
|
|
13
13
|
import { SecretFetchForGoogleAccessToken, SecretFetchForGoogleSecretManager, } from './secret/index.js';
|
|
14
14
|
import { ServiceContainerQueryLogsForCloudRun } from './service-container/index.js';
|
|
15
15
|
import { ModelGenerateTypeScriptTriggerDecoratorsForGoogle } from './typescript/index.js';
|
|
16
16
|
export function registerFunctions(context) {
|
|
17
|
-
context.registerFunctionImplementations(CausaListConfigurationSchemasForGoogle, DatabaseQueryRecordsForFirestore, DatabaseQueryRecordsForSpanner, EmulatorStartForFirebaseStorage, EmulatorStartForFirestore, EmulatorStartForIdentityPlatform, EmulatorStartForPubSub, EmulatorStartForSpanner, EmulatorStopForFirebaseStorage, EmulatorStopForFirestore, EmulatorStopForIdentityPlatform, EmulatorStopForPubSub, EmulatorStopForSpanner, EventTopicBrokerCreateTopicForPubSub, EventTopicBrokerCreateTriggerForCloudRun, EventTopicBrokerDeleteTopicForPubSub, EventTopicBrokerDeleteTriggerResourceForCloudRunInvokerRole, EventTopicBrokerDeleteTriggerResourceForCloudRunService, EventTopicBrokerDeleteTriggerResourceForPubSubSubscription, EventTopicBrokerDeleteTriggerResourceForServiceAccount, EventTopicBrokerGetTopicIdForPubSub, EventTopicBrokerPublishEventsForGoogle, EventTopicBrokerWaitForProcessingForPubSub, EventTopicCreateBackfillSourceForBigQuery, EventTopicQueryEventsForBigQuery, GoogleAppCheckGenerateToken, GoogleFirebaseStorageMergeRules, GoogleFirestoreMergeRules, GoogleIdentityPlatformGenerateCustomToken, GoogleIdentityPlatformGenerateToken, GooglePubSubWriteTopics, GoogleServicesEnable, GoogleSpannerListDatabases, GoogleSpannerWriteDatabases, GraphListRulesForGoogle, ModelGenerateTypeScriptDecoratorsForGoogleFirestore, ModelGenerateTypeScriptDecoratorsForGoogleSpanner, ModelGenerateTypeScriptTriggerDecoratorsForGoogle, ModelSchemaExtractDatabaseForGoogleFirestore, ModelSchemaExtractDatabaseForGoogleSpanner, ProjectGetArtefactDestinationForCloudFunctions, ProjectGetArtefactDestinationForCloudRun, ProjectPushArtefactForCloudFunctions, SecretFetchForGoogleAccessToken, SecretFetchForGoogleSecretManager, ServiceContainerQueryLogsForCloudRun);
|
|
17
|
+
context.registerFunctionImplementations(CausaListConfigurationSchemasForGoogle, DatabaseCountRecordsForSpanner, DatabaseQueryRecordsForFirestore, DatabaseQueryRecordsForSpanner, EmulatorStartForFirebaseStorage, EmulatorStartForFirestore, EmulatorStartForIdentityPlatform, EmulatorStartForPubSub, EmulatorStartForSpanner, EmulatorStopForFirebaseStorage, EmulatorStopForFirestore, EmulatorStopForIdentityPlatform, EmulatorStopForPubSub, EmulatorStopForSpanner, EventTopicBrokerCreateTopicForPubSub, EventTopicBrokerCreateTriggerForCloudRun, EventTopicBrokerDeleteTopicForPubSub, EventTopicBrokerDeleteTriggerResourceForCloudRunInvokerRole, EventTopicBrokerDeleteTriggerResourceForCloudRunService, EventTopicBrokerDeleteTriggerResourceForPubSubSubscription, EventTopicBrokerDeleteTriggerResourceForServiceAccount, EventTopicBrokerGetTopicIdForPubSub, EventTopicBrokerPublishEventsForGoogle, EventTopicBrokerWaitForProcessingForPubSub, EventTopicCountEventsForBigQuery, EventTopicCreateBackfillSourceForBigQuery, EventTopicQueryEventsForBigQuery, GoogleAppCheckGenerateToken, GoogleFirebaseStorageMergeRules, GoogleFirestoreMergeRules, GoogleIdentityPlatformGenerateCustomToken, GoogleIdentityPlatformGenerateToken, GooglePubSubWriteTopics, GoogleServicesEnable, GoogleSpannerListDatabases, GoogleSpannerWriteDatabases, GraphFetchEnvironmentMetricSeriesForGoogle, GraphGetEnvironmentProviderForGoogle, GraphListRulesForGoogle, ModelGenerateTypeScriptDecoratorsForGoogleFirestore, ModelGenerateTypeScriptDecoratorsForGoogleSpanner, ModelGenerateTypeScriptTriggerDecoratorsForGoogle, ModelSchemaExtractDatabaseForGoogleFirestore, ModelSchemaExtractDatabaseForGoogleSpanner, ProjectGetArtefactDestinationForCloudFunctions, ProjectGetArtefactDestinationForCloudRun, ProjectPushArtefactForCloudFunctions, SecretFetchForGoogleAccessToken, SecretFetchForGoogleSecretManager, ServiceContainerQueryLogsForCloudRun);
|
|
18
18
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ServiceContainerConfiguration } from '@causa/workspace-core';
|
|
2
|
-
import {
|
|
2
|
+
import { GraphExtractionFact, type GraphContext, type GraphFactOutput, type GraphTemplatePart, type WorkspaceProject } from '@causa/workspace-core/graph';
|
|
3
3
|
import { type TerraformModuleBlock } from '@causa/workspace-terraform';
|
|
4
4
|
/**
|
|
5
5
|
* A trigger defined in the `serviceContainer.triggers` configuration.
|
|
@@ -83,7 +83,7 @@ export type CloudRunService = {
|
|
|
83
83
|
* The blocks of the Cloud Run module, evaluated against the configuration of the projects they deploy.
|
|
84
84
|
* Blocks whose `configuration_file` does not name a project of the workspace cannot be mirrored, and are reported.
|
|
85
85
|
*/
|
|
86
|
-
export declare class CloudRunFact extends
|
|
86
|
+
export declare class CloudRunFact extends GraphExtractionFact<CloudRunService[]> {
|
|
87
87
|
compute(graph: GraphContext): Promise<GraphFactOutput<CloudRunService[]>>;
|
|
88
88
|
/**
|
|
89
89
|
* Evaluates a Cloud Run module block, mirroring the defaults of the module.
|
package/dist/graph/cloud-run.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GraphExtractionFact, ProjectsFact, serviceId, } from '@causa/workspace-core/graph';
|
|
2
2
|
import { modulesWithSource, TerraformProjectsFact, } from '@causa/workspace-terraform';
|
|
3
3
|
import { argumentOrConfiguration, projectOfConfigurationFile, } from './terraform-modules.js';
|
|
4
4
|
/**
|
|
@@ -13,7 +13,7 @@ const MIRRORED_CLOUD_RUN_MAJOR = 1;
|
|
|
13
13
|
* The blocks of the Cloud Run module, evaluated against the configuration of the projects they deploy.
|
|
14
14
|
* Blocks whose `configuration_file` does not name a project of the workspace cannot be mirrored, and are reported.
|
|
15
15
|
*/
|
|
16
|
-
export class CloudRunFact extends
|
|
16
|
+
export class CloudRunFact extends GraphExtractionFact {
|
|
17
17
|
async compute(graph) {
|
|
18
18
|
const [terraformProjects, projects] = await Promise.all([
|
|
19
19
|
graph.get(TerraformProjectsFact),
|
|
@@ -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
|
+
};
|