underpost 3.2.80 → 3.2.90
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/CHANGELOG.md +182 -1
- package/CLI-HELP.md +37 -16
- package/README.md +2 -2
- package/bin/deploy.js +18 -16
- package/docker-compose.yml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/playwright/deployment.yaml +1 -1
- package/manifests/mongodb/kustomization.yaml +4 -1
- package/manifests/mongodb/statefulset.yaml +4 -0
- package/manifests/mongodb/storage-class.yaml +9 -2
- package/package.json +17 -17
- package/scripts/nat-iptables.sh +10 -4
- package/scripts/test-monitor.sh +4 -3
- package/src/cli/cluster.js +740 -55
- package/src/cli/db.js +2 -2
- package/src/cli/deploy.js +1679 -174
- package/src/cli/docker-compose.js +19 -178
- package/src/cli/image.js +15 -6
- package/src/cli/index.js +124 -35
- package/src/cli/ipfs.js +82 -11
- package/src/cli/monitor.js +1 -1
- package/src/cli/repository.js +1 -1
- package/src/cli/run.js +2161 -420
- package/src/cli/secrets.js +969 -0
- package/src/cli/ssh.js +8 -28
- package/src/client-builder/client-build.js +94 -11
- package/src/client-builder/ssr.js +27 -73
- package/src/db/mongo/MongoBootstrap.js +295 -54
- package/src/db/mongo/MongooseDB.js +47 -32
- package/src/index.js +1 -1
- package/src/server/conf.js +1208 -70
- package/src/server/cri.js +70 -0
- package/src/server/underpost-gateway.js +1073 -0
- package/src/server/underpost-ingress.js +364 -0
- package/test/cluster-instances.test.js +435 -0
- package/test/deploy-node-placement.test.js +45 -0
- package/test/instance-traffic-plan.test.js +710 -0
- package/test/sops-secret-store.test.js +612 -0
- package/test/underpost-gateway.test.js +469 -0
- package/test/underpost-ingress.test.js +253 -0
package/src/cli/ipfs.js
CHANGED
|
@@ -6,11 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
import { loggerFactory } from '../server/logger.js';
|
|
8
8
|
import { shellExec } from '../server/process.js';
|
|
9
|
+
import { resolveReplicaCount } from '../server/conf.js';
|
|
9
10
|
import fs from 'fs-extra';
|
|
10
11
|
import Underpost from '../index.js';
|
|
11
12
|
|
|
12
13
|
const logger = loggerFactory(import.meta);
|
|
13
14
|
|
|
15
|
+
const IPFS_DEFAULT_REPLICA_COUNT = 3;
|
|
16
|
+
|
|
14
17
|
/**
|
|
15
18
|
* @class UnderpostIPFS
|
|
16
19
|
* @description Manages deployment of an ipfs-cluster StatefulSet on Kubernetes.
|
|
@@ -25,8 +28,12 @@ class UnderpostIPFS {
|
|
|
25
28
|
* @description Resolves the IPFS cluster credentials from engine-private/ if they
|
|
26
29
|
* already exist, otherwise generates new ones (hex cluster secret + peer identity
|
|
27
30
|
* via ipfs-cluster-service init) and persists them with mode 0o600.
|
|
31
|
+
* Idempotent and self-healing: an existing pair is reused untouched, and a missing or
|
|
32
|
+
* unreadable one is regenerated. Both files are always rewritten together — the peer id and
|
|
33
|
+
* the private key are two halves of one identity, so reusing one with a freshly minted other
|
|
34
|
+
* would advertise a peer id that does not match the key.
|
|
28
35
|
* @param {string} privateDir - Absolute path to the engine-private directory.
|
|
29
|
-
* @returns {{ CLUSTER_SECRET: string, IDENTITY_JSON: { id: string, private_key: string } }}
|
|
36
|
+
* @returns {{ CLUSTER_SECRET: string, IDENTITY_JSON: { id: string, private_key: string }, generated: boolean }}
|
|
30
37
|
* @memberof UnderpostIPFS
|
|
31
38
|
*/
|
|
32
39
|
resolveCredentials(privateDir) {
|
|
@@ -34,11 +41,17 @@ class UnderpostIPFS {
|
|
|
34
41
|
const identityPath = `${privateDir}/ipfs-cluster-identity.json`;
|
|
35
42
|
|
|
36
43
|
if (fs.existsSync(secretPath) && fs.existsSync(identityPath)) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
try {
|
|
45
|
+
const CLUSTER_SECRET = fs.readFileSync(secretPath, 'utf8').trim();
|
|
46
|
+
const IDENTITY_JSON = JSON.parse(fs.readFileSync(identityPath, 'utf8'));
|
|
47
|
+
if (CLUSTER_SECRET && IDENTITY_JSON?.id && IDENTITY_JSON?.private_key) {
|
|
48
|
+
logger.info('Reusing existing IPFS cluster credentials from engine-private/');
|
|
49
|
+
return { CLUSTER_SECRET, IDENTITY_JSON, generated: false };
|
|
50
|
+
}
|
|
51
|
+
logger.warn('Existing IPFS cluster credentials are incomplete; regenerating');
|
|
52
|
+
} catch (error) {
|
|
53
|
+
logger.warn(`Existing IPFS cluster credentials are unreadable (${error.message}); regenerating`);
|
|
54
|
+
}
|
|
42
55
|
}
|
|
43
56
|
|
|
44
57
|
logger.info('Generating new IPFS cluster credentials and persisting to engine-private/');
|
|
@@ -61,7 +74,52 @@ class UnderpostIPFS {
|
|
|
61
74
|
|
|
62
75
|
logger.info(`IPFS cluster credentials saved (peer ID: ${IDENTITY_JSON.id})`);
|
|
63
76
|
|
|
64
|
-
return { CLUSTER_SECRET, IDENTITY_JSON };
|
|
77
|
+
return { CLUSTER_SECRET, IDENTITY_JSON, generated: true };
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @method storeCredentials
|
|
82
|
+
* @description Encrypts the current IPFS credentials into the SOPS store, replacing whatever
|
|
83
|
+
* is there. Called after a regeneration so the encrypted Secret cannot drift from the peer id
|
|
84
|
+
* the `env-config` ConfigMap advertises — the id lives only in the local identity file, so a
|
|
85
|
+
* stale manifest would pair someone else's private key with the new id and the cluster would
|
|
86
|
+
* never form.
|
|
87
|
+
* @param {{ CLUSTER_SECRET: string, IDENTITY_JSON: { private_key: string } }} credentials
|
|
88
|
+
* @param {object} options
|
|
89
|
+
* @param {string} options.namespace - Kubernetes namespace.
|
|
90
|
+
* @memberof UnderpostIPFS
|
|
91
|
+
*/
|
|
92
|
+
storeCredentials({ CLUSTER_SECRET, IDENTITY_JSON }, options) {
|
|
93
|
+
const stageDir = '/dev/shm/underpost-secrets';
|
|
94
|
+
const stagePath = `${stageDir}/ipfs-cluster-secret.yaml`;
|
|
95
|
+
fs.ensureDirSync(stageDir);
|
|
96
|
+
fs.chmodSync(stageDir, 0o700);
|
|
97
|
+
try {
|
|
98
|
+
fs.outputFileSync(
|
|
99
|
+
stagePath,
|
|
100
|
+
[
|
|
101
|
+
'apiVersion: v1',
|
|
102
|
+
'kind: Secret',
|
|
103
|
+
'metadata:',
|
|
104
|
+
' name: ipfs-cluster-secret',
|
|
105
|
+
` namespace: ${options.namespace}`,
|
|
106
|
+
' labels:',
|
|
107
|
+
' app.kubernetes.io/managed-by: underpost',
|
|
108
|
+
'type: Opaque',
|
|
109
|
+
'stringData:',
|
|
110
|
+
` cluster-secret: '${CLUSTER_SECRET.replace(/'/g, "''")}'`,
|
|
111
|
+
` bootstrap-peer-priv-key: '${IDENTITY_JSON.private_key.replace(/'/g, "''")}'`,
|
|
112
|
+
'',
|
|
113
|
+
].join('\n'),
|
|
114
|
+
'utf8',
|
|
115
|
+
);
|
|
116
|
+
fs.chmodSync(stagePath, 0o600);
|
|
117
|
+
// encrypt() stages, validates, moves into place, and shreds the plaintext source.
|
|
118
|
+
Underpost.secret.sops.encrypt(stagePath, options.namespace, { force: true });
|
|
119
|
+
logger.info('Re-encrypted regenerated IPFS credentials into the SOPS store');
|
|
120
|
+
} finally {
|
|
121
|
+
fs.removeSync(stageDir);
|
|
122
|
+
}
|
|
65
123
|
},
|
|
66
124
|
|
|
67
125
|
/**
|
|
@@ -100,12 +158,18 @@ class UnderpostIPFS {
|
|
|
100
158
|
applySecrets({ CLUSTER_SECRET, IDENTITY_JSON }, options) {
|
|
101
159
|
logger.info('Applying IPFS cluster Kubernetes Secret and env ConfigMap');
|
|
102
160
|
|
|
103
|
-
|
|
104
|
-
|
|
161
|
+
// Encrypted store first, origin generate/read logic only when no manifest is stored.
|
|
162
|
+
// `--from-literal` places the cluster secret and the peer private key in the command
|
|
163
|
+
// string, so the seed path is kept out of the command log; the encrypted path never
|
|
164
|
+
// exposes them at all.
|
|
165
|
+
if (!Underpost.secret.sops.applyIfPresent('ipfs-cluster-secret', options.namespace))
|
|
166
|
+
shellExec(
|
|
167
|
+
`kubectl create secret generic ipfs-cluster-secret \
|
|
105
168
|
--from-literal=cluster-secret=${CLUSTER_SECRET} \
|
|
106
169
|
--from-literal=bootstrap-peer-priv-key=${IDENTITY_JSON.private_key} \
|
|
107
170
|
--dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
|
|
108
|
-
|
|
171
|
+
{ disableLog: true },
|
|
172
|
+
);
|
|
109
173
|
|
|
110
174
|
shellExec(
|
|
111
175
|
`kubectl create configmap env-config \
|
|
@@ -165,7 +229,14 @@ sudo sysctl -w net.core.wmem_max=7500000`,
|
|
|
165
229
|
|
|
166
230
|
const credentials = Underpost.ipfs.resolveCredentials(`${underpostRoot}/engine-private`);
|
|
167
231
|
|
|
168
|
-
|
|
232
|
+
// `env-config` advertises `bootstrap-peer-id` from the local identity file — the peer id is
|
|
233
|
+
// not carried in the Secret. So a regeneration invalidates any stored manifest: it would
|
|
234
|
+
// pair the previous private key with the new id and the cluster would never form. Re-encrypt
|
|
235
|
+
// the fresh pair so the store and the ConfigMap stay one identity.
|
|
236
|
+
if (credentials.generated && Underpost.secret.sops.has('ipfs-cluster-secret', options.namespace))
|
|
237
|
+
Underpost.ipfs.storeCredentials(credentials, options);
|
|
238
|
+
|
|
239
|
+
const ipfsReplicas = resolveReplicaCount(options.replicas, IPFS_DEFAULT_REPLICA_COUNT);
|
|
169
240
|
|
|
170
241
|
Underpost.ipfs.teardown(options, ipfsReplicas);
|
|
171
242
|
Underpost.ipfs.applySecrets(credentials, options);
|
package/src/cli/monitor.js
CHANGED
|
@@ -123,7 +123,7 @@ class UnderpostMonitor {
|
|
|
123
123
|
|
|
124
124
|
let errorPayloads = [];
|
|
125
125
|
if (options.sync === true) {
|
|
126
|
-
const currentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace });
|
|
126
|
+
const currentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace, env });
|
|
127
127
|
if (currentTraffic) Underpost.env.set(`${deployId}-${env}-traffic`, currentTraffic);
|
|
128
128
|
}
|
|
129
129
|
let traffic = Underpost.env.get(`${deployId}-${env}-traffic`) ?? 'blue';
|
package/src/cli/repository.js
CHANGED
|
@@ -1706,7 +1706,7 @@ Prevent build private config repo.`,
|
|
|
1706
1706
|
}
|
|
1707
1707
|
|
|
1708
1708
|
// Resolve the active blue/green traffic colour so we target the correct pod
|
|
1709
|
-
const traffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace });
|
|
1709
|
+
const traffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace, env });
|
|
1710
1710
|
if (!traffic) {
|
|
1711
1711
|
logger.warn(`backupPodRepositories: could not resolve current traffic for ${deployId} — skipping`);
|
|
1712
1712
|
return;
|