@geode/opengeodeweb-front 10.34.0 → 10.35.0-rc.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/.build/opengeodeweb_front_schemas.js +1 -10
- package/.build/server/utils/cloud.js +1 -70
- package/app/stores/api.ts +68 -0
- package/app/stores/cloud.ts +18 -6
- package/internal/utils/api_fetch.ts +6 -6
- package/nuxt.config.ts +50 -2
- package/opengeodeweb_front_schemas.json +0 -15
- package/package.json +3 -6
- package/server/utils/cloud.ts +1 -107
- package/server/api/serverless/run_cloud.post.ts +0 -92
- package/server/api/serverless/schemas/run_cloud.json +0 -10
|
@@ -160,16 +160,7 @@ var api = {
|
|
|
160
160
|
"required": ["extension", "platform"],
|
|
161
161
|
"additionalProperties": false
|
|
162
162
|
} }
|
|
163
|
-
}
|
|
164
|
-
"serverless": { "run_cloud": {
|
|
165
|
-
"$id": "api/serverless/run_cloud",
|
|
166
|
-
"route": "/run_cloud",
|
|
167
|
-
"methods": ["POST"],
|
|
168
|
-
"type": "object",
|
|
169
|
-
"properties": { "email": { "type": "string" } },
|
|
170
|
-
"required": ["email"],
|
|
171
|
-
"additionalProperties": false
|
|
172
|
-
} }
|
|
163
|
+
}
|
|
173
164
|
};
|
|
174
165
|
//#endregion
|
|
175
166
|
export { api };
|
|
@@ -1,77 +1,8 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
|
-
import { google } from "googleapis";
|
|
5
4
|
//#region server/utils/cloud.ts
|
|
6
5
|
const LOCATIONS_DIR = "/etc/nginx/locations";
|
|
7
|
-
async function artifactImage(parent, authClient) {
|
|
8
|
-
const projectName = process.env.PROJECT;
|
|
9
|
-
const registry = google.artifactregistry({
|
|
10
|
-
version: "v1",
|
|
11
|
-
auth: authClient
|
|
12
|
-
});
|
|
13
|
-
const branch = process.env.NETLIFY_BRANCH;
|
|
14
|
-
const [, projectId] = parent.split("/");
|
|
15
|
-
const name = `${`${parent}/repositories/github/packages/`}${projectName}/tags/${branch}`;
|
|
16
|
-
console.log({ name });
|
|
17
|
-
const response = await registry.projects.locations.repositories.packages.tags.get({ name });
|
|
18
|
-
console.log({ response });
|
|
19
|
-
const digest = (response.data.version ?? "").split("/").pop();
|
|
20
|
-
const image = `${`europe-west9-docker.pkg.dev/${projectId}/github`}/${projectName}@${digest}`;
|
|
21
|
-
console.log("Found image for", projectName, image);
|
|
22
|
-
return image;
|
|
23
|
-
}
|
|
24
|
-
function sanitizeLabelValue(label) {
|
|
25
|
-
console.log("label", label);
|
|
26
|
-
return label.toLowerCase().replaceAll(/[^a-z0-9_-]/gu, "_").slice(0, 63);
|
|
27
|
-
}
|
|
28
|
-
function requestConfig(parent, image, email, projectName) {
|
|
29
|
-
const resources = {
|
|
30
|
-
limits: {
|
|
31
|
-
cpu: "2000m",
|
|
32
|
-
memory: "3Gi"
|
|
33
|
-
},
|
|
34
|
-
cpuIdle: false,
|
|
35
|
-
startupCpuBoost: true
|
|
36
|
-
};
|
|
37
|
-
const labels = {
|
|
38
|
-
user: sanitizeLabelValue(email),
|
|
39
|
-
project: sanitizeLabelValue(projectName)
|
|
40
|
-
};
|
|
41
|
-
return {
|
|
42
|
-
parent,
|
|
43
|
-
service: {
|
|
44
|
-
ingress: "INGRESS_TRAFFIC_ALL",
|
|
45
|
-
invokerIamDisabled: true,
|
|
46
|
-
labels,
|
|
47
|
-
scaling: {
|
|
48
|
-
scalingMode: "MANUAL",
|
|
49
|
-
manualInstanceCount: 1
|
|
50
|
-
},
|
|
51
|
-
template: {
|
|
52
|
-
labels,
|
|
53
|
-
timeout: { seconds: 3600 },
|
|
54
|
-
containers: [{
|
|
55
|
-
image,
|
|
56
|
-
ports: [{ containerPort: 80 }],
|
|
57
|
-
resources,
|
|
58
|
-
env: [{
|
|
59
|
-
name: "PARENT",
|
|
60
|
-
value: parent
|
|
61
|
-
}],
|
|
62
|
-
startupProbe: {
|
|
63
|
-
httpGet: {
|
|
64
|
-
port: 80,
|
|
65
|
-
path: "/viewer/healthcheck"
|
|
66
|
-
},
|
|
67
|
-
periodSeconds: 1,
|
|
68
|
-
failureThreshold: 30
|
|
69
|
-
}
|
|
70
|
-
}]
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
6
|
function addSupervisorProgram(name, command, executableArgs) {
|
|
76
7
|
const conf = `
|
|
77
8
|
[program:${name}]
|
|
@@ -153,4 +84,4 @@ function removeNginxLocation(name) {
|
|
|
153
84
|
return true;
|
|
154
85
|
}
|
|
155
86
|
//#endregion
|
|
156
|
-
export { addNginxLocation, addSupervisorProgram,
|
|
87
|
+
export { addNginxLocation, addSupervisorProgram, removeNginxLocation };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { RequestHandlers } from "@ogw_shared/utils/types";
|
|
2
|
+
import { api_fetch } from "@ogw_internal/utils/api_fetch";
|
|
3
|
+
|
|
4
|
+
interface ApiSchema {
|
|
5
|
+
$id: string;
|
|
6
|
+
methods: string[];
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type ApiCallbacks = RequestHandlers & {
|
|
11
|
+
skip_feedback_error?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const MILLISECONDS_IN_SECOND = 1000;
|
|
15
|
+
|
|
16
|
+
export const useAPIStore = defineStore("api", () => {
|
|
17
|
+
const request_counter = ref(0);
|
|
18
|
+
const base_url = ref(useRuntimeConfig().public.CLOUD_API_URL);
|
|
19
|
+
|
|
20
|
+
function start_request(): void {
|
|
21
|
+
request_counter.value += 1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function stop_request(): void {
|
|
25
|
+
request_counter.value -= 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// `TResult` is asserted, not verified: the response is only checked against `schema` at runtime
|
|
29
|
+
async function request<TResult = unknown>(
|
|
30
|
+
{
|
|
31
|
+
schema,
|
|
32
|
+
params = {},
|
|
33
|
+
headers = {},
|
|
34
|
+
}: { schema: ApiSchema; params?: Record<string, unknown>; headers?: Record<string, string> },
|
|
35
|
+
callbacks: ApiCallbacks = {},
|
|
36
|
+
): Promise<TResult> {
|
|
37
|
+
console.log("[API] Request:", schema.$id);
|
|
38
|
+
const start = Date.now();
|
|
39
|
+
|
|
40
|
+
const result = await api_fetch(
|
|
41
|
+
{ $id: schema.$id, base_url: base_url.value, start_request, stop_request },
|
|
42
|
+
{ schema, params, headers },
|
|
43
|
+
{
|
|
44
|
+
...callbacks,
|
|
45
|
+
response_function: async (response: unknown) => {
|
|
46
|
+
console.log(
|
|
47
|
+
"[API] Request completed:",
|
|
48
|
+
schema.$id,
|
|
49
|
+
"in",
|
|
50
|
+
(Date.now() - start) / MILLISECONDS_IN_SECOND,
|
|
51
|
+
"s",
|
|
52
|
+
);
|
|
53
|
+
if (callbacks.response_function) {
|
|
54
|
+
await callbacks.response_function(response);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
// oxlint-disable-next-line no-unsafe-type-assertion -- trusted API boundary; see comment above.
|
|
60
|
+
return result as TResult;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
base_url,
|
|
64
|
+
request,
|
|
65
|
+
start_request,
|
|
66
|
+
stop_request,
|
|
67
|
+
};
|
|
68
|
+
});
|
package/app/stores/cloud.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { Status } from "@ogw_front/utils/status";
|
|
2
|
-
import { fetchSchema } from "@ogw_shared/utils/fetch_schema";
|
|
3
2
|
import { setAppBaseUrl } from "@ogw_shared/scripts";
|
|
3
|
+
import { useAPIStore } from "@ogw_front/stores/api";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const run_cloud_schema = {
|
|
6
|
+
$id: "/cloud/run",
|
|
7
|
+
methods: ["POST"],
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
email: { type: "string" },
|
|
11
|
+
project: { type: "string" },
|
|
12
|
+
branch: { type: "string" },
|
|
13
|
+
},
|
|
14
|
+
required: ["email", "project", "branch"],
|
|
15
|
+
additionalProperties: false,
|
|
16
|
+
};
|
|
6
17
|
|
|
7
18
|
export const useCloudStore = defineStore("cloud", {
|
|
8
19
|
state: () => ({
|
|
@@ -11,14 +22,15 @@ export const useCloudStore = defineStore("cloud", {
|
|
|
11
22
|
actions: {
|
|
12
23
|
async launch(email: string) {
|
|
13
24
|
this.status = Status.CONNECTING;
|
|
14
|
-
const
|
|
15
|
-
const params = { email };
|
|
25
|
+
const { PROJECT, BRANCH } = useRuntimeConfig().public;
|
|
26
|
+
const params = { email, project: PROJECT, branch: BRANCH };
|
|
16
27
|
const { useAppStore } = await import("./app");
|
|
17
28
|
const { useFeedbackStore } = await import("./feedback");
|
|
18
29
|
const appStore = useAppStore();
|
|
19
30
|
const feedbackStore = useFeedbackStore();
|
|
20
|
-
const
|
|
21
|
-
|
|
31
|
+
const APIStore = useAPIStore();
|
|
32
|
+
const result = await APIStore.request(
|
|
33
|
+
{ schema: run_cloud_schema, params },
|
|
22
34
|
{
|
|
23
35
|
request_error_function: () => {
|
|
24
36
|
feedbackStore.$patch({ server_error: true });
|
|
@@ -83,7 +83,7 @@ async function api_fetch(
|
|
|
83
83
|
timeout,
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
|
-
request_error_function(error: unknown) {
|
|
86
|
+
async request_error_function(error: unknown) {
|
|
87
87
|
microservice.stop_request();
|
|
88
88
|
const typedError = toFetchErrorLike(error);
|
|
89
89
|
if (skip_feedback_error !== true) {
|
|
@@ -95,17 +95,17 @@ async function api_fetch(
|
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
97
|
if (request_error_function) {
|
|
98
|
-
|
|
98
|
+
await request_error_function(error);
|
|
99
99
|
}
|
|
100
100
|
},
|
|
101
|
-
response_function(data: unknown) {
|
|
101
|
+
async response_function(data: unknown) {
|
|
102
102
|
endRequestLog(microservice, schema, requestStartingTime);
|
|
103
103
|
microservice.stop_request();
|
|
104
104
|
if (response_function) {
|
|
105
|
-
response_function(data);
|
|
105
|
+
await response_function(data);
|
|
106
106
|
}
|
|
107
107
|
},
|
|
108
|
-
response_error_function(response: unknown) {
|
|
108
|
+
async response_error_function(response: unknown) {
|
|
109
109
|
microservice.stop_request();
|
|
110
110
|
const typedResponse = toFetchErrorResponseLike(response);
|
|
111
111
|
if (skip_feedback_error !== true) {
|
|
@@ -117,7 +117,7 @@ async function api_fetch(
|
|
|
117
117
|
);
|
|
118
118
|
}
|
|
119
119
|
if (response_error_function) {
|
|
120
|
-
|
|
120
|
+
await response_error_function(response);
|
|
121
121
|
}
|
|
122
122
|
},
|
|
123
123
|
validation_error_function({ code, name, error }) {
|
package/nuxt.config.ts
CHANGED
|
@@ -4,12 +4,57 @@ import path from "node:path";
|
|
|
4
4
|
|
|
5
5
|
// Third party imports
|
|
6
6
|
import { defineNuxtConfig } from "nuxt/config";
|
|
7
|
+
import type { Nuxt } from "nuxt/schema";
|
|
7
8
|
|
|
8
9
|
// Local imports
|
|
9
|
-
import package_json from "./package.json";
|
|
10
10
|
|
|
11
11
|
const __dirname = import.meta.dirname;
|
|
12
12
|
|
|
13
|
+
const CLOUD_API_BASE_URL = "https://europe-west9-project-98b129be-91e9-491b-8ce.cloudfunctions.net";
|
|
14
|
+
const RELEASE_VERSION_REGEX = /^\d+\.\d+\.\d+$/u;
|
|
15
|
+
|
|
16
|
+
// Master builds ("latest" on cloud/docker, plain semver on desktop) target the
|
|
17
|
+
// Production api; next builds ("next", "x.y.z-rc.n") and local dev ("0.0.0")
|
|
18
|
+
// Target api-next.
|
|
19
|
+
function cloudApiUrl(version: string): string {
|
|
20
|
+
const is_master =
|
|
21
|
+
version === "latest" || (RELEASE_VERSION_REGEX.test(version) && version !== "0.0.0");
|
|
22
|
+
return `${CLOUD_API_BASE_URL}/${is_master ? "api" : "api-next"}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isUnset(value: unknown): boolean {
|
|
26
|
+
return typeof value !== "string" || value === "";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readAppPackage(root_dir: string): { name: string; version: string } {
|
|
30
|
+
const app_package: unknown = JSON.parse(
|
|
31
|
+
fs.readFileSync(path.join(root_dir, "package.json"), "utf8"),
|
|
32
|
+
);
|
|
33
|
+
if (typeof app_package !== "object" || app_package === null) {
|
|
34
|
+
return { name: "", version: "0.0.0" };
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
name: "name" in app_package && typeof app_package.name === "string" ? app_package.name : "",
|
|
38
|
+
version:
|
|
39
|
+
"version" in app_package && typeof app_package.version === "string"
|
|
40
|
+
? app_package.version
|
|
41
|
+
: "0.0.0",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// This file is also loaded as a layer, where ./package.json is the layer's own: the app's
|
|
46
|
+
// Package is only reachable from a module, once nuxt.options.rootDir points at the app.
|
|
47
|
+
function setAppPackageConfig(_options: unknown, nuxt: Nuxt): void {
|
|
48
|
+
const config = nuxt.options.runtimeConfig.public;
|
|
49
|
+
const { name, version } = readAppPackage(nuxt.options.rootDir);
|
|
50
|
+
if (isUnset(config.PROJECT)) {
|
|
51
|
+
config.PROJECT = name;
|
|
52
|
+
}
|
|
53
|
+
if (isUnset(config.CLOUD_API_URL)) {
|
|
54
|
+
config.CLOUD_API_URL = cloudApiUrl(version);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
13
58
|
// Oxlint's type-aware linter auto-discovers each file's nearest tsconfig.json
|
|
14
59
|
// By walking up directories, and any "extends" on that discovered file makes
|
|
15
60
|
// Its whole type-aware resolution collapse: every symbol coming through the
|
|
@@ -41,7 +86,9 @@ export default defineNuxtConfig({
|
|
|
41
86
|
COMMAND_VIEWER: "opengeodeweb-viewer",
|
|
42
87
|
NUXT_ROOT_PATH: __dirname,
|
|
43
88
|
MODE: process.env.MODE ?? "CLOUD",
|
|
44
|
-
PROJECT:
|
|
89
|
+
PROJECT: "",
|
|
90
|
+
BRANCH: process.env.NETLIFY_BRANCH ?? "next",
|
|
91
|
+
CLOUD_API_URL: process.env.CLOUD_API_URL ?? "",
|
|
45
92
|
},
|
|
46
93
|
},
|
|
47
94
|
|
|
@@ -49,6 +96,7 @@ export default defineNuxtConfig({
|
|
|
49
96
|
"vuetify-nuxt-module",
|
|
50
97
|
["@pinia/nuxt", { autoImports: ["defineStore", "storeToRefs"] }],
|
|
51
98
|
"@vueuse/nuxt",
|
|
99
|
+
setAppPackageConfig,
|
|
52
100
|
],
|
|
53
101
|
imports: {
|
|
54
102
|
scan: false,
|
|
@@ -210,21 +210,6 @@
|
|
|
210
210
|
"additionalProperties": false
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
-
},
|
|
214
|
-
"serverless": {
|
|
215
|
-
"run_cloud": {
|
|
216
|
-
"$id": "api/serverless/run_cloud",
|
|
217
|
-
"route": "/run_cloud",
|
|
218
|
-
"methods": ["POST"],
|
|
219
|
-
"type": "object",
|
|
220
|
-
"properties": {
|
|
221
|
-
"email": {
|
|
222
|
-
"type": "string"
|
|
223
|
-
}
|
|
224
|
-
},
|
|
225
|
-
"required": ["email"],
|
|
226
|
-
"additionalProperties": false
|
|
227
|
-
}
|
|
228
213
|
}
|
|
229
214
|
}
|
|
230
215
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geode/opengeodeweb-front",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.35.0-rc.2",
|
|
4
4
|
"description": "OpenSource Vue/Nuxt/Pinia/Vuetify framework for web applications",
|
|
5
5
|
"homepage": "https://github.com/Geode-solutions/OpenGeodeWeb-Front",
|
|
6
6
|
"bugs": {
|
|
@@ -62,9 +62,8 @@
|
|
|
62
62
|
"prepublishOnly": "npm run build"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@geode/opengeodeweb-back": "
|
|
66
|
-
"@geode/opengeodeweb-viewer": "
|
|
67
|
-
"@google-cloud/run": "4.1.0",
|
|
65
|
+
"@geode/opengeodeweb-back": "next",
|
|
66
|
+
"@geode/opengeodeweb-viewer": "next",
|
|
68
67
|
"@kitware/vtk.js": "33.3.0",
|
|
69
68
|
"@mdi/font": "7.4.47",
|
|
70
69
|
"@pinia/nuxt": "1.0.1",
|
|
@@ -80,8 +79,6 @@
|
|
|
80
79
|
"conf": "15.1.0",
|
|
81
80
|
"dexie": "4.2.1",
|
|
82
81
|
"get-port-please": "3.2.0",
|
|
83
|
-
"google-auth-library": "11.0.2",
|
|
84
|
-
"googleapis": "181.0.0",
|
|
85
82
|
"js-file-download": "0.4.12",
|
|
86
83
|
"jszip": "3.10.1",
|
|
87
84
|
"lodash": "4.18.1",
|
package/server/utils/cloud.ts
CHANGED
|
@@ -4,111 +4,11 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
|
|
6
6
|
// Third party imports
|
|
7
|
-
import type { AuthClient, OAuth2Client } from "google-auth-library";
|
|
8
|
-
import { google } from "googleapis";
|
|
9
|
-
import type { protos } from "@google-cloud/run";
|
|
10
7
|
|
|
11
8
|
// Local imports
|
|
12
9
|
|
|
13
|
-
// oxlint-disable-next-line eslint/id-length
|
|
14
|
-
type CreateServiceRequest = protos.google.cloud.run.v2.ICreateServiceRequest;
|
|
15
|
-
|
|
16
10
|
const LOCATIONS_DIR = "/etc/nginx/locations";
|
|
17
11
|
|
|
18
|
-
async function artifactImage(parent: string, authClient: AuthClient): Promise<string> {
|
|
19
|
-
const projectName = process.env.PROJECT;
|
|
20
|
-
const registry = google.artifactregistry({
|
|
21
|
-
version: "v1",
|
|
22
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
23
|
-
auth: authClient as OAuth2Client,
|
|
24
|
-
});
|
|
25
|
-
const branch = process.env.NETLIFY_BRANCH;
|
|
26
|
-
const [, projectId] = parent.split("/");
|
|
27
|
-
const repository = `${parent}/repositories/github/packages/`;
|
|
28
|
-
const name = `${repository}${projectName}/tags/${branch}`;
|
|
29
|
-
console.log({ name });
|
|
30
|
-
const response = await registry.projects.locations.repositories.packages.tags.get({
|
|
31
|
-
name,
|
|
32
|
-
});
|
|
33
|
-
console.log({ response });
|
|
34
|
-
const version = response.data.version ?? "";
|
|
35
|
-
const digest = version.split("/").pop();
|
|
36
|
-
const artifactRegistry = `europe-west9-docker.pkg.dev/${projectId}/github`;
|
|
37
|
-
const image = `${artifactRegistry}/${projectName}@${digest}`;
|
|
38
|
-
console.log("Found image for", projectName, image);
|
|
39
|
-
return image;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function sanitizeLabelValue(label: string): string {
|
|
43
|
-
console.log("label", label);
|
|
44
|
-
const maxLabelLength = 63;
|
|
45
|
-
return label
|
|
46
|
-
.toLowerCase()
|
|
47
|
-
.replaceAll(/[^a-z0-9_-]/gu, "_")
|
|
48
|
-
.slice(0, maxLabelLength);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function requestConfig(
|
|
52
|
-
parent: string,
|
|
53
|
-
image: string,
|
|
54
|
-
email: string,
|
|
55
|
-
projectName: string,
|
|
56
|
-
): CreateServiceRequest {
|
|
57
|
-
const resources = {
|
|
58
|
-
limits: {
|
|
59
|
-
cpu: "2000m",
|
|
60
|
-
memory: "3Gi",
|
|
61
|
-
},
|
|
62
|
-
cpuIdle: false,
|
|
63
|
-
startupCpuBoost: true,
|
|
64
|
-
};
|
|
65
|
-
const labels = {
|
|
66
|
-
user: sanitizeLabelValue(email),
|
|
67
|
-
project: sanitizeLabelValue(projectName),
|
|
68
|
-
};
|
|
69
|
-
return {
|
|
70
|
-
parent,
|
|
71
|
-
service: {
|
|
72
|
-
ingress: "INGRESS_TRAFFIC_ALL",
|
|
73
|
-
invokerIamDisabled: true,
|
|
74
|
-
labels,
|
|
75
|
-
scaling: {
|
|
76
|
-
scalingMode: "MANUAL",
|
|
77
|
-
manualInstanceCount: 1,
|
|
78
|
-
},
|
|
79
|
-
template: {
|
|
80
|
-
labels,
|
|
81
|
-
timeout: { seconds: 3600 },
|
|
82
|
-
containers: [
|
|
83
|
-
{
|
|
84
|
-
image,
|
|
85
|
-
ports: [
|
|
86
|
-
{
|
|
87
|
-
containerPort: 80,
|
|
88
|
-
},
|
|
89
|
-
],
|
|
90
|
-
resources,
|
|
91
|
-
env: [
|
|
92
|
-
{
|
|
93
|
-
name: "PARENT",
|
|
94
|
-
value: parent,
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
startupProbe: {
|
|
98
|
-
httpGet: {
|
|
99
|
-
port: 80,
|
|
100
|
-
path: "/viewer/healthcheck",
|
|
101
|
-
},
|
|
102
|
-
periodSeconds: 1,
|
|
103
|
-
failureThreshold: 30,
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
12
|
function addSupervisorProgram(
|
|
113
13
|
name: string,
|
|
114
14
|
command: string,
|
|
@@ -211,10 +111,4 @@ function removeNginxLocation(name: string): boolean {
|
|
|
211
111
|
return true;
|
|
212
112
|
}
|
|
213
113
|
|
|
214
|
-
export {
|
|
215
|
-
addNginxLocation,
|
|
216
|
-
addSupervisorProgram,
|
|
217
|
-
artifactImage,
|
|
218
|
-
removeNginxLocation,
|
|
219
|
-
requestConfig,
|
|
220
|
-
};
|
|
114
|
+
export { addNginxLocation, addSupervisorProgram, removeNginxLocation };
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
// Node imports
|
|
2
|
-
|
|
3
|
-
// Third party imports
|
|
4
|
-
import { GoogleAuth, type JWTInput } from "google-auth-library";
|
|
5
|
-
import { type H3Event, createError, defineEventHandler, readBody } from "h3";
|
|
6
|
-
import { ServicesClient } from "@google-cloud/run";
|
|
7
|
-
|
|
8
|
-
// Local imports
|
|
9
|
-
import { artifactImage, requestConfig } from "@geode/opengeodeweb-front/server/utils/cloud.ts";
|
|
10
|
-
|
|
11
|
-
interface RunCloudBody {
|
|
12
|
-
email: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function isServiceAccountCredentials(value: unknown): value is JWTInput {
|
|
16
|
-
return (
|
|
17
|
-
typeof value === "object" && value !== null && "client_email" in value && "private_key" in value
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export default defineEventHandler(async (event: H3Event) => {
|
|
22
|
-
try {
|
|
23
|
-
const { email } = await readBody<RunCloudBody>(event);
|
|
24
|
-
console.log("[RUN CLOUD] Received request to create backend for email:", email);
|
|
25
|
-
|
|
26
|
-
const { GOOGLE_CLOUD_KEY, GOOGLE_CLOUD_PROJECT, PROJECT } = process.env;
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
GOOGLE_CLOUD_KEY === undefined ||
|
|
30
|
-
GOOGLE_CLOUD_KEY === "" ||
|
|
31
|
-
GOOGLE_CLOUD_PROJECT === undefined ||
|
|
32
|
-
GOOGLE_CLOUD_PROJECT === "" ||
|
|
33
|
-
PROJECT === undefined ||
|
|
34
|
-
PROJECT === ""
|
|
35
|
-
) {
|
|
36
|
-
throw createError({
|
|
37
|
-
statusCode: 500,
|
|
38
|
-
statusMessage:
|
|
39
|
-
"Missing required environment variables: GOOGLE_CLOUD_KEY, GOOGLE_CLOUD_PROJECT, or PROJECT",
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const projectId: string = GOOGLE_CLOUD_PROJECT;
|
|
44
|
-
const projectName: string = PROJECT;
|
|
45
|
-
|
|
46
|
-
const parsedKey: unknown = JSON.parse(GOOGLE_CLOUD_KEY);
|
|
47
|
-
if (!isServiceAccountCredentials(parsedKey)) {
|
|
48
|
-
throw createError({
|
|
49
|
-
statusCode: 500,
|
|
50
|
-
statusMessage: "GOOGLE_CLOUD_KEY is not a valid service account credentials JSON",
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
const credentials: JWTInput = parsedKey;
|
|
54
|
-
|
|
55
|
-
const location = "europe-west9";
|
|
56
|
-
const parent = `projects/${projectId}/locations/${location}`;
|
|
57
|
-
console.log({ parent });
|
|
58
|
-
|
|
59
|
-
const auth = new GoogleAuth({
|
|
60
|
-
// oxlint-disable-next-line typescript/no-deprecated
|
|
61
|
-
credentials,
|
|
62
|
-
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
|
|
63
|
-
});
|
|
64
|
-
const authClient = await auth.getClient();
|
|
65
|
-
const image = await artifactImage(parent, authClient);
|
|
66
|
-
const request = requestConfig(parent, image, email, projectName);
|
|
67
|
-
console.log({ request });
|
|
68
|
-
|
|
69
|
-
const runClient = new ServicesClient({ authClient });
|
|
70
|
-
const [operation] = await runClient.createService(request);
|
|
71
|
-
const [response] = await operation.promise();
|
|
72
|
-
|
|
73
|
-
if (response.uri === null || response.uri === undefined || response.uri === "") {
|
|
74
|
-
throw createError({
|
|
75
|
-
statusCode: 500,
|
|
76
|
-
statusMessage: "Cloud Run service created but no URI was returned",
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
console.log("Service URL created:", response.uri);
|
|
81
|
-
return {
|
|
82
|
-
statusCode: 200,
|
|
83
|
-
url: response.uri.replace(/^https?:\/\//iu, ""),
|
|
84
|
-
};
|
|
85
|
-
} catch (error) {
|
|
86
|
-
console.log(error);
|
|
87
|
-
throw createError({
|
|
88
|
-
statusCode: 500,
|
|
89
|
-
statusMessage: error instanceof Error ? error.message : String(error),
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
});
|