c8y-nitro 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +292 -0
- package/dist/cli/commands/bootstrap.mjs +64 -0
- package/dist/cli/commands/roles.mjs +41 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +18 -0
- package/dist/cli/utils/c8y-api.mjs +172 -0
- package/dist/cli/utils/config.mjs +57 -0
- package/dist/cli/utils/env-file.mjs +61 -0
- package/dist/index.d.mts +12 -0
- package/dist/index.mjs +51 -0
- package/dist/module/apiClient.mjs +207 -0
- package/dist/module/autoBootstrap.mjs +54 -0
- package/dist/module/c8yzip.mjs +66 -0
- package/dist/module/constants.mjs +6 -0
- package/dist/module/docker.mjs +101 -0
- package/dist/module/manifest.mjs +72 -0
- package/dist/module/probeCheck.mjs +30 -0
- package/dist/module/register.mjs +58 -0
- package/dist/module/runtime/handlers/liveness-readiness.ts +7 -0
- package/dist/module/runtime/middlewares/dev-user.ts +25 -0
- package/dist/module/runtime/plugins/c8y-variables.ts +24 -0
- package/dist/module/runtime.mjs +31 -0
- package/dist/package.mjs +7 -0
- package/dist/types/apiClient.d.mts +16 -0
- package/dist/types/manifest.d.mts +323 -0
- package/dist/types/roles.d.mts +4 -0
- package/dist/types/zip.d.mts +22 -0
- package/dist/types.d.mts +13 -0
- package/dist/types.mjs +1 -0
- package/dist/utils/client.d.mts +50 -0
- package/dist/utils/client.mjs +91 -0
- package/dist/utils/credentials.d.mts +66 -0
- package/dist/utils/credentials.mjs +117 -0
- package/dist/utils/internal/common.mjs +26 -0
- package/dist/utils/middleware.d.mts +89 -0
- package/dist/utils/middleware.mjs +62 -0
- package/dist/utils/resources.d.mts +28 -0
- package/dist/utils/resources.mjs +50 -0
- package/dist/utils.d.mts +5 -0
- package/dist/utils.mjs +6 -0
- package/package.json +87 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { GENERATED_LIVENESS_ROUTE, GENERATED_READINESS_ROUTE } from "./constants.mjs";
|
|
2
|
+
import { join } from "pathe";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
//#region src/module/register.ts
|
|
6
|
+
/**
|
|
7
|
+
* Links runtime middleware, handlers, and plugins to the nitro instance.
|
|
8
|
+
* Works by having the handlers in a relative path to this file.
|
|
9
|
+
* Needs to be the same when built.
|
|
10
|
+
* @param nitro - Nitro instance
|
|
11
|
+
* @param options - C8yNitroModuleOptions
|
|
12
|
+
*/
|
|
13
|
+
function registerRuntime(nitro, options = {}) {
|
|
14
|
+
const thisFilePath = fileURLToPath(new URL(".", import.meta.url));
|
|
15
|
+
/**
|
|
16
|
+
* Plugins
|
|
17
|
+
*/
|
|
18
|
+
const plugins = [];
|
|
19
|
+
const c8yVariablesPluginPath = join(thisFilePath, "./runtime/plugins/c8y-variables");
|
|
20
|
+
plugins.push(c8yVariablesPluginPath);
|
|
21
|
+
nitro.options.plugins.push(...plugins);
|
|
22
|
+
/**
|
|
23
|
+
* Middlewares (global)
|
|
24
|
+
*/
|
|
25
|
+
const middlewares = [];
|
|
26
|
+
const devUserMiddlewarePath = join(thisFilePath, "./runtime/middlewares/dev-user");
|
|
27
|
+
middlewares.push(devUserMiddlewarePath);
|
|
28
|
+
nitro.options.handlers.push(...middlewares.map((handler) => ({
|
|
29
|
+
route: "/**",
|
|
30
|
+
handler,
|
|
31
|
+
middleware: true
|
|
32
|
+
})));
|
|
33
|
+
/**
|
|
34
|
+
* Handlers
|
|
35
|
+
*/
|
|
36
|
+
const handlers = [];
|
|
37
|
+
const probeHandlerPath = join(thisFilePath, "./runtime/handlers/liveness-readiness");
|
|
38
|
+
if (!options.manifest?.livenessProbe?.httpGet) {
|
|
39
|
+
handlers.push({
|
|
40
|
+
route: GENERATED_LIVENESS_ROUTE,
|
|
41
|
+
handler: probeHandlerPath,
|
|
42
|
+
method: "GET"
|
|
43
|
+
});
|
|
44
|
+
nitro.logger.debug(`Generated liveness probe at ${GENERATED_LIVENESS_ROUTE}`);
|
|
45
|
+
} else nitro.logger.debug("Liveness probe httpGet defined by user; skipping generation");
|
|
46
|
+
if (!options.manifest?.readinessProbe?.httpGet) {
|
|
47
|
+
handlers.push({
|
|
48
|
+
route: GENERATED_READINESS_ROUTE,
|
|
49
|
+
handler: probeHandlerPath,
|
|
50
|
+
method: "GET"
|
|
51
|
+
});
|
|
52
|
+
nitro.logger.debug(`Generated readiness probe at ${GENERATED_READINESS_ROUTE}`);
|
|
53
|
+
} else nitro.logger.debug("Readiness probe httpGet defined by user; skipping generation");
|
|
54
|
+
nitro.options.handlers.push(...handlers);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { registerRuntime };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { defineHandler } from 'nitro/h3'
|
|
2
|
+
import process from 'node:process'
|
|
3
|
+
import { Buffer } from 'node:buffer'
|
|
4
|
+
import consola from 'consola'
|
|
5
|
+
|
|
6
|
+
// Middleware to inject a development c8y user into the request during development mode
|
|
7
|
+
export default defineHandler((event) => {
|
|
8
|
+
if (import.meta.dev) {
|
|
9
|
+
// in development mode, we check for a development user to inject into the request
|
|
10
|
+
// this is necessary to correctly use auth middlewares during development
|
|
11
|
+
const requiredDevEnvVars = ['C8Y_DEVELOPMENT_TENANT', 'C8Y_DEVELOPMENT_USER', 'C8Y_DEVELOPMENT_PASSWORD']
|
|
12
|
+
const missingDevVars = requiredDevEnvVars.filter((varName) => !process.env[varName])
|
|
13
|
+
if (missingDevVars.length > 0) {
|
|
14
|
+
consola.warn(`Missing development environment variables: ${missingDevVars.join(', ')}. Dev user injection will be skipped. Routes requiring authentication or roles will fail.`)
|
|
15
|
+
} else {
|
|
16
|
+
// build basic auth header in form "tenant/user:password"
|
|
17
|
+
const authString = `${process.env.C8Y_DEVELOPMENT_TENANT}/${process.env.C8Y_DEVELOPMENT_USER}:${process.env.C8Y_DEVELOPMENT_PASSWORD}`
|
|
18
|
+
const encodedAuth = Buffer.from(authString).toString('base64')
|
|
19
|
+
const authHeader = `Basic ${encodedAuth}`
|
|
20
|
+
|
|
21
|
+
// inject auth header into request
|
|
22
|
+
event.req.headers.set('authorization', authHeader)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { definePlugin } from 'nitro'
|
|
2
|
+
import process from 'node:process'
|
|
3
|
+
|
|
4
|
+
export default definePlugin(() => {
|
|
5
|
+
if (import.meta.dev) {
|
|
6
|
+
const env = process.env
|
|
7
|
+
|
|
8
|
+
const requiredVars = ['C8Y_BASEURL', 'C8Y_BOOTSTRAP_TENANT', 'C8Y_BOOTSTRAP_USER', 'C8Y_BOOTSTRAP_PASSWORD']
|
|
9
|
+
const missingVars = requiredVars.filter((varName) => !env[varName])
|
|
10
|
+
|
|
11
|
+
if (missingVars.length > 0) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`Missing required environment variables for development: ${missingVars.join(', ')}\n\n`
|
|
14
|
+
+ `To set up your development environment, run:\n`
|
|
15
|
+
+ ` npx c8y-nitro bootstrap\n\n`
|
|
16
|
+
+ `This command will:\n`
|
|
17
|
+
+ ` 1. Require your development tenant credentials (C8Y_BASEURL C8Y_DEVELOPMENT_TENANT, C8Y_DEVELOPMENT_USER, C8Y_DEVELOPMENT_PASSWORD)\n`
|
|
18
|
+
+ ` 2. Register your microservice on the tenant\n`
|
|
19
|
+
+ ` 3. Generate the necessary bootstrap credentials in a .env file\n\n`
|
|
20
|
+
+ `Make sure you have your development tenant credentials configured first.`,
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { join } from "pathe";
|
|
2
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
3
|
+
|
|
4
|
+
//#region src/module/runtime.ts
|
|
5
|
+
function setupRuntime(nitro, manifestOptions = {}) {
|
|
6
|
+
nitro.logger.debug("Setting up C8Y nitro runtime");
|
|
7
|
+
const roles = manifestOptions.roles ?? [];
|
|
8
|
+
const completeTypesDir = join(nitro.options.rootDir, nitro.options.typescript.generatedTypesDir ?? "node_modules/.nitro/types");
|
|
9
|
+
const rolesTypeFile = join(completeTypesDir, "c8y-nitro-roles.d.ts");
|
|
10
|
+
const rolesTypeContent = `// generated by c8y-nitro
|
|
11
|
+
declare module 'c8y-nitro/types' {
|
|
12
|
+
interface C8YRoles {
|
|
13
|
+
${roles.map((role) => ` '${role}': '${role}';`).join("\n")}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
declare module 'c8y-nitro/runtime' {
|
|
17
|
+
import type { C8YRoles } from 'c8y-nitro/types';
|
|
18
|
+
export const c8yRoles: C8YRoles;
|
|
19
|
+
}`;
|
|
20
|
+
nitro.options.virtual["c8y-nitro/runtime"] = `
|
|
21
|
+
export const c8yRoles = {
|
|
22
|
+
${roles.map((role) => ` '${role}': '${role}',`).join("\n")}
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
mkdirSync(completeTypesDir, { recursive: true });
|
|
26
|
+
writeFileSync(rolesTypeFile, rolesTypeContent, { encoding: "utf-8" });
|
|
27
|
+
nitro.logger.debug(`Written C8Y roles types to ${rolesTypeFile}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
export { setupRuntime };
|
package/dist/package.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/types/apiClient.d.ts
|
|
2
|
+
interface C8YAPIClientOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Relative directory from nitro configuration file to generate the API client into.
|
|
5
|
+
*/
|
|
6
|
+
dir: string;
|
|
7
|
+
/**
|
|
8
|
+
* Service context path for microservice endpoints.\
|
|
9
|
+
* Defaults to contextPath from manifest (package.json name, with scope stripped).\
|
|
10
|
+
* Override this if deploying with a different context path.
|
|
11
|
+
* @example "my-microservice" results in "https://<tenant>.com/service/my-microservice/..."
|
|
12
|
+
*/
|
|
13
|
+
contextPath?: string;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { C8YAPIClientOptions };
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
//#region src/types/manifest.d.ts
|
|
2
|
+
type C8YManifestOptions = Partial<Omit<C8YManifest, 'name' | 'version' | 'apiVersion' | 'key'>>;
|
|
3
|
+
interface C8YManifest {
|
|
4
|
+
/**
|
|
5
|
+
* API version (e.g., "v2", "2"). Version 2 required for enhanced container security.
|
|
6
|
+
* Automatically set by the module to "v2".
|
|
7
|
+
*/
|
|
8
|
+
apiVersion: string;
|
|
9
|
+
/**
|
|
10
|
+
* Unique microservice key
|
|
11
|
+
*/
|
|
12
|
+
key: string;
|
|
13
|
+
/**
|
|
14
|
+
* Type of Cumulocity application. For microservices, always "MICROSERVICE".
|
|
15
|
+
* Technically can be "HOSTED" or "EXTERNAL" as well, but those are not supported here.
|
|
16
|
+
*/
|
|
17
|
+
type: 'MICROSERVICE';
|
|
18
|
+
/**
|
|
19
|
+
* Microservice name (lowercase a-z, digits, hyphens, max 23 chars).
|
|
20
|
+
* Automatically populated from package.json name.
|
|
21
|
+
*/
|
|
22
|
+
name: string;
|
|
23
|
+
/**
|
|
24
|
+
* SemVer version (no "+" allowed). Use "-SNAPSHOT" suffix for dev builds.
|
|
25
|
+
* Automatically populated from package.json version.
|
|
26
|
+
*/
|
|
27
|
+
version: string;
|
|
28
|
+
/**
|
|
29
|
+
* URL path prefix for your microservice endpoints.
|
|
30
|
+
* Letters, digits, hyphens, dots, underscores, tildes allowed.
|
|
31
|
+
* Defaults to package.json name. "my-service" results in endpoints like "/service/my-service/..."
|
|
32
|
+
* @example "my-service"
|
|
33
|
+
*/
|
|
34
|
+
contextPath?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Company/organization information.
|
|
37
|
+
*/
|
|
38
|
+
provider: Provider;
|
|
39
|
+
/**
|
|
40
|
+
* Billing strategy:
|
|
41
|
+
* - RESOURCES: Tenants pay per usage
|
|
42
|
+
* - SUBSCRIPTION: Owner pays, tenants pay flat subscription
|
|
43
|
+
* @default "RESOURCES"
|
|
44
|
+
*/
|
|
45
|
+
billingMode?: 'RESOURCES' | 'SUBSCRIPTION';
|
|
46
|
+
/**
|
|
47
|
+
* Container sharing strategy:
|
|
48
|
+
* - MULTI_TENANT: One container serves all tenants (cost efficient)
|
|
49
|
+
* - PER_TENANT: Separate container per tenant (data isolation)
|
|
50
|
+
* @default "MULTI_TENANT"
|
|
51
|
+
*/
|
|
52
|
+
isolation?: 'MULTI_TENANT' | 'PER_TENANT';
|
|
53
|
+
/**
|
|
54
|
+
* Auto-scaling based on CPU:
|
|
55
|
+
* - NONE: Fixed instance count
|
|
56
|
+
* - AUTO: Scales up/down automatically
|
|
57
|
+
* @default "NONE"
|
|
58
|
+
*/
|
|
59
|
+
scale?: 'AUTO' | 'NONE';
|
|
60
|
+
/**
|
|
61
|
+
* Number of container instances (1-5).
|
|
62
|
+
* For AUTO scale: minimum count. Use 2+ for production.
|
|
63
|
+
* @default 1
|
|
64
|
+
*/
|
|
65
|
+
replicas?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Maximum resources (hard limits).
|
|
68
|
+
* Container killed if exceeded.
|
|
69
|
+
*/
|
|
70
|
+
resources?: Resources;
|
|
71
|
+
/**
|
|
72
|
+
* Minimum resources (guaranteed reservation).
|
|
73
|
+
* Platform ensures availability.
|
|
74
|
+
*/
|
|
75
|
+
requestedResources?: RequestedResources;
|
|
76
|
+
/**
|
|
77
|
+
* Runtime configuration exposed to tenants.
|
|
78
|
+
* Allows customization without redeployment.
|
|
79
|
+
*/
|
|
80
|
+
settings?: Option[];
|
|
81
|
+
/**
|
|
82
|
+
* Category for grouping settings (letters, digits, dots).
|
|
83
|
+
* @default contextPath
|
|
84
|
+
*/
|
|
85
|
+
settingsCategory?: string;
|
|
86
|
+
/**
|
|
87
|
+
* Permissions granted to microservice service user.
|
|
88
|
+
* Needed to access Cumulocity APIs (inventory, alarms, etc.).
|
|
89
|
+
* @example ["ROLE_ALARM_READ", "ROLE_INVENTORY_ADMIN"]
|
|
90
|
+
*/
|
|
91
|
+
requiredRoles?: string[];
|
|
92
|
+
/**
|
|
93
|
+
* New permissions provided by this microservice.
|
|
94
|
+
* Users can be assigned these to access your endpoints.
|
|
95
|
+
* @example ["ROLE_MY_SERVICE_READ", "ROLE_MY_SERVICE_ADMIN"]
|
|
96
|
+
*/
|
|
97
|
+
roles?: string[];
|
|
98
|
+
/**
|
|
99
|
+
* Health check for crashed/frozen containers.
|
|
100
|
+
* Fails trigger restart. Recommended for production.
|
|
101
|
+
*/
|
|
102
|
+
livenessProbe?: Probe;
|
|
103
|
+
/**
|
|
104
|
+
* Health check for readiness to serve traffic.
|
|
105
|
+
* Fails prevent routing. Recommended for production.
|
|
106
|
+
*/
|
|
107
|
+
readinessProbe?: Probe;
|
|
108
|
+
/**
|
|
109
|
+
* Platform extensions (e.g., UI plugins).
|
|
110
|
+
*/
|
|
111
|
+
extensions?: Extension[];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Company/organization information.
|
|
115
|
+
*/
|
|
116
|
+
interface Provider {
|
|
117
|
+
/**
|
|
118
|
+
* Company name ("c8y" for Cumulocity).
|
|
119
|
+
* Defaults to package.json author ?? author.name
|
|
120
|
+
*/
|
|
121
|
+
name: string;
|
|
122
|
+
/**
|
|
123
|
+
* Company website.
|
|
124
|
+
* Defaults to package.json author.url ?? homepage.
|
|
125
|
+
*/
|
|
126
|
+
domain?: string;
|
|
127
|
+
/**
|
|
128
|
+
* Support link or email.
|
|
129
|
+
* Defaults to package.json bugs.url ?? bugs.email ?? author.email.
|
|
130
|
+
*/
|
|
131
|
+
support?: string;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Maximum resource limits (hard caps).
|
|
135
|
+
*/
|
|
136
|
+
interface Resources {
|
|
137
|
+
/**
|
|
138
|
+
* CPU cores ("1", "0.5") or millicores ("500m").
|
|
139
|
+
* 1 core = 1000m. Min: "0.1" or "100m".
|
|
140
|
+
* @default "0.5"
|
|
141
|
+
* @example "1" | "500m"
|
|
142
|
+
*/
|
|
143
|
+
cpu?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Max memory before kill. Units: E, P, T, G, M, K, Ei, Pi, Ti, Gi, Mi, Ki.
|
|
146
|
+
* Min: "10M".
|
|
147
|
+
* @default "512M"
|
|
148
|
+
* @example "1G" | "512M"
|
|
149
|
+
*/
|
|
150
|
+
memory?: string;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Minimum resources (guaranteed reservation).
|
|
154
|
+
*/
|
|
155
|
+
interface RequestedResources {
|
|
156
|
+
/**
|
|
157
|
+
* Min CPU guaranteed (millicores typical).
|
|
158
|
+
* @default "250m".
|
|
159
|
+
* @example "100m" | "250m"
|
|
160
|
+
*/
|
|
161
|
+
cpu?: string;
|
|
162
|
+
/**
|
|
163
|
+
* Min memory guaranteed. Units: E, P, T, G, M, K, Ei, Pi, Ti, Gi, Mi, Ki.
|
|
164
|
+
* @default "256M".
|
|
165
|
+
* @example "128Mi" | "256M"
|
|
166
|
+
*/
|
|
167
|
+
memory?: string;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Runtime configuration option for tenants.
|
|
171
|
+
*/
|
|
172
|
+
interface Option {
|
|
173
|
+
/**
|
|
174
|
+
* Unique setting identifier.
|
|
175
|
+
* @example "tracker-id"
|
|
176
|
+
*/
|
|
177
|
+
key: string;
|
|
178
|
+
/**
|
|
179
|
+
* Initial value if not overridden.
|
|
180
|
+
* @example "1234"
|
|
181
|
+
*/
|
|
182
|
+
defaultValue?: string;
|
|
183
|
+
/**
|
|
184
|
+
* Allow tenants to modify at runtime.
|
|
185
|
+
* @default false
|
|
186
|
+
*/
|
|
187
|
+
editable?: boolean;
|
|
188
|
+
/**
|
|
189
|
+
* Reset editable value on microservice update.
|
|
190
|
+
* @default true
|
|
191
|
+
*/
|
|
192
|
+
overwriteOnUpdate?: boolean;
|
|
193
|
+
/**
|
|
194
|
+
* Inherit value from owner tenant.
|
|
195
|
+
* @default true
|
|
196
|
+
*/
|
|
197
|
+
inheritFromOwner?: boolean;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Health check probe configuration.
|
|
201
|
+
*/
|
|
202
|
+
interface Probe {
|
|
203
|
+
/**
|
|
204
|
+
* Execute command in container. Success = exit code 0.
|
|
205
|
+
*/
|
|
206
|
+
exec?: ExecAction;
|
|
207
|
+
/**
|
|
208
|
+
* TCP connection attempt. Success = connection established.
|
|
209
|
+
*/
|
|
210
|
+
tcpSocket?: TCPSocketAction;
|
|
211
|
+
/**
|
|
212
|
+
* HTTP request. Success = 200-399 status code.
|
|
213
|
+
*/
|
|
214
|
+
httpGet?: HTTPGetAction;
|
|
215
|
+
/**
|
|
216
|
+
* Seconds before first probe (startup grace period).
|
|
217
|
+
* @default 0
|
|
218
|
+
* @example 60
|
|
219
|
+
*/
|
|
220
|
+
initialDelaySeconds?: number;
|
|
221
|
+
/**
|
|
222
|
+
* Seconds between probes.
|
|
223
|
+
* @default 10
|
|
224
|
+
*/
|
|
225
|
+
periodSeconds?: number;
|
|
226
|
+
/**
|
|
227
|
+
* Consecutive successes to consider healthy.
|
|
228
|
+
* @default 1
|
|
229
|
+
*/
|
|
230
|
+
successThreshold?: number;
|
|
231
|
+
/**
|
|
232
|
+
* Seconds before probe times out.
|
|
233
|
+
* @default 1
|
|
234
|
+
*/
|
|
235
|
+
timeoutSeconds?: number;
|
|
236
|
+
/**
|
|
237
|
+
* Consecutive failures before action (restart/stop routing).
|
|
238
|
+
* @default 3
|
|
239
|
+
*/
|
|
240
|
+
failureThreshold?: number;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Execute command probe.
|
|
244
|
+
*/
|
|
245
|
+
interface ExecAction {
|
|
246
|
+
/**
|
|
247
|
+
* Command and arguments to execute.
|
|
248
|
+
* @example ["cat", "/tmp/healthy"]
|
|
249
|
+
*/
|
|
250
|
+
command: string[];
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* TCP socket probe.
|
|
254
|
+
*/
|
|
255
|
+
interface TCPSocketAction {
|
|
256
|
+
/**
|
|
257
|
+
* Hostname or IP to connect to.
|
|
258
|
+
*/
|
|
259
|
+
host: string;
|
|
260
|
+
/**
|
|
261
|
+
* Port number to connect to.
|
|
262
|
+
* @default 80
|
|
263
|
+
*/
|
|
264
|
+
port: number;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* HTTP GET probe.
|
|
268
|
+
*/
|
|
269
|
+
interface HTTPGetAction {
|
|
270
|
+
/**
|
|
271
|
+
* Hostname to connect to.
|
|
272
|
+
*/
|
|
273
|
+
host?: string;
|
|
274
|
+
/**
|
|
275
|
+
* URL path to request.
|
|
276
|
+
* @example "/health"
|
|
277
|
+
*/
|
|
278
|
+
path: string;
|
|
279
|
+
/**
|
|
280
|
+
* Port to connect to.
|
|
281
|
+
* @default 80
|
|
282
|
+
*/
|
|
283
|
+
port?: number;
|
|
284
|
+
/**
|
|
285
|
+
* Protocol to use.
|
|
286
|
+
* @default "HTTP"
|
|
287
|
+
*/
|
|
288
|
+
scheme?: 'HTTP' | 'HTTPS';
|
|
289
|
+
/**
|
|
290
|
+
* Custom HTTP headers.
|
|
291
|
+
*/
|
|
292
|
+
headers?: HttpHeader[];
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* HTTP header for probes.
|
|
296
|
+
*/
|
|
297
|
+
interface HttpHeader {
|
|
298
|
+
/**
|
|
299
|
+
* Header name.
|
|
300
|
+
* @example "Authorization"
|
|
301
|
+
*/
|
|
302
|
+
name: string;
|
|
303
|
+
/**
|
|
304
|
+
* Header value.
|
|
305
|
+
* @example "Bearer token"
|
|
306
|
+
*/
|
|
307
|
+
value: string;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Platform extension configuration.
|
|
311
|
+
*/
|
|
312
|
+
interface Extension {
|
|
313
|
+
/**
|
|
314
|
+
* Extension type identifier.
|
|
315
|
+
*/
|
|
316
|
+
type: string;
|
|
317
|
+
/**
|
|
318
|
+
* Extension-specific config.
|
|
319
|
+
*/
|
|
320
|
+
[key: string]: unknown;
|
|
321
|
+
}
|
|
322
|
+
//#endregion
|
|
323
|
+
export { C8YManifestOptions };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { C8YManifestOptions } from "./manifest.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/types/zip.d.ts
|
|
4
|
+
interface C8YZipOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Name of the generated zip file
|
|
7
|
+
* @default '${packageName}-${version}.zip'
|
|
8
|
+
*/
|
|
9
|
+
name?: string | ((packageName: string, version: string) => string);
|
|
10
|
+
/**
|
|
11
|
+
* Output directory for the generated zip file.\
|
|
12
|
+
* Relative to the config file.
|
|
13
|
+
* @default './'
|
|
14
|
+
*/
|
|
15
|
+
outputDir?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Configuration of the "cumulocity.json" manifest file used for the zip.
|
|
18
|
+
*/
|
|
19
|
+
manifest?: C8YManifestOptions;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { C8YZipOptions };
|
package/dist/types.d.mts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { C8YAPIClientOptions } from "./types/apiClient.mjs";
|
|
2
|
+
import { C8YManifestOptions } from "./types/manifest.mjs";
|
|
3
|
+
import { C8YZipOptions } from "./types/zip.mjs";
|
|
4
|
+
import { C8YRoles } from "./types/roles.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/types/index.d.ts
|
|
7
|
+
interface C8yNitroModuleOptions {
|
|
8
|
+
manifest?: C8YManifestOptions;
|
|
9
|
+
apiClient?: C8YAPIClientOptions;
|
|
10
|
+
zip?: C8YZipOptions;
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { C8YAPIClientOptions, type C8YManifestOptions, C8YRoles, C8YZipOptions, C8yNitroModuleOptions };
|
package/dist/types.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Client } from "@c8y/client";
|
|
2
|
+
|
|
3
|
+
//#region src/utils/client.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Creates a Cumulocity client authenticated with the current user's credentials.\
|
|
6
|
+
* Extracts credentials from the Authorization header of the current request.\
|
|
7
|
+
* Must be called within a request handler context.\
|
|
8
|
+
* @returns A configured Cumulocity Client instance
|
|
9
|
+
* @example
|
|
10
|
+
* // In a request handler:
|
|
11
|
+
* const client = useUserClient()
|
|
12
|
+
* const { data } = await client.inventory.list()
|
|
13
|
+
*/
|
|
14
|
+
declare function useUserClient(): Client;
|
|
15
|
+
/**
|
|
16
|
+
* Creates a Cumulocity client for the tenant of the current user.\
|
|
17
|
+
* Uses the tenant's service user credentials rather than the user's own credentials.\
|
|
18
|
+
* Must be called within a request handler context.\
|
|
19
|
+
* @returns A configured Cumulocity Client instance for the user's tenant
|
|
20
|
+
* @example
|
|
21
|
+
* // In a request handler:
|
|
22
|
+
* const tenantClient = await useUserTenantClient()
|
|
23
|
+
* const { data } = await tenantClient.inventory.list()
|
|
24
|
+
*/
|
|
25
|
+
declare function useUserTenantClient(): Promise<Client>;
|
|
26
|
+
/**
|
|
27
|
+
* Creates Cumulocity clients for all tenants subscribed to this microservice.\
|
|
28
|
+
* Each client is authenticated with that tenant's service user credentials.\
|
|
29
|
+
* @returns Object mapping tenant IDs to their respective Client instances
|
|
30
|
+
* @example
|
|
31
|
+
* // Get clients for all subscribed tenants:
|
|
32
|
+
* const clients = await useSubscribedTenantClients()
|
|
33
|
+
* for (const [tenant, client] of Object.entries(clients)) {
|
|
34
|
+
* const { data } = await client.inventory.list()
|
|
35
|
+
* console.log(`Tenant ${tenant} has ${data.length} inventory items`)
|
|
36
|
+
* }
|
|
37
|
+
*/
|
|
38
|
+
declare function useSubscribedTenantClients(): Promise<Record<string, Client>>;
|
|
39
|
+
/**
|
|
40
|
+
* Creates a Cumulocity client for the tenant where this microservice is deployed.\
|
|
41
|
+
* Uses the bootstrap tenant ID from runtime config to identify the deployed tenant.\
|
|
42
|
+
* @returns A configured Cumulocity Client instance for the deployed tenant
|
|
43
|
+
* @example
|
|
44
|
+
* // Get client for the tenant hosting this microservice:
|
|
45
|
+
* const client = await useDeployedTenantClient()
|
|
46
|
+
* const { data } = await client.application.list()
|
|
47
|
+
*/
|
|
48
|
+
declare function useDeployedTenantClient(): Promise<Client>;
|
|
49
|
+
//#endregion
|
|
50
|
+
export { useDeployedTenantClient, useSubscribedTenantClients, useUserClient, useUserTenantClient };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { convertRequestHeadersToC8yFormat } from "./internal/common.mjs";
|
|
2
|
+
import { useSubscribedTenantCredentials } from "./credentials.mjs";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { BasicAuth, Client, MicroserviceClientRequestAuth } from "@c8y/client";
|
|
5
|
+
import { useRequest } from "nitro/context";
|
|
6
|
+
import { HTTPError } from "nitro/h3";
|
|
7
|
+
|
|
8
|
+
//#region src/utils/client.ts
|
|
9
|
+
/**
|
|
10
|
+
* Creates a Cumulocity client authenticated with the current user's credentials.\
|
|
11
|
+
* Extracts credentials from the Authorization header of the current request.\
|
|
12
|
+
* Must be called within a request handler context.\
|
|
13
|
+
* @returns A configured Cumulocity Client instance
|
|
14
|
+
* @example
|
|
15
|
+
* // In a request handler:
|
|
16
|
+
* const client = useUserClient()
|
|
17
|
+
* const { data } = await client.inventory.list()
|
|
18
|
+
*/
|
|
19
|
+
function useUserClient() {
|
|
20
|
+
const request = useRequest();
|
|
21
|
+
if (request.context?.["c8y_user_client"]) return request.context["c8y_user_client"];
|
|
22
|
+
const client = new Client(new MicroserviceClientRequestAuth(convertRequestHeadersToC8yFormat(request)), process.env.C8Y_BASEURL);
|
|
23
|
+
request.context ??= {};
|
|
24
|
+
request.context["c8y_user_client"] = client;
|
|
25
|
+
return client;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Creates a Cumulocity client for the tenant of the current user.\
|
|
29
|
+
* Uses the tenant's service user credentials rather than the user's own credentials.\
|
|
30
|
+
* Must be called within a request handler context.\
|
|
31
|
+
* @returns A configured Cumulocity Client instance for the user's tenant
|
|
32
|
+
* @example
|
|
33
|
+
* // In a request handler:
|
|
34
|
+
* const tenantClient = await useUserTenantClient()
|
|
35
|
+
* const { data } = await tenantClient.inventory.list()
|
|
36
|
+
*/
|
|
37
|
+
async function useUserTenantClient() {
|
|
38
|
+
const request = useRequest();
|
|
39
|
+
if (request.context?.["c8y_user_tenant_client"]) return request.context["c8y_user_tenant_client"];
|
|
40
|
+
const tenantId = useUserClient().core.tenant;
|
|
41
|
+
const creds = await useSubscribedTenantCredentials();
|
|
42
|
+
if (!creds[tenantId]) throw new HTTPError({
|
|
43
|
+
message: `No subscribed tenant credentials found for user tenant '${tenantId}'`,
|
|
44
|
+
status: 500,
|
|
45
|
+
statusText: "Internal Server Error"
|
|
46
|
+
});
|
|
47
|
+
const tenantClient = new Client(new BasicAuth(creds[tenantId]), process.env.C8Y_BASEURL);
|
|
48
|
+
request.context ??= {};
|
|
49
|
+
request.context["c8y_user_tenant_client"] = tenantClient;
|
|
50
|
+
return tenantClient;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Creates Cumulocity clients for all tenants subscribed to this microservice.\
|
|
54
|
+
* Each client is authenticated with that tenant's service user credentials.\
|
|
55
|
+
* @returns Object mapping tenant IDs to their respective Client instances
|
|
56
|
+
* @example
|
|
57
|
+
* // Get clients for all subscribed tenants:
|
|
58
|
+
* const clients = await useSubscribedTenantClients()
|
|
59
|
+
* for (const [tenant, client] of Object.entries(clients)) {
|
|
60
|
+
* const { data } = await client.inventory.list()
|
|
61
|
+
* console.log(`Tenant ${tenant} has ${data.length} inventory items`)
|
|
62
|
+
* }
|
|
63
|
+
*/
|
|
64
|
+
async function useSubscribedTenantClients() {
|
|
65
|
+
const creds = await useSubscribedTenantCredentials();
|
|
66
|
+
const clients = {};
|
|
67
|
+
for (const [tenant, tenantCreds] of Object.entries(creds)) clients[tenant] = new Client(new BasicAuth(tenantCreds), process.env.C8Y_BASEURL);
|
|
68
|
+
return clients;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Creates a Cumulocity client for the tenant where this microservice is deployed.\
|
|
72
|
+
* Uses the bootstrap tenant ID from runtime config to identify the deployed tenant.\
|
|
73
|
+
* @returns A configured Cumulocity Client instance for the deployed tenant
|
|
74
|
+
* @example
|
|
75
|
+
* // Get client for the tenant hosting this microservice:
|
|
76
|
+
* const client = await useDeployedTenantClient()
|
|
77
|
+
* const { data } = await client.application.list()
|
|
78
|
+
*/
|
|
79
|
+
async function useDeployedTenantClient() {
|
|
80
|
+
const creds = await useSubscribedTenantCredentials();
|
|
81
|
+
const tenant = process.env.C8Y_BOOTSTRAP_TENANT;
|
|
82
|
+
if (!creds[tenant]) throw new HTTPError({
|
|
83
|
+
message: `No subscribed tenant credentials found for tenant '${tenant}'`,
|
|
84
|
+
status: 500,
|
|
85
|
+
statusText: "Internal Server Error"
|
|
86
|
+
});
|
|
87
|
+
return new Client(new BasicAuth(creds[tenant]), process.env.C8Y_BASEURL);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
export { useDeployedTenantClient, useSubscribedTenantClients, useUserClient, useUserTenantClient };
|