@r8s/recipes 0.2.0 → 0.3.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/dist/app.d.ts +1 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +4 -8
- package/dist/app.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +54 -94
- package/dist/auth.js.map +1 -1
- package/dist/backup.d.ts +18 -2
- package/dist/backup.d.ts.map +1 -1
- package/dist/backup.js +54 -6
- package/dist/backup.js.map +1 -1
- package/dist/cert-provider.d.ts.map +1 -1
- package/dist/cert-provider.js +2 -5
- package/dist/cert-provider.js.map +1 -1
- package/dist/database.d.ts +55 -13
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +135 -58
- package/dist/database.js.map +1 -1
- package/dist/dns-provider.d.ts.map +1 -1
- package/dist/dns-provider.js +2 -5
- package/dist/dns-provider.js.map +1 -1
- package/dist/endpoint.d.ts +1 -1
- package/dist/endpoint.d.ts.map +1 -1
- package/dist/endpoint.js +31 -23
- package/dist/endpoint.js.map +1 -1
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +19 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -5
- package/dist/index.js.map +1 -1
- package/dist/monitoring.d.ts.map +1 -1
- package/dist/monitoring.js +6 -13
- package/dist/monitoring.js.map +1 -1
- package/dist/platform.d.ts +3 -2
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +3 -2
- package/dist/platform.js.map +1 -1
- package/dist/r8s-cluster.d.ts +1 -1
- package/dist/r8s-cluster.d.ts.map +1 -1
- package/dist/r8s-cluster.js +25 -16
- package/dist/r8s-cluster.js.map +1 -1
- package/dist/s3-provider.d.ts +151 -0
- package/dist/s3-provider.d.ts.map +1 -0
- package/dist/s3-provider.js +126 -0
- package/dist/s3-provider.js.map +1 -0
- package/dist/secret-provider.d.ts +16 -7
- package/dist/secret-provider.d.ts.map +1 -1
- package/dist/secret-provider.js +80 -9
- package/dist/secret-provider.js.map +1 -1
- package/dist/static-secret.d.ts +107 -0
- package/dist/static-secret.d.ts.map +1 -0
- package/dist/static-secret.js +87 -0
- package/dist/static-secret.js.map +1 -0
- package/dist/use-operators.d.ts +7 -0
- package/dist/use-operators.d.ts.map +1 -0
- package/dist/use-operators.js +13 -0
- package/dist/use-operators.js.map +1 -0
- package/dist/web-service.d.ts +10 -2
- package/dist/web-service.d.ts.map +1 -1
- package/dist/web-service.js +54 -14
- package/dist/web-service.js.map +1 -1
- package/package.json +30 -5
- package/dist/operators.d.ts +0 -19
- package/dist/operators.d.ts.map +0 -1
- package/dist/operators.js +0 -30
- package/dist/operators.js.map +0 -1
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import { type SecretProvider as SecretProviderConfig } from '@r8s/core/defaults';
|
|
1
|
+
import { type SecretProvider as SecretProviderConfig, type SecretProvisioner } from '@r8s/core/defaults';
|
|
2
|
+
/**
|
|
3
|
+
* Provisioner resolution — THE single identity-aware point in the platform.
|
|
4
|
+
* Everything downstream codes against the `provision()` capability; new
|
|
5
|
+
* backends plug in by carrying their own provisioner on the context value
|
|
6
|
+
* (or by being registered here — the orchestration point).
|
|
7
|
+
*/
|
|
8
|
+
export declare function provisionerForSecretProvider(provider: SecretProviderConfig | null | undefined): SecretProvisioner | undefined;
|
|
9
|
+
/** Can this provider provision secrets? (capability check, not identity) */
|
|
10
|
+
export declare function canProvisionSecrets(provider: SecretProviderConfig | null | undefined): provider is SecretProviderConfig;
|
|
2
11
|
/**
|
|
3
12
|
* OpenBao configuration component.
|
|
4
13
|
*
|
|
@@ -60,7 +69,7 @@ export interface SecretProviderProps {
|
|
|
60
69
|
*
|
|
61
70
|
* export default (
|
|
62
71
|
* <SecretProvider provider="openbao">
|
|
63
|
-
* <Database name="app-db" />
|
|
72
|
+
* <Database backup={false} name="app-db" />
|
|
64
73
|
* </SecretProvider>
|
|
65
74
|
* )
|
|
66
75
|
*
|
|
@@ -69,7 +78,7 @@ export interface SecretProviderProps {
|
|
|
69
78
|
*
|
|
70
79
|
* export default (
|
|
71
80
|
* <SecretProvider provider={<OpenBao mount="secret" path="infra" authRef="custom" />}>
|
|
72
|
-
* <Database name="app-db" />
|
|
81
|
+
* <Database backup={false} name="app-db" />
|
|
73
82
|
* </SecretProvider>
|
|
74
83
|
* )
|
|
75
84
|
*/
|
|
@@ -88,7 +97,7 @@ export interface SecretProviderProps {
|
|
|
88
97
|
*
|
|
89
98
|
* export default (
|
|
90
99
|
* <SecretProvider provider="openbao">
|
|
91
|
-
* <Database name="app-db" />
|
|
100
|
+
* <Database backup={false} name="app-db" />
|
|
92
101
|
* </SecretProvider>
|
|
93
102
|
* )
|
|
94
103
|
*
|
|
@@ -97,7 +106,7 @@ export interface SecretProviderProps {
|
|
|
97
106
|
*
|
|
98
107
|
* export default (
|
|
99
108
|
* <SecretProvider provider={<OpenBao mount="secret" path="infra" authRef="custom-auth" />}>
|
|
100
|
-
* <Database name="app-db" />
|
|
109
|
+
* <Database backup={false} name="app-db" />
|
|
101
110
|
* </SecretProvider>
|
|
102
111
|
* )
|
|
103
112
|
*
|
|
@@ -106,7 +115,7 @@ export interface SecretProviderProps {
|
|
|
106
115
|
*
|
|
107
116
|
* export default (
|
|
108
117
|
* <SecretProvider provider={<Vault mount="kv" path="apps" />}>
|
|
109
|
-
* <Database name="app-db" />
|
|
118
|
+
* <Database backup={false} name="app-db" />
|
|
110
119
|
* </SecretProvider>
|
|
111
120
|
* )
|
|
112
121
|
*
|
|
@@ -115,7 +124,7 @@ export interface SecretProviderProps {
|
|
|
115
124
|
*
|
|
116
125
|
* export default (
|
|
117
126
|
* <SecretProvider provider="sealed-secrets">
|
|
118
|
-
* <Database name="app-db" />
|
|
127
|
+
* <Database backup={false} name="app-db" />
|
|
119
128
|
* </SecretProvider>
|
|
120
129
|
* )
|
|
121
130
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-provider.d.ts","sourceRoot":"","sources":["../src/secret-provider.tsx"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"secret-provider.d.ts","sourceRoot":"","sources":["../src/secret-provider.tsx"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,cAAc,IAAI,oBAAoB,EAC3C,KAAK,iBAAiB,EAEvB,MAAM,oBAAoB,CAAA;AAI3B;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,oBAAoB,GAAG,IAAI,GAAG,SAAS,GAChD,iBAAiB,GAAG,SAAS,CAO/B;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,GAAG,SAAS,GAChD,QAAQ,IAAI,oBAAoB,CAElC;AA2DD;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iEAAiE;IACjE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,oBAAoB,CAQjE;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gCAAgC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,oBAAoB,CAQ7D;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,oBAAoB,CAI7E;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;CAAG;AAEtC,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,oBAAoB,CAI7E;AAED,kDAAkD;AAClD,MAAM,MAAM,mBAAmB,GAC7B,SAAS,GAAG,OAAO,GAAG,gBAAgB,GAAG,gBAAgB,GAAG,oBAAoB,CAAA;AAElF,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,uBAAuB;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,kCAuCxD"}
|
package/dist/secret-provider.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.provisionerForSecretProvider = provisionerForSecretProvider;
|
|
4
|
+
exports.canProvisionSecrets = canProvisionSecrets;
|
|
3
5
|
exports.OpenBao = OpenBao;
|
|
4
6
|
exports.Vault = Vault;
|
|
5
7
|
exports.SealedSecrets = SealedSecrets;
|
|
@@ -8,7 +10,79 @@ exports.SecretProvider = SecretProvider;
|
|
|
8
10
|
const core_1 = require("@r8s/core");
|
|
9
11
|
const defaults_1 = require("@r8s/core/defaults");
|
|
10
12
|
const defaults_2 = require("@r8s/core/defaults");
|
|
11
|
-
const
|
|
13
|
+
const operator_vault_secrets_1 = require("@r8s/operator-vault-secrets");
|
|
14
|
+
/**
|
|
15
|
+
* Provisioner resolution — THE single identity-aware point in the platform.
|
|
16
|
+
* Everything downstream codes against the `provision()` capability; new
|
|
17
|
+
* backends plug in by carrying their own provisioner on the context value
|
|
18
|
+
* (or by being registered here — the orchestration point).
|
|
19
|
+
*/
|
|
20
|
+
function provisionerForSecretProvider(provider) {
|
|
21
|
+
if (!provider)
|
|
22
|
+
return undefined;
|
|
23
|
+
if (typeof provider.provision === 'function')
|
|
24
|
+
return provider.provision;
|
|
25
|
+
if (provider.backend === 'vault' || provider.backend === 'openbao') {
|
|
26
|
+
return (req) => builtinProvisionStaticSecret(provider, req);
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
/** Can this provider provision secrets? (capability check, not identity) */
|
|
31
|
+
function canProvisionSecrets(provider) {
|
|
32
|
+
return provisionerForSecretProvider(provider) !== undefined;
|
|
33
|
+
}
|
|
34
|
+
/** Built-in Vault/OpenBao emission (VaultStaticSecret / OpenBaoStaticSecret) */
|
|
35
|
+
function builtinProvisionStaticSecret(provider, req) {
|
|
36
|
+
// Two modes: an EXHAUSTIVE bundle (keys/templates given — destination
|
|
37
|
+
// carries exactly those keys, excludeRaw) or a RAW SYNC (empty keys and
|
|
38
|
+
// no templates — destination passes the whole store entry through,
|
|
39
|
+
// legacy Database/operator-contract shape).
|
|
40
|
+
const exhaustive = Object.keys(req.keys).length > 0 || req.templates !== undefined;
|
|
41
|
+
const spec = {
|
|
42
|
+
...(provider.backend === 'vault'
|
|
43
|
+
? { vaultAuthRef: req.authRef ?? provider.authRef }
|
|
44
|
+
: { openbaoAuthRef: req.authRef ?? provider.authRef }),
|
|
45
|
+
mount: req.mount ?? provider.mount,
|
|
46
|
+
type: 'kv-v2',
|
|
47
|
+
path: req.path,
|
|
48
|
+
refreshAfter: req.refreshAfter ?? provider.refreshAfter ?? (exhaustive ? '1h' : undefined),
|
|
49
|
+
...(req.restartTargets && req.restartTargets.length > 0
|
|
50
|
+
? { rolloutRestartTargets: req.restartTargets }
|
|
51
|
+
: {}),
|
|
52
|
+
destination: exhaustive
|
|
53
|
+
? {
|
|
54
|
+
create: true,
|
|
55
|
+
name: req.secretName ?? req.name,
|
|
56
|
+
overwrite: true,
|
|
57
|
+
transformation: {
|
|
58
|
+
excludeRaw: true,
|
|
59
|
+
templates: {
|
|
60
|
+
...Object.fromEntries(Object.entries(req.keys).map(([dest, src]) => [
|
|
61
|
+
dest,
|
|
62
|
+
{ text: `{{ .Secrets.${src} }}` },
|
|
63
|
+
])),
|
|
64
|
+
// Raw passthrough templates (literals, composed templates) win
|
|
65
|
+
// over key-mapped entries on collision
|
|
66
|
+
...Object.fromEntries(Object.entries(req.templates ?? {}).map(([dest, tpl]) => [dest, { text: tpl }])),
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
: { create: true, name: req.secretName ?? req.name },
|
|
71
|
+
};
|
|
72
|
+
return provider.backend === 'vault'
|
|
73
|
+
? (0, core_1.jsx)('VaultStaticSecret', {
|
|
74
|
+
apiVersion: 'secrets.hashicorp.com/v1beta1',
|
|
75
|
+
kind: 'VaultStaticSecret',
|
|
76
|
+
metadata: { name: req.name, namespace: req.namespace },
|
|
77
|
+
spec,
|
|
78
|
+
})
|
|
79
|
+
: (0, core_1.jsx)('OpenBaoStaticSecret', {
|
|
80
|
+
apiVersion: 'secrets.openbao.org/v1beta1',
|
|
81
|
+
kind: 'OpenBaoStaticSecret',
|
|
82
|
+
metadata: { name: req.name, namespace: req.namespace },
|
|
83
|
+
spec,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
12
86
|
function OpenBao(props) {
|
|
13
87
|
return {
|
|
14
88
|
backend: 'openbao',
|
|
@@ -48,7 +122,7 @@ function ManualSecrets(props) {
|
|
|
48
122
|
*
|
|
49
123
|
* export default (
|
|
50
124
|
* <SecretProvider provider="openbao">
|
|
51
|
-
* <Database name="app-db" />
|
|
125
|
+
* <Database backup={false} name="app-db" />
|
|
52
126
|
* </SecretProvider>
|
|
53
127
|
* )
|
|
54
128
|
*
|
|
@@ -57,7 +131,7 @@ function ManualSecrets(props) {
|
|
|
57
131
|
*
|
|
58
132
|
* export default (
|
|
59
133
|
* <SecretProvider provider={<OpenBao mount="secret" path="infra" authRef="custom-auth" />}>
|
|
60
|
-
* <Database name="app-db" />
|
|
134
|
+
* <Database backup={false} name="app-db" />
|
|
61
135
|
* </SecretProvider>
|
|
62
136
|
* )
|
|
63
137
|
*
|
|
@@ -66,7 +140,7 @@ function ManualSecrets(props) {
|
|
|
66
140
|
*
|
|
67
141
|
* export default (
|
|
68
142
|
* <SecretProvider provider={<Vault mount="kv" path="apps" />}>
|
|
69
|
-
* <Database name="app-db" />
|
|
143
|
+
* <Database backup={false} name="app-db" />
|
|
70
144
|
* </SecretProvider>
|
|
71
145
|
* )
|
|
72
146
|
*
|
|
@@ -75,7 +149,7 @@ function ManualSecrets(props) {
|
|
|
75
149
|
*
|
|
76
150
|
* export default (
|
|
77
151
|
* <SecretProvider provider="sealed-secrets">
|
|
78
|
-
* <Database name="app-db" />
|
|
152
|
+
* <Database backup={false} name="app-db" />
|
|
79
153
|
* </SecretProvider>
|
|
80
154
|
* )
|
|
81
155
|
*/
|
|
@@ -115,10 +189,7 @@ function SecretProvider(props) {
|
|
|
115
189
|
const resources = [];
|
|
116
190
|
// Declare VSO for vault/openbao
|
|
117
191
|
if (config.backend === 'vault' || config.backend === 'openbao') {
|
|
118
|
-
|
|
119
|
-
if (!hasVSO) {
|
|
120
|
-
resources.push((0, core_1.declareOperator)(crds_1.operators['vault-secrets-operator']()));
|
|
121
|
-
}
|
|
192
|
+
resources.push(...(0, operator_vault_secrets_1.declareIfMissing)(sharedOperators));
|
|
122
193
|
}
|
|
123
194
|
return (0, core_1.jsx)(core_1.Fragment, {
|
|
124
195
|
children: [...resources, (0, core_1.jsx)(defaults_1.SecretContext.Provider, { value: config, children })],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-provider.js","sourceRoot":"","sources":["../src/secret-provider.tsx"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"secret-provider.js","sourceRoot":"","sources":["../src/secret-provider.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export interface StaticSecretProps {
|
|
2
|
+
/**
|
|
3
|
+
* Resource name (also the default for the provisioned Kubernetes
|
|
4
|
+
* Secret's name — override with secretName).
|
|
5
|
+
*/
|
|
6
|
+
name: string;
|
|
7
|
+
/** Kubernetes namespace (inherited from Platform context when omitted) */
|
|
8
|
+
namespace?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Vault/OpenBao KV v2 path holding the source keys, e.g. 'outline/app'.
|
|
11
|
+
* Build it from the provider's base path when composing:
|
|
12
|
+
* `${secretProvider.path}/myapp/credentials`.
|
|
13
|
+
*/
|
|
14
|
+
path: string;
|
|
15
|
+
/**
|
|
16
|
+
* Destination→source key mapping. The destination carries env-case
|
|
17
|
+
* names consumed by the app; the vault/store keys stay snake_case:
|
|
18
|
+
* keys={{ SECRET_KEY: 'secret_key', UTILS_SECRET: 'utils_secret' }}
|
|
19
|
+
* A string array is shorthand for identity mappings ({ KEY: 'KEY' }).
|
|
20
|
+
*
|
|
21
|
+
* Rendered as destination.transformation.templates + excludeRaw —
|
|
22
|
+
* the destination holds ONLY these keys.
|
|
23
|
+
*/
|
|
24
|
+
keys: Record<string, string> | string[];
|
|
25
|
+
/**
|
|
26
|
+
* Raw Go-template passthrough merged AFTER the keys mapping — for
|
|
27
|
+
* advanced bundles needing literals or composed templates
|
|
28
|
+
* (e.g. `AWS_ENDPOINTS: 'https://s3.berget.cloud'`). Passthrough wins
|
|
29
|
+
* over key-mapped entries on collision.
|
|
30
|
+
*/
|
|
31
|
+
templates?: Record<string, string>;
|
|
32
|
+
/** Provisioned Kubernetes Secret name (defaults to name) */
|
|
33
|
+
secretName?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Re-sync interval rendered as refreshAfter. Defaults to the provider's
|
|
36
|
+
* refreshAfter, else '1h'.
|
|
37
|
+
*/
|
|
38
|
+
refreshAfter?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Workloads restarted after rotation (rolloutRestartTargets). Pass the
|
|
41
|
+
* workloads consuming these values so a rotation actually takes effect —
|
|
42
|
+
* pod env vars do not update in place.
|
|
43
|
+
*
|
|
44
|
+
* Deliberately has no default: some consumers read the Secret at use
|
|
45
|
+
* time (e.g. CNPG barman backup credentials are read when a backup
|
|
46
|
+
* runs, and the Cluster CRD is not a supported restart target), so an
|
|
47
|
+
* automatic restart is wrong for them.
|
|
48
|
+
*/
|
|
49
|
+
restart?: {
|
|
50
|
+
kind?: string;
|
|
51
|
+
name: string;
|
|
52
|
+
apiVersion?: string;
|
|
53
|
+
}[];
|
|
54
|
+
/**
|
|
55
|
+
* Auth reference override (defaults to the provider's authRef).
|
|
56
|
+
* Rendered as vaultAuthRef / openbaoAuthRef per backend.
|
|
57
|
+
*/
|
|
58
|
+
authRef?: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* StaticSecret — provision a Kubernetes Secret from Vault/OpenBao,
|
|
62
|
+
* with rotation semantics that actually reach the workloads.
|
|
63
|
+
*
|
|
64
|
+
* Wraps VaultStaticSecret / OpenBaoStaticSecret with the platform's
|
|
65
|
+
* conventions baked in:
|
|
66
|
+
* - snake_case source keys templated to env-case destination keys
|
|
67
|
+
* (excludeRaw: only the declared keys land in the Secret)
|
|
68
|
+
* - destination create + overwrite (idempotent sync)
|
|
69
|
+
* - hourly refresh by default, inheriting the provider's refreshAfter
|
|
70
|
+
* - explicit, per-bundle restart semantics (app creds restart pods;
|
|
71
|
+
* backup creds read at use time never restart)
|
|
72
|
+
*
|
|
73
|
+
* Requires an active secrets backend (openbao or vault) — passive
|
|
74
|
+
* backends (manual-secrets, sealed-secrets) cannot provision: reference
|
|
75
|
+
* a pre-created Secret instead.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* import { Platform, StaticSecret } from '@r8s/recipes'
|
|
79
|
+
*
|
|
80
|
+
* export default (
|
|
81
|
+
* <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>
|
|
82
|
+
* <StaticSecret
|
|
83
|
+
* name="myapp-secrets"
|
|
84
|
+
* path="myapp/app"
|
|
85
|
+
* keys={{
|
|
86
|
+
* SECRET_KEY: 'secret_key',
|
|
87
|
+
* OIDC_CLIENT_ID: 'oidc_client_id',
|
|
88
|
+
* OIDC_CLIENT_SECRET: 'oidc_client_secret',
|
|
89
|
+
* }}
|
|
90
|
+
* restart={[{ kind: 'Deployment', name: 'myapp' }]}
|
|
91
|
+
* />
|
|
92
|
+
* </Platform>
|
|
93
|
+
* )
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* import { Platform, StaticSecret } from '@r8s/recipes'
|
|
97
|
+
*
|
|
98
|
+
* // backup creds: read at use time — never restart workloads
|
|
99
|
+
* export default (
|
|
100
|
+
* <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>
|
|
101
|
+
* <StaticSecret name="myapp-backup-creds" path="rustfs/myapp"
|
|
102
|
+
* keys={['accesskey', 'secretkey']} />
|
|
103
|
+
* </Platform>
|
|
104
|
+
* )
|
|
105
|
+
*/
|
|
106
|
+
export declare function StaticSecret(props: StaticSecretProps): import("@r8s/core").r8sElement;
|
|
107
|
+
//# sourceMappingURL=static-secret.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static-secret.d.ts","sourceRoot":"","sources":["../src/static-secret.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;;;OAQG;IACH,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,CAAA;IACvC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAClC,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAChE;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,kCAoDpD"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StaticSecret = StaticSecret;
|
|
4
|
+
const core_1 = require("@r8s/core");
|
|
5
|
+
const defaults_1 = require("@r8s/core/defaults");
|
|
6
|
+
const secret_provider_1 = require("./secret-provider");
|
|
7
|
+
/**
|
|
8
|
+
* StaticSecret — provision a Kubernetes Secret from Vault/OpenBao,
|
|
9
|
+
* with rotation semantics that actually reach the workloads.
|
|
10
|
+
*
|
|
11
|
+
* Wraps VaultStaticSecret / OpenBaoStaticSecret with the platform's
|
|
12
|
+
* conventions baked in:
|
|
13
|
+
* - snake_case source keys templated to env-case destination keys
|
|
14
|
+
* (excludeRaw: only the declared keys land in the Secret)
|
|
15
|
+
* - destination create + overwrite (idempotent sync)
|
|
16
|
+
* - hourly refresh by default, inheriting the provider's refreshAfter
|
|
17
|
+
* - explicit, per-bundle restart semantics (app creds restart pods;
|
|
18
|
+
* backup creds read at use time never restart)
|
|
19
|
+
*
|
|
20
|
+
* Requires an active secrets backend (openbao or vault) — passive
|
|
21
|
+
* backends (manual-secrets, sealed-secrets) cannot provision: reference
|
|
22
|
+
* a pre-created Secret instead.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* import { Platform, StaticSecret } from '@r8s/recipes'
|
|
26
|
+
*
|
|
27
|
+
* export default (
|
|
28
|
+
* <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>
|
|
29
|
+
* <StaticSecret
|
|
30
|
+
* name="myapp-secrets"
|
|
31
|
+
* path="myapp/app"
|
|
32
|
+
* keys={{
|
|
33
|
+
* SECRET_KEY: 'secret_key',
|
|
34
|
+
* OIDC_CLIENT_ID: 'oidc_client_id',
|
|
35
|
+
* OIDC_CLIENT_SECRET: 'oidc_client_secret',
|
|
36
|
+
* }}
|
|
37
|
+
* restart={[{ kind: 'Deployment', name: 'myapp' }]}
|
|
38
|
+
* />
|
|
39
|
+
* </Platform>
|
|
40
|
+
* )
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* import { Platform, StaticSecret } from '@r8s/recipes'
|
|
44
|
+
*
|
|
45
|
+
* // backup creds: read at use time — never restart workloads
|
|
46
|
+
* export default (
|
|
47
|
+
* <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>
|
|
48
|
+
* <StaticSecret name="myapp-backup-creds" path="rustfs/myapp"
|
|
49
|
+
* keys={['accesskey', 'secretkey']} />
|
|
50
|
+
* </Platform>
|
|
51
|
+
* )
|
|
52
|
+
*/
|
|
53
|
+
function StaticSecret(props) {
|
|
54
|
+
const { name, namespace: namespaceProp, path, keys, templates, secretName, refreshAfter, restart, authRef, } = props;
|
|
55
|
+
const secretProvider = (0, core_1.useContext)(defaults_1.SecretContext);
|
|
56
|
+
const namespace = (0, defaults_1.useNamespace)(namespaceProp);
|
|
57
|
+
const provisioner = (0, secret_provider_1.provisionerForSecretProvider)(secretProvider);
|
|
58
|
+
if (!provisioner) {
|
|
59
|
+
throw new Error(`StaticSecret "${name}" requires a provisioning secrets backend (openbao, vault,\n` +
|
|
60
|
+
`or any provider carrying a provision() hook).\n` +
|
|
61
|
+
`\n` +
|
|
62
|
+
`Fix: configure a backend on the Platform:\n` +
|
|
63
|
+
` <Platform secrets={{ backend: 'openbao', mount: 'kv', path: 'apps' }}>\n` +
|
|
64
|
+
` <StaticSecret name="${name}" path="${path}" keys={...} />\n` +
|
|
65
|
+
` </Platform>\n` +
|
|
66
|
+
`\n` +
|
|
67
|
+
`On passive backends (manual-secrets, sealed-secrets, kubernetes), create the Secret\n` +
|
|
68
|
+
`out of band and reference it directly instead of rendering this component.`);
|
|
69
|
+
}
|
|
70
|
+
const mapping = Object.fromEntries((Array.isArray(keys) ? keys.map((k) => [k, k]) : Object.entries(keys)));
|
|
71
|
+
// Default missing fields (consistent with Database/WebService restart
|
|
72
|
+
// target conventions)
|
|
73
|
+
const restartTargets = restart?.map((t) => ({ kind: 'Deployment', ...t }));
|
|
74
|
+
const el = provisioner({
|
|
75
|
+
name,
|
|
76
|
+
namespace,
|
|
77
|
+
path,
|
|
78
|
+
keys: mapping,
|
|
79
|
+
templates,
|
|
80
|
+
secretName,
|
|
81
|
+
authRef,
|
|
82
|
+
refreshAfter,
|
|
83
|
+
restartTargets,
|
|
84
|
+
});
|
|
85
|
+
return Array.isArray(el) ? (0, core_1.jsx)(core_1.Fragment, { children: el }) : el;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=static-secret.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static-secret.js","sourceRoot":"","sources":["../src/static-secret.tsx"],"names":[],"mappings":";;AA0GA,oCAoDC;AA9JD,oCAAqD;AACrD,iDAAgE;AAChE,uDAAgE;AA0DhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,SAAgB,YAAY,CAAC,KAAwB;IACnD,MAAM,EACJ,IAAI,EACJ,SAAS,EAAE,aAAa,EACxB,IAAI,EACJ,IAAI,EACJ,SAAS,EACT,UAAU,EACV,YAAY,EACZ,OAAO,EACP,OAAO,GACR,GAAG,KAAK,CAAA;IAET,MAAM,cAAc,GAAG,IAAA,iBAAU,EAAC,wBAAa,CAAC,CAAA;IAChD,MAAM,SAAS,GAAG,IAAA,uBAAY,EAAC,aAAa,CAAC,CAAA;IAC7C,MAAM,WAAW,GAAG,IAAA,8CAA4B,EAAC,cAAc,CAAC,CAAA;IAEhE,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,iBAAiB,IAAI,8DAA8D;YACjF,iDAAiD;YACjD,IAAI;YACJ,6CAA6C;YAC7C,4EAA4E;YAC5E,2BAA2B,IAAI,WAAW,IAAI,mBAAmB;YACjE,iBAAiB;YACjB,IAAI;YACJ,uFAAuF;YACvF,4EAA4E,CAC/E,CAAA;IACH,CAAC;IAED,MAAM,OAAO,GAA2B,MAAM,CAAC,WAAW,CACxD,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAuB,CAC7F,CAAA;IAED,sEAAsE;IACtE,sBAAsB;IACtB,MAAM,cAAc,GAAG,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;IAE1E,MAAM,EAAE,GAAG,WAAW,CAAC;QACrB,IAAI;QACJ,SAAS;QACT,IAAI;QACJ,IAAI,EAAE,OAAO;QACb,SAAS;QACT,UAAU;QACV,OAAO;QACP,YAAY;QACZ,cAAc;KACf,CAAC,CAAA;IACF,OAAO,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAA,UAAG,EAAC,eAAQ,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAE,EAA6B,CAAA;AAC7F,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Operator } from '@r8s/k8s-types';
|
|
2
|
+
/**
|
|
3
|
+
* Read the operators the surrounding Platform already provides — the
|
|
4
|
+
* dedupe-seed shared with every consumer's `declareIfMissing`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useOperators(): Operator[];
|
|
7
|
+
//# sourceMappingURL=use-operators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-operators.d.ts","sourceRoot":"","sources":["../src/use-operators.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAE9C;;;GAGG;AACH,wBAAgB,YAAY,IAAI,QAAQ,EAAE,CAEzC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useOperators = useOperators;
|
|
4
|
+
const core_1 = require("@r8s/core");
|
|
5
|
+
const defaults_1 = require("@r8s/core/defaults");
|
|
6
|
+
/**
|
|
7
|
+
* Read the operators the surrounding Platform already provides — the
|
|
8
|
+
* dedupe-seed shared with every consumer's `declareIfMissing`.
|
|
9
|
+
*/
|
|
10
|
+
function useOperators() {
|
|
11
|
+
return (0, core_1.useContext)(defaults_1.OperatorContext);
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=use-operators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-operators.js","sourceRoot":"","sources":["../src/use-operators.ts"],"names":[],"mappings":";;AAQA,oCAEC;AAVD,oCAAsC;AACtC,iDAAoD;AAGpD;;;GAGG;AACH,SAAgB,YAAY;IAC1B,OAAO,IAAA,iBAAU,EAAC,0BAAe,CAAC,CAAA;AACpC,CAAC"}
|
package/dist/web-service.d.ts
CHANGED
|
@@ -100,7 +100,11 @@ export interface WebServiceProps {
|
|
|
100
100
|
tolerations?: Record<string, unknown>[];
|
|
101
101
|
/** Pod topologySpreadConstraints (spread replicas across nodes) */
|
|
102
102
|
topologySpreadConstraints?: Record<string, unknown>[];
|
|
103
|
-
/**
|
|
103
|
+
/**
|
|
104
|
+
* Deployment update strategy ('Recreate' for RWO-volume/migration apps).
|
|
105
|
+
* Default: 'Recreate' when the pod mounts a PVC volume and replicas=1
|
|
106
|
+
* (RollingUpdate would deadlock on the single-attach volume).
|
|
107
|
+
*/
|
|
104
108
|
strategy?: 'Recreate' | 'RollingUpdate' | {
|
|
105
109
|
type: string;
|
|
106
110
|
rollingUpdate?: {
|
|
@@ -130,7 +134,11 @@ export interface WebServiceProps {
|
|
|
130
134
|
} & Record<string, unknown>)[];
|
|
131
135
|
/** Container lifecycle hooks (e.g. { preStop: { exec: { command: [...] } } }) */
|
|
132
136
|
lifecycle?: Record<string, unknown>;
|
|
133
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* Image pull policy. Default depends on the tag: floating tags
|
|
139
|
+
* (latest/main/master/dev/edge/head/nightly/canary) get 'Always', pinned
|
|
140
|
+
* tags and digests get 'IfNotPresent' (immutable — pointless to re-pull).
|
|
141
|
+
*/
|
|
134
142
|
imagePullPolicy?: 'Always' | 'IfNotPresent' | 'Never';
|
|
135
143
|
/** Names of Secrets used to pull private registry images */
|
|
136
144
|
imagePullSecrets?: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-service.d.ts","sourceRoot":"","sources":["../src/web-service.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"web-service.d.ts","sourceRoot":"","sources":["../src/web-service.tsx"],"names":[],"mappings":"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"}
|
package/dist/web-service.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.WebService = WebService;
|
|
4
4
|
const core_1 = require("@r8s/core");
|
|
5
5
|
const defaults_1 = require("@r8s/core/defaults");
|
|
6
|
-
const
|
|
6
|
+
const operator_vault_secrets_1 = require("@r8s/operator-vault-secrets");
|
|
7
7
|
/**
|
|
8
8
|
* Simple web service (backend or frontend).
|
|
9
9
|
*
|
|
@@ -42,7 +42,7 @@ const operators_1 = require("./operators");
|
|
|
42
42
|
* )
|
|
43
43
|
*/
|
|
44
44
|
function WebService(props) {
|
|
45
|
-
const { name, namespace
|
|
45
|
+
const { name, namespace: namespaceProp, image, port = 3000, replicas = 2, probes, env = {}, secrets = {}, vault = {}, command, args, rawEnv = [], resources, securityContext, podSecurityContext, tolerations, topologySpreadConstraints, strategy, volumes, volumeMounts, initContainers, lifecycle, imagePullPolicy, imagePullSecrets, } = props;
|
|
46
46
|
const buildProbe = (spec, fallbackPath) => {
|
|
47
47
|
if (spec === null)
|
|
48
48
|
return undefined;
|
|
@@ -51,18 +51,57 @@ function WebService(props) {
|
|
|
51
51
|
const initialDelaySeconds = spec.initialDelaySeconds ?? 10;
|
|
52
52
|
const periodSeconds = spec.periodSeconds ?? 10;
|
|
53
53
|
if (spec.tcp) {
|
|
54
|
-
return {
|
|
54
|
+
return {
|
|
55
|
+
tcpSocket: { port: spec.port ?? port },
|
|
56
|
+
initialDelaySeconds,
|
|
57
|
+
periodSeconds,
|
|
58
|
+
// !== undefined (not truthy): an explicit 0 must surface as-is so
|
|
59
|
+
// Kubernetes validation fails loudly instead of silently reverting
|
|
60
|
+
// to the default
|
|
61
|
+
...(spec.failureThreshold !== undefined && { failureThreshold: spec.failureThreshold }),
|
|
62
|
+
};
|
|
55
63
|
}
|
|
56
64
|
return {
|
|
57
65
|
httpGet: { path: spec.path ?? fallbackPath, port: spec.port ?? port },
|
|
58
66
|
initialDelaySeconds,
|
|
59
67
|
periodSeconds,
|
|
60
|
-
...(spec.failureThreshold && { failureThreshold: spec.failureThreshold }),
|
|
68
|
+
...(spec.failureThreshold !== undefined && { failureThreshold: spec.failureThreshold }),
|
|
61
69
|
};
|
|
62
70
|
};
|
|
63
71
|
const livenessProbe = buildProbe(probes?.liveness, '/health');
|
|
64
72
|
const readinessProbe = buildProbe(probes?.readiness, '/ready');
|
|
65
73
|
const startupProbe = buildProbe(probes?.startup ?? null, '/health');
|
|
74
|
+
// --- Safe defaults derived from the image and storage shape -------------
|
|
75
|
+
// imagePullPolicy: a floating tag must be re-pulled (Always); a pinned tag
|
|
76
|
+
// (or digest) is immutable, so IfNotPresent avoids pointless pulls and
|
|
77
|
+
// makes deploys robust when a registry is slow/unreachable. Explicit prop
|
|
78
|
+
// always wins. Floating set covers the conventional mutables.
|
|
79
|
+
const imageLeaf = image.slice(image.lastIndexOf('/') + 1);
|
|
80
|
+
const imageTag = imageLeaf.includes('@')
|
|
81
|
+
? imageLeaf.slice(imageLeaf.indexOf('@') + 1)
|
|
82
|
+
: imageLeaf.includes(':')
|
|
83
|
+
? imageLeaf.slice(imageLeaf.indexOf(':') + 1)
|
|
84
|
+
: 'latest';
|
|
85
|
+
const FLOATING_TAGS = new Set([
|
|
86
|
+
'latest',
|
|
87
|
+
'main',
|
|
88
|
+
'master',
|
|
89
|
+
'dev',
|
|
90
|
+
'edge',
|
|
91
|
+
'head',
|
|
92
|
+
'nightly',
|
|
93
|
+
'canary',
|
|
94
|
+
]);
|
|
95
|
+
const pullPolicy = imagePullPolicy ??
|
|
96
|
+
(!imageLeaf.includes('@') && FLOATING_TAGS.has(imageTag.toLowerCase())
|
|
97
|
+
? 'Always'
|
|
98
|
+
: 'IfNotPresent');
|
|
99
|
+
// strategy: RollingUpdate + a ReadWriteOnce PVC + replicas=1 deadlocks
|
|
100
|
+
// (the new pod cannot attach the volume until the old releases it), and
|
|
101
|
+
// in-place DB-migration apps (n8n, outline) must not run two versions at
|
|
102
|
+
// once. Recreate is always safe at replicas=1 — there is already no HA.
|
|
103
|
+
const hasPvcVolume = (volumes ?? []).some((v) => 'persistentVolumeClaim' in v);
|
|
104
|
+
const derivedStrategy = strategy ?? (hasPvcVolume && replicas === 1 ? 'Recreate' : undefined);
|
|
66
105
|
const envVars = [];
|
|
67
106
|
const vaultResources = [];
|
|
68
107
|
// Secrets from Kubernetes Secrets — pushed before plain env vars so
|
|
@@ -89,6 +128,7 @@ function WebService(props) {
|
|
|
89
128
|
envVars.push({ name: key, value });
|
|
90
129
|
}
|
|
91
130
|
// Vault secrets — create VaultStaticSecret objects
|
|
131
|
+
const namespace = (0, defaults_1.useNamespace)(namespaceProp);
|
|
92
132
|
const platformSecrets = (0, core_1.useContext)(defaults_1.SecretContext);
|
|
93
133
|
for (const [envName, ref] of Object.entries(vault)) {
|
|
94
134
|
const secretName = `${name}-${envName.toLowerCase().replace(/_/g, '-')}-vault`;
|
|
@@ -155,10 +195,7 @@ function WebService(props) {
|
|
|
155
195
|
// Declare Vault Secrets Operator if vault secrets are used
|
|
156
196
|
if (Object.keys(vault).length > 0) {
|
|
157
197
|
const sharedOperators = (0, core_1.useContext)(defaults_1.OperatorContext);
|
|
158
|
-
|
|
159
|
-
if (!hasVSO) {
|
|
160
|
-
vaultResources.push((0, core_1.declareOperator)((0, operators_1.vaultSecretsOperator)()));
|
|
161
|
-
}
|
|
198
|
+
vaultResources.push(...(0, operator_vault_secrets_1.declareIfMissing)(sharedOperators));
|
|
162
199
|
}
|
|
163
200
|
const deployment = {
|
|
164
201
|
apiVersion: 'apps/v1',
|
|
@@ -166,8 +203,8 @@ function WebService(props) {
|
|
|
166
203
|
metadata: { name, namespace, labels: { app: name } },
|
|
167
204
|
spec: {
|
|
168
205
|
replicas,
|
|
169
|
-
...(
|
|
170
|
-
strategy: typeof
|
|
206
|
+
...(derivedStrategy && {
|
|
207
|
+
strategy: typeof derivedStrategy === 'string' ? { type: derivedStrategy } : derivedStrategy,
|
|
171
208
|
}),
|
|
172
209
|
selector: { matchLabels: { app: name } },
|
|
173
210
|
template: {
|
|
@@ -187,7 +224,7 @@ function WebService(props) {
|
|
|
187
224
|
{
|
|
188
225
|
name: 'app',
|
|
189
226
|
image,
|
|
190
|
-
imagePullPolicy,
|
|
227
|
+
imagePullPolicy: pullPolicy,
|
|
191
228
|
...(command && { command }),
|
|
192
229
|
...(args && { args }),
|
|
193
230
|
ports: [{ containerPort: port }],
|
|
@@ -215,11 +252,14 @@ function WebService(props) {
|
|
|
215
252
|
// Expose the app port directly (so consumers can reference it) while
|
|
216
253
|
// keeping 80 as a stable backward-compatible alias: existing
|
|
217
254
|
// Endpoints/default servicePort=80 users keep routing during
|
|
218
|
-
// migration to explicit servicePort
|
|
255
|
+
// migration to explicit servicePort.
|
|
256
|
+
// Port names are REQUIRED whenever a Service has more than one port —
|
|
257
|
+
// without them the API server rejects the resource (caught by Flux
|
|
258
|
+
// dry-run as "spec.ports[i].name: Required value").
|
|
219
259
|
ports: [
|
|
220
|
-
{ port, targetPort: port },
|
|
260
|
+
{ name: 'http', port, targetPort: port },
|
|
221
261
|
// eslint-disable-next-line -- 80 alias for legacy servicePort=80 consumers
|
|
222
|
-
...(port !== 80 ? [{ port: 80, targetPort: port }] : []),
|
|
262
|
+
...(port !== 80 ? [{ name: 'http-80', port: 80, targetPort: port }] : []),
|
|
223
263
|
],
|
|
224
264
|
},
|
|
225
265
|
};
|