@intentius/chant-lexicon-cpln 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -0
- package/dist/api.d.ts +77 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/codegen/docs-cli.d.ts +3 -0
- package/dist/codegen/docs-cli.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts +17 -0
- package/dist/codegen/docs.d.ts.map +1 -0
- package/dist/codegen/generate-cli.d.ts +6 -0
- package/dist/codegen/generate-cli.d.ts.map +1 -0
- package/dist/codegen/generate-lexicon.d.ts +36 -0
- package/dist/codegen/generate-lexicon.d.ts.map +1 -0
- package/dist/codegen/generate-typescript.d.ts +11 -0
- package/dist/codegen/generate-typescript.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts +15 -0
- package/dist/codegen/generate.d.ts.map +1 -0
- package/dist/codegen/naming.d.ts +30 -0
- package/dist/codegen/naming.d.ts.map +1 -0
- package/dist/codegen/package.d.ts +17 -0
- package/dist/codegen/package.d.ts.map +1 -0
- package/dist/composites/cron-job.d.ts +60 -0
- package/dist/composites/cron-job.d.ts.map +1 -0
- package/dist/composites/gvc-environment.d.ts +47 -0
- package/dist/composites/gvc-environment.d.ts.map +1 -0
- package/dist/composites/index.d.ts +13 -0
- package/dist/composites/index.d.ts.map +1 -0
- package/dist/composites/public-domain.d.ts +68 -0
- package/dist/composites/public-domain.d.ts.map +1 -0
- package/dist/composites/secret-access.d.ts +78 -0
- package/dist/composites/secret-access.d.ts.map +1 -0
- package/dist/composites/serverless-service.d.ts +78 -0
- package/dist/composites/serverless-service.d.ts.map +1 -0
- package/dist/composites/stateful-service.d.ts +79 -0
- package/dist/composites/stateful-service.d.ts.map +1 -0
- package/dist/coverage.d.ts +59 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/describe-resources.d.ts +71 -0
- package/dist/describe-resources.d.ts.map +1 -0
- package/dist/detect.d.ts +14 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/entity-props.d.ts +34 -0
- package/dist/entity-props.d.ts.map +1 -0
- package/dist/generated/index.d.ts +151 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/runtime.d.ts +5 -0
- package/dist/generated/runtime.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/init-templates.d.ts +16 -0
- package/dist/init-templates.d.ts.map +1 -0
- package/dist/integrity.json +35 -0
- package/dist/kinds.d.ts +70 -0
- package/dist/kinds.d.ts.map +1 -0
- package/dist/lint/audit-catalog.d.ts +19 -0
- package/dist/lint/audit-catalog.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl010-unrestricted-outbound.d.ts +12 -0
- package/dist/lint/post-synth/cpl010-unrestricted-outbound.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl011-internal-firewall-scope.d.ts +11 -0
- package/dist/lint/post-synth/cpl011-internal-firewall-scope.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl012-no-literal-credential-env.d.ts +11 -0
- package/dist/lint/post-synth/cpl012-no-literal-credential-env.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl013-qualified-identity-principal.d.ts +11 -0
- package/dist/lint/post-synth/cpl013-qualified-identity-principal.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl014-qualified-secret-ref.d.ts +14 -0
- package/dist/lint/post-synth/cpl014-qualified-secret-ref.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl020-serverless-single-port.d.ts +10 -0
- package/dist/lint/post-synth/cpl020-serverless-single-port.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl021-cron-job-shape.d.ts +11 -0
- package/dist/lint/post-synth/cpl021-cron-job-shape.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl022-container-ports.d.ts +10 -0
- package/dist/lint/post-synth/cpl022-container-ports.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl023-container-resources.d.ts +12 -0
- package/dist/lint/post-synth/cpl023-container-resources.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl024-probe-handler.d.ts +10 -0
- package/dist/lint/post-synth/cpl024-probe-handler.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl025-autoscaling-shape.d.ts +11 -0
- package/dist/lint/post-synth/cpl025-autoscaling-shape.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl026-scale-to-zero.d.ts +11 -0
- package/dist/lint/post-synth/cpl026-scale-to-zero.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl027-capacity-ai-conflict.d.ts +13 -0
- package/dist/lint/post-synth/cpl027-capacity-ai-conflict.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl028-volume-set.d.ts +3 -0
- package/dist/lint/post-synth/cpl028-volume-set.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl029-link-targets.d.ts +3 -0
- package/dist/lint/post-synth/cpl029-link-targets.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl030-domain-routing.d.ts +3 -0
- package/dist/lint/post-synth/cpl030-domain-routing.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl040-pinned-image.d.ts +11 -0
- package/dist/lint/post-synth/cpl040-pinned-image.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl041-image-reference-form.d.ts +12 -0
- package/dist/lint/post-synth/cpl041-image-reference-form.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl042-gvc-placement.d.ts +10 -0
- package/dist/lint/post-synth/cpl042-gvc-placement.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl043-policy-scope.d.ts +16 -0
- package/dist/lint/post-synth/cpl043-policy-scope.d.ts.map +1 -0
- package/dist/lint/post-synth/helpers.d.ts +45 -0
- package/dist/lint/post-synth/helpers.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts +3 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -0
- package/dist/lint/rules/cpl001-no-secret-literals.d.ts +3 -0
- package/dist/lint/rules/cpl001-no-secret-literals.d.ts.map +1 -0
- package/dist/lint/rules/cpl002-prefer-resource-reference.d.ts +3 -0
- package/dist/lint/rules/cpl002-prefer-resource-reference.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +6 -0
- package/dist/lint/rules/index.d.ts.map +1 -0
- package/dist/lsp/completions.d.ts +7 -0
- package/dist/lsp/completions.d.ts.map +1 -0
- package/dist/lsp/hover.d.ts +4 -0
- package/dist/lsp/hover.d.ts.map +1 -0
- package/dist/manifest.json +6 -0
- package/dist/meta.json +2110 -0
- package/dist/okf/index.md +39 -0
- package/dist/okf/rules/CPL001.md +22 -0
- package/dist/okf/rules/CPL002.md +22 -0
- package/dist/okf/rules/CPL010.md +15 -0
- package/dist/okf/rules/CPL011.md +15 -0
- package/dist/okf/rules/CPL012.md +15 -0
- package/dist/okf/rules/CPL013.md +15 -0
- package/dist/okf/rules/CPL014.md +16 -0
- package/dist/okf/rules/CPL020.md +11 -0
- package/dist/okf/rules/CPL021.md +15 -0
- package/dist/okf/rules/CPL022.md +15 -0
- package/dist/okf/rules/CPL023.md +15 -0
- package/dist/okf/rules/CPL024.md +15 -0
- package/dist/okf/rules/CPL025.md +15 -0
- package/dist/okf/rules/CPL026.md +15 -0
- package/dist/okf/rules/CPL027.md +15 -0
- package/dist/okf/rules/CPL028.md +15 -0
- package/dist/okf/rules/CPL029.md +15 -0
- package/dist/okf/rules/CPL030.md +16 -0
- package/dist/okf/rules/CPL040.md +15 -0
- package/dist/okf/rules/CPL041.md +15 -0
- package/dist/okf/rules/CPL042.md +11 -0
- package/dist/okf/rules/CPL043.md +15 -0
- package/dist/okf/types/Domain.md +22 -0
- package/dist/okf/types/Gvc.md +21 -0
- package/dist/okf/types/Identity.md +29 -0
- package/dist/okf/types/IpSet.md +21 -0
- package/dist/okf/types/Policy.md +28 -0
- package/dist/okf/types/Secret.md +23 -0
- package/dist/okf/types/VolumeSet.md +22 -0
- package/dist/okf/types/Workload.md +38 -0
- package/dist/ownership.d.ts +23 -0
- package/dist/ownership.d.ts.map +1 -0
- package/dist/package-cli.d.ts +3 -0
- package/dist/package-cli.d.ts.map +1 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/reference-catalog.d.ts +24 -0
- package/dist/reference-catalog.d.ts.map +1 -0
- package/dist/rules/cpl001-no-secret-literals.ts +83 -0
- package/dist/rules/cpl002-prefer-resource-reference.ts +71 -0
- package/dist/rules/cpl010-unrestricted-outbound.ts +41 -0
- package/dist/rules/cpl011-internal-firewall-scope.ts +38 -0
- package/dist/rules/cpl012-no-literal-credential-env.ts +58 -0
- package/dist/rules/cpl013-qualified-identity-principal.ts +45 -0
- package/dist/rules/cpl014-qualified-secret-ref.ts +54 -0
- package/dist/rules/cpl020-serverless-single-port.ts +58 -0
- package/dist/rules/cpl021-cron-job-shape.ts +64 -0
- package/dist/rules/cpl022-container-ports.ts +64 -0
- package/dist/rules/cpl023-container-resources.ts +87 -0
- package/dist/rules/cpl024-probe-handler.ts +51 -0
- package/dist/rules/cpl025-autoscaling-shape.ts +100 -0
- package/dist/rules/cpl026-scale-to-zero.ts +67 -0
- package/dist/rules/cpl027-capacity-ai-conflict.ts +78 -0
- package/dist/rules/cpl028-volume-set.ts +183 -0
- package/dist/rules/cpl029-link-targets.ts +155 -0
- package/dist/rules/cpl030-domain-routing.ts +137 -0
- package/dist/rules/cpl040-pinned-image.ts +48 -0
- package/dist/rules/cpl041-image-reference-form.ts +60 -0
- package/dist/rules/cpl042-gvc-placement.ts +57 -0
- package/dist/rules/cpl043-policy-scope.ts +0 -0
- package/dist/rules/helpers.ts +114 -0
- package/dist/serializer.d.ts +69 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/skills/chant-cpln-secrets.md +110 -0
- package/dist/skills/chant-cpln-workloads.md +78 -0
- package/dist/skills/chant-cpln.md +103 -0
- package/dist/spec/fetch.d.ts +43 -0
- package/dist/spec/fetch.d.ts.map +1 -0
- package/dist/spec/parse.d.ts +79 -0
- package/dist/spec/parse.d.ts.map +1 -0
- package/dist/spec/snapshot-cli.d.ts +18 -0
- package/dist/spec/snapshot-cli.d.ts.map +1 -0
- package/dist/types/index.d.ts +1649 -0
- package/dist/validate-cli.d.ts +3 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate.d.ts +26 -0
- package/dist/validate.d.ts.map +1 -0
- package/package.json +74 -0
- package/src/api.ts +134 -0
- package/src/codegen/docs-cli.ts +7 -0
- package/src/codegen/docs.ts +94 -0
- package/src/codegen/generate-cli.ts +34 -0
- package/src/codegen/generate-lexicon.ts +88 -0
- package/src/codegen/generate-typescript.ts +71 -0
- package/src/codegen/generate.ts +110 -0
- package/src/codegen/naming.ts +61 -0
- package/src/codegen/package.ts +35 -0
- package/src/composites/composites.test.ts +252 -0
- package/src/composites/cron-job.ts +124 -0
- package/src/composites/gvc-environment.ts +104 -0
- package/src/composites/index.ts +17 -0
- package/src/composites/public-domain.ts +144 -0
- package/src/composites/secret-access.ts +136 -0
- package/src/composites/serverless-service.ts +143 -0
- package/src/composites/stateful-service.ts +179 -0
- package/src/coverage.ts +149 -0
- package/src/describe-resources.test.ts +252 -0
- package/src/describe-resources.ts +310 -0
- package/src/detect.ts +58 -0
- package/src/entity-props.ts +98 -0
- package/src/generated/index.d.ts +1649 -0
- package/src/generated/index.ts +156 -0
- package/src/generated/lexicon-cpln.json +2110 -0
- package/src/generated/runtime.ts +4 -0
- package/src/index.ts +31 -0
- package/src/init-templates.ts +131 -0
- package/src/kinds.ts +173 -0
- package/src/lint/audit-catalog.ts +230 -0
- package/src/lint/post-synth/cpl010-unrestricted-outbound.ts +41 -0
- package/src/lint/post-synth/cpl011-internal-firewall-scope.ts +38 -0
- package/src/lint/post-synth/cpl012-no-literal-credential-env.ts +58 -0
- package/src/lint/post-synth/cpl013-qualified-identity-principal.ts +45 -0
- package/src/lint/post-synth/cpl014-qualified-secret-ref.ts +54 -0
- package/src/lint/post-synth/cpl020-serverless-single-port.ts +58 -0
- package/src/lint/post-synth/cpl021-cron-job-shape.ts +64 -0
- package/src/lint/post-synth/cpl022-container-ports.ts +64 -0
- package/src/lint/post-synth/cpl023-container-resources.ts +87 -0
- package/src/lint/post-synth/cpl024-probe-handler.ts +51 -0
- package/src/lint/post-synth/cpl025-autoscaling-shape.ts +100 -0
- package/src/lint/post-synth/cpl026-scale-to-zero.ts +67 -0
- package/src/lint/post-synth/cpl027-capacity-ai-conflict.ts +78 -0
- package/src/lint/post-synth/cpl028-volume-set.ts +183 -0
- package/src/lint/post-synth/cpl029-link-targets.ts +155 -0
- package/src/lint/post-synth/cpl030-domain-routing.ts +137 -0
- package/src/lint/post-synth/cpl040-pinned-image.ts +48 -0
- package/src/lint/post-synth/cpl041-image-reference-form.ts +60 -0
- package/src/lint/post-synth/cpl042-gvc-placement.ts +57 -0
- package/src/lint/post-synth/cpl043-policy-scope.ts +0 -0
- package/src/lint/post-synth/helpers.ts +114 -0
- package/src/lint/post-synth/index.ts +45 -0
- package/src/lint/post-synth/post-synth.test.ts +737 -0
- package/src/lint/rules/cpl001-no-secret-literals.ts +83 -0
- package/src/lint/rules/cpl002-prefer-resource-reference.ts +71 -0
- package/src/lint/rules/index.ts +9 -0
- package/src/lint/rules/rules.test.ts +108 -0
- package/src/lsp/completions.test.ts +44 -0
- package/src/lsp/completions.ts +27 -0
- package/src/lsp/hover.test.ts +53 -0
- package/src/lsp/hover.ts +130 -0
- package/src/ownership.ts +28 -0
- package/src/package-cli.ts +25 -0
- package/src/plugin.test.ts +131 -0
- package/src/plugin.ts +187 -0
- package/src/reference-catalog.ts +110 -0
- package/src/serializer.test.ts +233 -0
- package/src/serializer.ts +253 -0
- package/src/skills/chant-cpln-secrets.md +110 -0
- package/src/skills/chant-cpln-workloads.md +78 -0
- package/src/skills/chant-cpln.md +103 -0
- package/src/spec/cpln-openapi.snapshot.json +10651 -0
- package/src/spec/fetch.ts +77 -0
- package/src/spec/parse.ts +451 -0
- package/src/spec/snapshot-cli.ts +71 -0
- package/src/validate-cli.ts +8 -0
- package/src/validate.ts +102 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cpln naming strategy.
|
|
3
|
+
*
|
|
4
|
+
* Every generated type is `Cpln::Core::{Name}` with a `{Name}` the parser has
|
|
5
|
+
* already made unique — `$ref`'d schemas take their upstream name and inline
|
|
6
|
+
* shapes take their path — so the core strategy's collision phases never fire
|
|
7
|
+
* and `resolve` returns the last segment unchanged. The value of going through
|
|
8
|
+
* the core strategy anyway is phase 1a: once this lexicon publishes a surface
|
|
9
|
+
* snapshot, a name that has shipped stays attached to the type that shipped it
|
|
10
|
+
* even if an upstream reshuffle would otherwise hand it to a newcomer.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
NamingStrategy as CoreNamingStrategy,
|
|
15
|
+
reservedNamesFromSnapshot,
|
|
16
|
+
type NamingConfig,
|
|
17
|
+
type NamingInput,
|
|
18
|
+
} from "@intentius/chant/codegen/naming";
|
|
19
|
+
import { cplnShortName, cplnServiceName, KINDS, SERVICE } from "../kinds";
|
|
20
|
+
import type { CplnParseResult } from "../spec/parse";
|
|
21
|
+
|
|
22
|
+
export { propertyTypeName, extractDefName } from "@intentius/chant/codegen/naming";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The eight kinds are pinned by hand. They are the names users type, and they
|
|
26
|
+
* should not move because a property type elsewhere in the spec started or
|
|
27
|
+
* stopped existing.
|
|
28
|
+
*/
|
|
29
|
+
const priorityNames: Record<string, string> = Object.fromEntries(
|
|
30
|
+
KINDS.map((k) => [k.typeName, k.className]),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const cplnNamingConfig: NamingConfig = {
|
|
34
|
+
priorityNames,
|
|
35
|
+
priorityAliases: {},
|
|
36
|
+
priorityPropertyAliases: {},
|
|
37
|
+
serviceAbbreviations: { [SERVICE]: SERVICE },
|
|
38
|
+
shortName: cplnShortName,
|
|
39
|
+
serviceName: cplnServiceName,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export interface CplnNamingOptions {
|
|
43
|
+
/** A committed `surface.snapshot.json`, when one exists. */
|
|
44
|
+
snapshot?: { entries?: Record<string, { kind?: string; resourceType?: string }> };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* cpln-specific naming strategy.
|
|
49
|
+
*/
|
|
50
|
+
export class NamingStrategy extends CoreNamingStrategy {
|
|
51
|
+
constructor(results: CplnParseResult[], options: CplnNamingOptions = {}) {
|
|
52
|
+
const inputs: NamingInput[] = results.map((r) => ({
|
|
53
|
+
typeName: r.resource.typeName,
|
|
54
|
+
propertyTypes: r.propertyTypes,
|
|
55
|
+
}));
|
|
56
|
+
super(inputs, {
|
|
57
|
+
...cplnNamingConfig,
|
|
58
|
+
reservedNames: reservedNamesFromSnapshot(options.snapshot),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package the cpln lexicon into the distributable bundle under `dist/`.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import { dirname, join } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { packagePipeline, collectSkills } from "@intentius/chant/codegen/package";
|
|
9
|
+
import { generate } from "./generate";
|
|
10
|
+
import { cplnPlugin } from "../plugin";
|
|
11
|
+
|
|
12
|
+
export async function packageLexicon(options?: { verbose?: boolean; force?: boolean }) {
|
|
13
|
+
// This file is `src/codegen/package.ts`; rules and skills are collected
|
|
14
|
+
// relative to `src/`, so srcDir is the parent of this directory. Pointing it
|
|
15
|
+
// at `src/codegen` globs a directory that does not exist and silently ships
|
|
16
|
+
// a bundle with zero rules and zero skills.
|
|
17
|
+
const srcDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
18
|
+
const pkgJson = JSON.parse(readFileSync(join(srcDir, "..", "package.json"), "utf-8"));
|
|
19
|
+
|
|
20
|
+
const { spec, stats } = await packagePipeline({
|
|
21
|
+
generate: (opts) => generate({ verbose: opts?.verbose, force: opts?.force }),
|
|
22
|
+
buildManifest: () => ({
|
|
23
|
+
name: "cpln",
|
|
24
|
+
version: pkgJson.version ?? "0.0.0",
|
|
25
|
+
chantVersion: ">=0.1.0",
|
|
26
|
+
namespace: "Cpln",
|
|
27
|
+
}),
|
|
28
|
+
srcDir,
|
|
29
|
+
collectSkills: () => collectSkills(cplnPlugin.skills?.() ?? []),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Both callers (the plugin's `package()` and `package-cli`) print their own
|
|
33
|
+
// summary — don't double-report here.
|
|
34
|
+
return { spec, stats };
|
|
35
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { isCompositeInstance } from "@intentius/chant";
|
|
3
|
+
import { GvcEnvironment, ServerlessService, CronJob, StatefulService, SecretAccess, PublicDomain } from "./index";
|
|
4
|
+
import { identityLink, secretRef } from "./secret-access";
|
|
5
|
+
import { isApexDomain } from "./public-domain";
|
|
6
|
+
import { propsOf, readString, readArray, readNumber, readPath } from "../entity-props";
|
|
7
|
+
|
|
8
|
+
describe("GvcEnvironment", () => {
|
|
9
|
+
it("builds location links from the org and location ids", () => {
|
|
10
|
+
const { gvc } = GvcEnvironment({ name: "prod", org: "acme", locations: ["aws-us-east-1"] });
|
|
11
|
+
|
|
12
|
+
expect(isCompositeInstance({ gvc })).toBe(false);
|
|
13
|
+
expect(readString(gvc, "name")).toBe("prod");
|
|
14
|
+
expect(readArray(gvc, "spec", "staticPlacement", "locationLinks")).toEqual([
|
|
15
|
+
"/org/acme/location/aws-us-east-1",
|
|
16
|
+
]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("sorts locations and pull secrets so output is stable", () => {
|
|
20
|
+
const { gvc } = GvcEnvironment({
|
|
21
|
+
name: "prod",
|
|
22
|
+
org: "acme",
|
|
23
|
+
locations: ["gcp-us-central1", "aws-us-east-1"],
|
|
24
|
+
pullSecrets: ["z-creds", "a-creds"],
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
expect(readArray(gvc, "spec", "staticPlacement", "locationLinks")).toEqual([
|
|
28
|
+
"/org/acme/location/aws-us-east-1",
|
|
29
|
+
"/org/acme/location/gcp-us-central1",
|
|
30
|
+
]);
|
|
31
|
+
expect(readArray(gvc, "spec", "pullSecretLinks")).toEqual(["//secret/a-creds", "//secret/z-creds"]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("refuses a GVC with no locations", () => {
|
|
35
|
+
expect(() => GvcEnvironment({ name: "prod", org: "acme", locations: [] })).toThrow(/runs them nowhere/);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("refuses a location id that is not one", () => {
|
|
39
|
+
expect(() => GvcEnvironment({ name: "prod", org: "acme", locations: ["us-east-1"] })).toThrow(
|
|
40
|
+
/not a location id/,
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("ServerlessService", () => {
|
|
46
|
+
it("emits exactly one HTTP port", () => {
|
|
47
|
+
const { workload } = ServerlessService({ name: "web", gvc: "prod", image: "nginx:1.27" });
|
|
48
|
+
const containers = readArray(workload, "spec", "containers");
|
|
49
|
+
|
|
50
|
+
expect(readString(workload, "spec", "type")).toBe("serverless");
|
|
51
|
+
expect(containers).toHaveLength(1);
|
|
52
|
+
const ports = readArray(containers[0], "ports");
|
|
53
|
+
expect(ports).toHaveLength(1);
|
|
54
|
+
expect(readNumber(ports[0], "number")).toBe(8080);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("leaves the firewall closed by default in both directions", () => {
|
|
58
|
+
const { workload } = ServerlessService({ name: "web", gvc: "prod", image: "nginx:1.27" });
|
|
59
|
+
expect(readArray(workload, "spec", "firewallConfig", "external", "inboundAllowCIDR")).toEqual([]);
|
|
60
|
+
expect(readArray(workload, "spec", "firewallConfig", "external", "outboundAllowCIDR")).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("maps env to Control Plane's name/value pairs", () => {
|
|
64
|
+
const { workload } = ServerlessService({
|
|
65
|
+
name: "web",
|
|
66
|
+
gvc: "prod",
|
|
67
|
+
image: "nginx:1.27",
|
|
68
|
+
env: { LOG_LEVEL: "info" },
|
|
69
|
+
});
|
|
70
|
+
const env = readArray(readArray(workload, "spec", "containers")[0], "env");
|
|
71
|
+
expect(env).toEqual([{ name: "LOG_LEVEL", value: "info" }]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("honours per-member defaults", () => {
|
|
75
|
+
const { workload } = ServerlessService({
|
|
76
|
+
name: "web",
|
|
77
|
+
gvc: "prod",
|
|
78
|
+
image: "nginx:1.27",
|
|
79
|
+
defaults: { workload: { description: "the web tier" } },
|
|
80
|
+
});
|
|
81
|
+
expect(readString(workload, "description")).toBe("the web tier");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("CronJob", () => {
|
|
86
|
+
it("sets the schedule and exposes no ports", () => {
|
|
87
|
+
const { workload } = CronJob({ name: "nightly", gvc: "prod", image: "busybox:1.36", schedule: "0 3 * * *" });
|
|
88
|
+
|
|
89
|
+
expect(readString(workload, "spec", "type")).toBe("cron");
|
|
90
|
+
expect(readString(workload, "spec", "job", "schedule")).toBe("0 3 * * *");
|
|
91
|
+
expect(readArray(readArray(workload, "spec", "containers")[0], "ports")).toEqual([]);
|
|
92
|
+
expect(readArray(workload, "spec", "firewallConfig", "external", "inboundAllowCIDR")).toEqual([]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("defaults to Forbid so runs do not overlap", () => {
|
|
96
|
+
const { workload } = CronJob({ name: "nightly", gvc: "prod", image: "busybox:1.36", schedule: "0 3 * * *" });
|
|
97
|
+
expect(readString(workload, "spec", "job", "concurrencyPolicy")).toBe("Forbid");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("StatefulService", () => {
|
|
102
|
+
it("pairs the workload and volume set on the same mount", () => {
|
|
103
|
+
const { workload, volumeSet } = StatefulService({
|
|
104
|
+
name: "db",
|
|
105
|
+
gvc: "prod",
|
|
106
|
+
image: "postgres:17",
|
|
107
|
+
mountPath: "/var/lib/postgresql/data",
|
|
108
|
+
capacityGb: 20,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(readString(volumeSet, "name")).toBe("db-data");
|
|
112
|
+
expect(readString(volumeSet, "gvc")).toBe("prod");
|
|
113
|
+
expect(readString(workload, "spec", "type")).toBe("stateful");
|
|
114
|
+
|
|
115
|
+
const volumes = readArray(readArray(workload, "spec", "containers")[0], "volumes");
|
|
116
|
+
expect(readString(volumes[0], "uri")).toBe("cpln://volumeset/db-data");
|
|
117
|
+
expect(readString(volumes[0], "path")).toBe("/var/lib/postgresql/data");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("refuses a capacity below the performance class floor", () => {
|
|
121
|
+
expect(() =>
|
|
122
|
+
StatefulService({
|
|
123
|
+
name: "db",
|
|
124
|
+
gvc: "prod",
|
|
125
|
+
image: "postgres:17",
|
|
126
|
+
mountPath: "/data",
|
|
127
|
+
capacityGb: 50,
|
|
128
|
+
performanceClass: "high-throughput-ssd",
|
|
129
|
+
}),
|
|
130
|
+
).toThrow(/200 GB minimum/);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("forces performanceClass shared for a shared filesystem", () => {
|
|
134
|
+
// Control Plane forces it server-side; declaring it too keeps the manifest
|
|
135
|
+
// equal to what comes back, so it does not read as drift on every plan.
|
|
136
|
+
const { volumeSet } = StatefulService({
|
|
137
|
+
name: "files",
|
|
138
|
+
gvc: "prod",
|
|
139
|
+
image: "nginx:1.27",
|
|
140
|
+
mountPath: "/files",
|
|
141
|
+
capacityGb: 10,
|
|
142
|
+
fileSystemType: "shared",
|
|
143
|
+
});
|
|
144
|
+
expect(readString(volumeSet, "spec", "performanceClass")).toBe("shared");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("keeps createFinalSnapshot on and defaults to ext4", () => {
|
|
148
|
+
// `createFinalSnapshot` auto-snapshots before any volume deletion. It
|
|
149
|
+
// defaults on upstream; declaring it means a later edit that turns it off
|
|
150
|
+
// is a visible diff rather than an inherited default nobody chose.
|
|
151
|
+
const { volumeSet } = StatefulService({
|
|
152
|
+
name: "db",
|
|
153
|
+
gvc: "prod",
|
|
154
|
+
image: "postgres:17",
|
|
155
|
+
mountPath: "/data",
|
|
156
|
+
capacityGb: 20,
|
|
157
|
+
});
|
|
158
|
+
expect(readString(volumeSet, "spec", "fileSystemType")).toBe("ext4");
|
|
159
|
+
expect(propsOf(readPath(volumeSet, "spec", "snapshots")).createFinalSnapshot).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe("SecretAccess", () => {
|
|
164
|
+
it("emits a GVC-qualified principal link", () => {
|
|
165
|
+
const { identity, policy } = SecretAccess({ name: "api", gvc: "prod", secrets: ["db-password"] });
|
|
166
|
+
|
|
167
|
+
expect(readString(identity, "name")).toBe("api");
|
|
168
|
+
expect(readString(policy, "targetKind")).toBe("secret");
|
|
169
|
+
expect(readArray(policy, "targetLinks")).toEqual(["//secret/db-password"]);
|
|
170
|
+
|
|
171
|
+
const bindings = readArray(policy, "bindings");
|
|
172
|
+
expect(readArray(bindings[0], "principalLinks")).toEqual(["//gvc/prod/identity/api"]);
|
|
173
|
+
// The bare form is accepted by Control Plane and silently ignored.
|
|
174
|
+
expect(JSON.stringify(bindings)).not.toContain('"//identity/api"');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("sorts and deduplicates permissions", () => {
|
|
178
|
+
const { policy } = SecretAccess({
|
|
179
|
+
name: "api",
|
|
180
|
+
gvc: "prod",
|
|
181
|
+
secrets: ["db"],
|
|
182
|
+
permissions: ["reveal", "edit", "reveal"],
|
|
183
|
+
});
|
|
184
|
+
expect(readArray(readArray(policy, "bindings")[0], "permissions")).toEqual(["edit", "reveal"]);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("refuses an empty secret list", () => {
|
|
188
|
+
expect(() => SecretAccess({ name: "api", gvc: "prod", secrets: [] })).toThrow(/grants nothing/);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe("PublicDomain", () => {
|
|
193
|
+
it("routes to the GVC when no routes are given", () => {
|
|
194
|
+
const { domain } = PublicDomain({ name: "api.example.com", gvc: "prod" });
|
|
195
|
+
expect(readString(domain, "spec", "gvcLink")).toBe("//gvc/prod");
|
|
196
|
+
expect(readArray(domain, "spec", "ports")).toEqual([]);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("emits per-port routes and no gvcLink when routes are given", () => {
|
|
200
|
+
const { domain } = PublicDomain({
|
|
201
|
+
name: "api.example.com",
|
|
202
|
+
gvc: "prod",
|
|
203
|
+
routes: [{ prefix: "/v1", workload: "api", port: 8080 }],
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
expect(readString(domain, "spec", "gvcLink")).toBeUndefined();
|
|
207
|
+
const routes = readArray(readArray(domain, "spec", "ports")[0], "routes");
|
|
208
|
+
expect(readString(routes[0], "workloadLink")).toBe("//gvc/prod/workload/api");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("defaults the challenge type to the one the DNS mode allows", () => {
|
|
212
|
+
expect(readString(PublicDomain({ name: "api.example.com", gvc: "prod" }).domain, "spec", "certChallengeType")).toBe(
|
|
213
|
+
"http01",
|
|
214
|
+
);
|
|
215
|
+
expect(
|
|
216
|
+
readString(
|
|
217
|
+
PublicDomain({ name: "api.example.com", gvc: "prod", dnsMode: "ns" }).domain,
|
|
218
|
+
"spec",
|
|
219
|
+
"certChallengeType",
|
|
220
|
+
),
|
|
221
|
+
).toBe("dns01");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("refuses an apex domain in ns mode", () => {
|
|
225
|
+
expect(() => PublicDomain({ name: "example.com", gvc: "prod", dnsMode: "ns" })).toThrow(/apex/);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("refuses a route with both prefix and regex", () => {
|
|
229
|
+
expect(() =>
|
|
230
|
+
PublicDomain({
|
|
231
|
+
name: "api.example.com",
|
|
232
|
+
gvc: "prod",
|
|
233
|
+
routes: [{ prefix: "/v1", regex: "^/v1", workload: "api" }],
|
|
234
|
+
}),
|
|
235
|
+
).toThrow(/only one may be provided/);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe("helpers", () => {
|
|
240
|
+
it("qualifies an identity link", () => {
|
|
241
|
+
expect(identityLink("prod", "api")).toBe("//gvc/prod/identity/api");
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("qualifies a secret reference with its field", () => {
|
|
245
|
+
expect(secretRef("db", "payload")).toBe("cpln://secret/db.payload");
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("recognises apex domains", () => {
|
|
249
|
+
expect(isApexDomain("example.com")).toBe(true);
|
|
250
|
+
expect(isApexDomain("api.example.com")).toBe(false);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CronJob — a scheduled workload, with the constraints cron carries.
|
|
3
|
+
*
|
|
4
|
+
* Cron is the workload type with the most *silent* rules: it must not expose
|
|
5
|
+
* any port, its container has to exit on completion, and probes, autoscaling,
|
|
6
|
+
* `timeoutSeconds` and `debug` are all accepted and then ignored. Rather than
|
|
7
|
+
* let a caller set those and wonder why nothing happens, this composite has no
|
|
8
|
+
* knobs for them.
|
|
9
|
+
*
|
|
10
|
+
* A cron workload also deploys to every location in its GVC with no
|
|
11
|
+
* per-location override, so placement is a property of the GVC, not of this.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
15
|
+
import { Workload } from "../generated";
|
|
16
|
+
|
|
17
|
+
export interface CronJobProps {
|
|
18
|
+
/** Workload name. Max 49 characters. */
|
|
19
|
+
name: string;
|
|
20
|
+
/** GVC this job is deployed into. */
|
|
21
|
+
gvc: string;
|
|
22
|
+
/** Container image. */
|
|
23
|
+
image: string;
|
|
24
|
+
/** Cron schedule, standard five-field syntax (e.g. `"0 * * * *"`). */
|
|
25
|
+
schedule: string;
|
|
26
|
+
/** Container name (default `main`). */
|
|
27
|
+
containerName?: string;
|
|
28
|
+
/** Entrypoint override. */
|
|
29
|
+
command?: string;
|
|
30
|
+
/** Arguments passed to the entrypoint. */
|
|
31
|
+
args?: string[];
|
|
32
|
+
/** CPU in millicores (default `50m`). */
|
|
33
|
+
cpu?: string;
|
|
34
|
+
/** Memory (default `128Mi`). */
|
|
35
|
+
memory?: string;
|
|
36
|
+
/** Environment variables, as a plain map. */
|
|
37
|
+
env?: Record<string, string>;
|
|
38
|
+
/**
|
|
39
|
+
* What to do when a run is still going at the next scheduled time
|
|
40
|
+
* (default `Forbid` — skip the new run rather than overlap).
|
|
41
|
+
*/
|
|
42
|
+
concurrencyPolicy?: "Allow" | "Forbid" | "Replace";
|
|
43
|
+
/** Restart behaviour for a failed run (default `Never`). */
|
|
44
|
+
restartPolicy?: "Never" | "OnFailure";
|
|
45
|
+
/** Seconds a run may take before it is killed. */
|
|
46
|
+
activeDeadlineSeconds?: number;
|
|
47
|
+
/** How many finished runs to retain. */
|
|
48
|
+
historyLimit?: number;
|
|
49
|
+
/** Hostnames or CIDRs the job may call out to. Defaults to closed. */
|
|
50
|
+
outboundAllowCidr?: string[];
|
|
51
|
+
/** Identity to run as, as a link (`//gvc/GVC/identity/NAME`). */
|
|
52
|
+
identityLink?: string;
|
|
53
|
+
/** Tags applied to the workload. */
|
|
54
|
+
tags?: Record<string, string>;
|
|
55
|
+
/** Per-member defaults for customizing the underlying resource. */
|
|
56
|
+
defaults?: {
|
|
57
|
+
workload?: Partial<ConstructorParameters<typeof Workload>[0]>;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const CronJob = Composite((props: CronJobProps) => {
|
|
62
|
+
const {
|
|
63
|
+
name,
|
|
64
|
+
gvc,
|
|
65
|
+
image,
|
|
66
|
+
schedule,
|
|
67
|
+
containerName = "main",
|
|
68
|
+
command,
|
|
69
|
+
args,
|
|
70
|
+
cpu = "50m",
|
|
71
|
+
memory = "128Mi",
|
|
72
|
+
env,
|
|
73
|
+
concurrencyPolicy = "Forbid",
|
|
74
|
+
restartPolicy = "Never",
|
|
75
|
+
activeDeadlineSeconds,
|
|
76
|
+
historyLimit,
|
|
77
|
+
outboundAllowCidr = [],
|
|
78
|
+
identityLink,
|
|
79
|
+
tags,
|
|
80
|
+
defaults: defs,
|
|
81
|
+
} = props;
|
|
82
|
+
|
|
83
|
+
const workload = new Workload(
|
|
84
|
+
mergeDefaults(
|
|
85
|
+
{
|
|
86
|
+
name,
|
|
87
|
+
gvc,
|
|
88
|
+
...(tags && { tags }),
|
|
89
|
+
spec: {
|
|
90
|
+
type: "cron",
|
|
91
|
+
...(identityLink && { identityLink }),
|
|
92
|
+
job: {
|
|
93
|
+
schedule,
|
|
94
|
+
concurrencyPolicy,
|
|
95
|
+
restartPolicy,
|
|
96
|
+
...(activeDeadlineSeconds !== undefined && { activeDeadlineSeconds }),
|
|
97
|
+
...(historyLimit !== undefined && { historyLimit }),
|
|
98
|
+
},
|
|
99
|
+
containers: [
|
|
100
|
+
{
|
|
101
|
+
name: containerName,
|
|
102
|
+
image,
|
|
103
|
+
cpu,
|
|
104
|
+
memory,
|
|
105
|
+
...(command && { command }),
|
|
106
|
+
...(args && { args }),
|
|
107
|
+
...(env && {
|
|
108
|
+
env: Object.entries(env).map(([envName, value]) => ({ name: envName, value })),
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
firewallConfig: {
|
|
113
|
+
// A cron job serves nothing, so inbound stays closed. Outbound is
|
|
114
|
+
// the only direction worth opening.
|
|
115
|
+
external: { inboundAllowCIDR: [], outboundAllowCIDR: outboundAllowCidr },
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
} as Record<string, unknown>,
|
|
119
|
+
defs?.workload,
|
|
120
|
+
),
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return { workload };
|
|
124
|
+
}, "CronJob");
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GvcEnvironment — a GVC placed in named locations, with its pull secrets.
|
|
3
|
+
*
|
|
4
|
+
* Two things belong here rather than at the workload:
|
|
5
|
+
*
|
|
6
|
+
* - **Pull secrets are GVC-level** (`spec.pullSecretLinks`), not per-workload.
|
|
7
|
+
* Looking for them on a workload is a common wrong turn, and only the
|
|
8
|
+
* `docker`, `ecr` and `gcp` secret types are valid as pull secrets.
|
|
9
|
+
* - **Placement is GVC-level.** Location links use the full org-qualified path
|
|
10
|
+
* (`/org/ORG/location/aws-us-east-1`), which is why this takes an `org`.
|
|
11
|
+
*
|
|
12
|
+
* The location id format is `<provider>-<region>`: `aws-us-east-1`,
|
|
13
|
+
* `gcp-us-central1`, `azure-eastus`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
17
|
+
import { Gvc } from "../generated";
|
|
18
|
+
|
|
19
|
+
/** Location ids look like `aws-us-east-1`, `gcp-us-central1`, `azure-eastus`. */
|
|
20
|
+
const LOCATION_ID = /^(aws|gcp|azure)-[a-z0-9-]+$/;
|
|
21
|
+
|
|
22
|
+
export interface GvcEnvironmentProps {
|
|
23
|
+
/** GVC name. */
|
|
24
|
+
name: string;
|
|
25
|
+
/** Org the GVC belongs to. Needed to build location links. */
|
|
26
|
+
org: string;
|
|
27
|
+
/** Location ids to place workloads in, e.g. `["aws-us-east-1"]`. */
|
|
28
|
+
locations: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Names of secrets used to pull private images. Only `docker`, `ecr` and
|
|
31
|
+
* `gcp` secret types are valid here.
|
|
32
|
+
*/
|
|
33
|
+
pullSecrets?: string[];
|
|
34
|
+
/**
|
|
35
|
+
* Public endpoint naming scheme (default `org`, the default for new GVCs):
|
|
36
|
+
* `{workload}-{gvcAlias}.{orgPrefix}.cpln.app`.
|
|
37
|
+
*/
|
|
38
|
+
endpointNamingFormat?: "default" | "legacy" | "org";
|
|
39
|
+
/** Environment variables inherited by every workload in the GVC. */
|
|
40
|
+
env?: Record<string, string>;
|
|
41
|
+
/** Enable KEDA autoscaling for standard and stateful workloads in this GVC. */
|
|
42
|
+
keda?: boolean;
|
|
43
|
+
/** Tags applied to the GVC. */
|
|
44
|
+
tags?: Record<string, string>;
|
|
45
|
+
/** Per-member defaults for customizing the underlying resource. */
|
|
46
|
+
defaults?: {
|
|
47
|
+
gvc?: Partial<ConstructorParameters<typeof Gvc>[0]>;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const GvcEnvironment = Composite((props: GvcEnvironmentProps) => {
|
|
52
|
+
const {
|
|
53
|
+
name,
|
|
54
|
+
org,
|
|
55
|
+
locations,
|
|
56
|
+
pullSecrets,
|
|
57
|
+
endpointNamingFormat = "org",
|
|
58
|
+
env,
|
|
59
|
+
keda,
|
|
60
|
+
tags,
|
|
61
|
+
defaults: defs,
|
|
62
|
+
} = props;
|
|
63
|
+
|
|
64
|
+
if (locations.length === 0) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`GvcEnvironment "${name}": no locations. A GVC with no placement accepts workloads and runs them nowhere.`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const location of locations) {
|
|
71
|
+
if (!LOCATION_ID.test(location)) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`GvcEnvironment "${name}": "${location}" is not a location id. Expected <provider>-<region>, ` +
|
|
74
|
+
`e.g. "aws-us-east-1", "gcp-us-central1", "azure-eastus".`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const gvc = new Gvc(
|
|
80
|
+
mergeDefaults(
|
|
81
|
+
{
|
|
82
|
+
name,
|
|
83
|
+
...(tags && { tags }),
|
|
84
|
+
spec: {
|
|
85
|
+
endpointNamingFormat,
|
|
86
|
+
staticPlacement: {
|
|
87
|
+
locationLinks: [...locations].sort().map((location) => `/org/${org}/location/${location}`),
|
|
88
|
+
},
|
|
89
|
+
...(pullSecrets &&
|
|
90
|
+
pullSecrets.length > 0 && {
|
|
91
|
+
pullSecretLinks: [...pullSecrets].sort().map((secret) => `//secret/${secret}`),
|
|
92
|
+
}),
|
|
93
|
+
...(env && {
|
|
94
|
+
env: Object.entries(env).map(([envName, value]) => ({ name: envName, value })),
|
|
95
|
+
}),
|
|
96
|
+
...(keda !== undefined && { keda: { enabled: keda } }),
|
|
97
|
+
},
|
|
98
|
+
} as Record<string, unknown>,
|
|
99
|
+
defs?.gvc,
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return { gvc };
|
|
104
|
+
}, "GvcEnvironment");
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { GvcEnvironment } from "./gvc-environment";
|
|
2
|
+
export type { GvcEnvironmentProps } from "./gvc-environment";
|
|
3
|
+
|
|
4
|
+
export { ServerlessService } from "./serverless-service";
|
|
5
|
+
export type { ServerlessServiceProps } from "./serverless-service";
|
|
6
|
+
|
|
7
|
+
export { CronJob } from "./cron-job";
|
|
8
|
+
export type { CronJobProps } from "./cron-job";
|
|
9
|
+
|
|
10
|
+
export { StatefulService } from "./stateful-service";
|
|
11
|
+
export type { StatefulServiceProps } from "./stateful-service";
|
|
12
|
+
|
|
13
|
+
export { SecretAccess, identityLink, secretRef } from "./secret-access";
|
|
14
|
+
export type { SecretAccessProps } from "./secret-access";
|
|
15
|
+
|
|
16
|
+
export { PublicDomain, isApexDomain } from "./public-domain";
|
|
17
|
+
export type { PublicDomainProps, DomainRoute } from "./public-domain";
|