@fnndsc/stratus 0.1.0 → 0.2.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 +6 -1
- package/dist/args.d.ts +20 -6
- package/dist/args.js +45 -2
- package/dist/args.js.map +1 -1
- package/dist/backend.d.ts +35 -4
- package/dist/backend.js +77 -12
- package/dist/backend.js.map +1 -1
- package/dist/cloud/builder.d.ts +172 -0
- package/dist/cloud/builder.js +41 -0
- package/dist/cloud/builder.js.map +1 -0
- package/dist/cloud/driver.d.ts +149 -0
- package/dist/cloud/fake/builder.d.ts +147 -0
- package/dist/cloud/fake/builder.js +213 -0
- package/dist/cloud/fake/builder.js.map +1 -0
- package/dist/cloud/fake/driver.d.ts +73 -1
- package/dist/cloud/fake/driver.js +122 -0
- package/dist/cloud/fake/driver.js.map +1 -1
- package/dist/cloud/oci/api.d.ts +3 -2
- package/dist/cloud/oci/api.js +2 -0
- package/dist/cloud/oci/api.js.map +1 -1
- package/dist/cloud/oci/buildApi.d.ts +61 -0
- package/dist/cloud/oci/buildApi.js +132 -0
- package/dist/cloud/oci/buildApi.js.map +1 -0
- package/dist/cloud/oci/builder.d.ts +172 -0
- package/dist/cloud/oci/builder.js +370 -0
- package/dist/cloud/oci/builder.js.map +1 -0
- package/dist/cloud/oci/driver.d.ts +23 -2
- package/dist/cloud/oci/driver.js +107 -1
- package/dist/cloud/oci/driver.js.map +1 -1
- package/dist/cloud/oci/planRead.d.ts +38 -0
- package/dist/cloud/oci/planRead.js +67 -0
- package/dist/cloud/oci/planRead.js.map +1 -0
- package/dist/cloud/oci/prices.d.ts +22 -1
- package/dist/cloud/oci/prices.js +35 -0
- package/dist/cloud/oci/prices.js.map +1 -1
- package/dist/cloud/oci/resourceApi.d.ts +114 -0
- package/dist/cloud/oci/resourceApi.js +261 -0
- package/dist/cloud/oci/resourceApi.js.map +1 -0
- package/dist/cloud/oci/resourceMap.d.ts +44 -0
- package/dist/cloud/oci/resourceMap.js +70 -0
- package/dist/cloud/oci/resourceMap.js.map +1 -0
- package/dist/cloud/oci/rmApi.d.ts +74 -0
- package/dist/cloud/oci/rmApi.js +141 -0
- package/dist/cloud/oci/rmApi.js.map +1 -0
- package/dist/cloud/oci/stackVars.d.ts +49 -0
- package/dist/cloud/oci/stackVars.js +70 -0
- package/dist/cloud/oci/stackVars.js.map +1 -0
- package/dist/cluster/fakeKube.d.ts +43 -0
- package/dist/cluster/fakeKube.js +86 -0
- package/dist/cluster/fakeKube.js.map +1 -0
- package/dist/cluster/kube.d.ts +40 -0
- package/dist/cluster/kube.js +46 -0
- package/dist/cluster/kube.js.map +1 -0
- package/dist/cluster/kubeHttp.d.ts +16 -0
- package/dist/cluster/kubeHttp.js +86 -0
- package/dist/cluster/kubeHttp.js.map +1 -0
- package/dist/commands/build.d.ts +90 -0
- package/dist/commands/build.js +358 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/capacity.d.ts +37 -0
- package/dist/commands/capacity.js +93 -0
- package/dist/commands/capacity.js.map +1 -0
- package/dist/commands/domain.d.ts +45 -0
- package/dist/commands/domain.js +150 -0
- package/dist/commands/domain.js.map +1 -0
- package/dist/commands/spend.d.ts +22 -0
- package/dist/commands/spend.js +65 -0
- package/dist/commands/spend.js.map +1 -0
- package/dist/commands/teardown.d.ts +10 -0
- package/dist/commands/teardown.js +72 -0
- package/dist/commands/teardown.js.map +1 -0
- package/dist/commands/verify.d.ts +28 -0
- package/dist/commands/verify.js +98 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/domain/adopt.d.ts +60 -0
- package/dist/domain/adopt.js +143 -0
- package/dist/domain/adopt.js.map +1 -0
- package/dist/domain/sizing.d.ts +55 -0
- package/dist/domain/sizing.js +66 -0
- package/dist/domain/sizing.js.map +1 -0
- package/dist/domain/spec.d.ts +101 -0
- package/dist/domain/spec.js +191 -0
- package/dist/domain/spec.js.map +1 -0
- package/dist/domain/spend.d.ts +84 -0
- package/dist/domain/spend.js +109 -0
- package/dist/domain/spend.js.map +1 -0
- package/dist/domain/state.d.ts +149 -0
- package/dist/domain/state.js +182 -0
- package/dist/domain/state.js.map +1 -0
- package/dist/domain/steps.d.ts +82 -0
- package/dist/domain/steps.js +311 -0
- package/dist/domain/steps.js.map +1 -0
- package/dist/domain/teardown.d.ts +38 -0
- package/dist/domain/teardown.js +48 -0
- package/dist/domain/teardown.js.map +1 -0
- package/dist/domain/verify.d.ts +68 -0
- package/dist/domain/verify.js +216 -0
- package/dist/domain/verify.js.map +1 -0
- package/dist/domain/view.d.ts +24 -0
- package/dist/domain/view.js +38 -0
- package/dist/domain/view.js.map +1 -0
- package/dist/domains/harborOci.d.ts +75 -0
- package/dist/domains/harborOci.js +165 -0
- package/dist/domains/harborOci.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/installer/assisted.d.ts +97 -0
- package/dist/installer/assisted.js +171 -0
- package/dist/installer/assisted.js.map +1 -0
- package/dist/installer/installer.d.ts +86 -0
- package/dist/installer/installer.js +15 -0
- package/dist/installer/installer.js.map +1 -0
- package/dist/prompt.d.ts +33 -0
- package/dist/prompt.js +46 -0
- package/dist/prompt.js.map +1 -0
- package/dist/session.d.ts +2 -0
- package/dist/session.js +4 -0
- package/dist/session.js.map +1 -1
- package/dist/stratus.js +75 -3
- package/dist/stratus.js.map +1 -1
- package/dist/vfs/domains.d.ts +39 -0
- package/dist/vfs/domains.js +128 -0
- package/dist/vfs/domains.js.map +1 -0
- package/dist/vfs/look.d.ts +10 -0
- package/dist/vfs/look.js +43 -8
- package/dist/vfs/look.js.map +1 -1
- package/dist/vfs/resources.d.ts +136 -0
- package/dist/vfs/resources.js +325 -0
- package/dist/vfs/resources.js.map +1 -0
- package/dist/vfs/tenancy.d.ts +15 -2
- package/dist/vfs/tenancy.js +53 -17
- package/dist/vfs/tenancy.js.map +1 -1
- package/package.json +13 -6
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `domain verify [<name>]`: the ten checks against a domain's cluster
|
|
3
|
+
* (docs/stratus.adoc, D7), through its kubeconfig on the host, which never
|
|
4
|
+
* leaves it. The result is kept as the domain's verify step, so `domain
|
|
5
|
+
* status` and the DOMAINS tile show it.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { promises as dns } from 'node:dns';
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import { envelope_error, envelope_ok } from '@fnndsc/menu';
|
|
13
|
+
import { commandCancellation_enable, commandCancellation_signalGet, surface_get } from '@fnndsc/brasa/core';
|
|
14
|
+
import { kubeconfig_read } from '../cluster/kube.js';
|
|
15
|
+
import { httpsStatus_get, kubeApi_make } from '../cluster/kubeHttp.js';
|
|
16
|
+
import { familyShape_find } from '../domain/sizing.js';
|
|
17
|
+
import { verify_run } from '../domain/verify.js';
|
|
18
|
+
/** The resolver `verify` asks, as `harbor-oci` did: one outside the tenancy. */
|
|
19
|
+
export const PUBLIC_RESOLVER = '8.8.8.8';
|
|
20
|
+
/**
|
|
21
|
+
* The reach over the network: the Kubernetes API, a public resolver, HTTPS.
|
|
22
|
+
*
|
|
23
|
+
* @returns The reach.
|
|
24
|
+
*/
|
|
25
|
+
export function verifyReach_default() {
|
|
26
|
+
const resolver = new dns.Resolver();
|
|
27
|
+
resolver.setServers([PUBLIC_RESOLVER]);
|
|
28
|
+
return {
|
|
29
|
+
kube_make: (kubeconfig) => kubeApi_make(kubeconfig_read(kubeconfig)),
|
|
30
|
+
resolve: (name) => resolver.resolve4(name),
|
|
31
|
+
https_status: httpsStatus_get,
|
|
32
|
+
probeTimeoutMs: 300_000,
|
|
33
|
+
pollMs: 5_000,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A check as a line.
|
|
38
|
+
*
|
|
39
|
+
* @param check - The check.
|
|
40
|
+
* @returns The line.
|
|
41
|
+
*/
|
|
42
|
+
function check_line(check) {
|
|
43
|
+
const mark = check.ok === null ? chalk.gray('·') : check.ok ? chalk.cyan('✓') : chalk.red('✗');
|
|
44
|
+
return ` ${mark} ${check.name.padEnd(16)} ${check.ok === false ? chalk.red(check.detail) : check.detail}${check.how === 'writes' ? chalk.yellow(' (writes)') : ''}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `domain verify [<name>]`.
|
|
48
|
+
*
|
|
49
|
+
* @param context - The session.
|
|
50
|
+
* @param reach - How the checks reach the cluster.
|
|
51
|
+
* @param args - The words after `verify`.
|
|
52
|
+
* @returns The envelope: failed when a check failed.
|
|
53
|
+
*/
|
|
54
|
+
export async function domainVerify_run(context, reach, args) {
|
|
55
|
+
if (args.length > 1 || args.some((word) => word.startsWith('-')))
|
|
56
|
+
return envelope_error('', undefined, 'domain verify: usage: domain verify [<name>]\n');
|
|
57
|
+
const kept = context.store.names_list();
|
|
58
|
+
const name = args[0] ?? (kept.length === 1 ? kept[0] : undefined);
|
|
59
|
+
if (name === undefined)
|
|
60
|
+
return envelope_error('', undefined, `domain verify: which domain? (${kept.join(', ') || 'none kept here'})\n`);
|
|
61
|
+
const domain = context.domains.domain_read(name);
|
|
62
|
+
if (domain === null)
|
|
63
|
+
return envelope_error('', undefined, `domain verify: ${name}: no such domain\n`);
|
|
64
|
+
const record = context.store.record_read(name);
|
|
65
|
+
if (domain.origin !== 'built' || record === null)
|
|
66
|
+
return envelope_error('', undefined, `domain verify: ${name}: read from a harbor-oci checkout; domain adopt ${name} --from ${domain.root} keeps it here first\n`);
|
|
67
|
+
const kubeconfig = domain.secrets.find((secret) => secret.name === 'kubeconfig');
|
|
68
|
+
if (kubeconfig === undefined)
|
|
69
|
+
return envelope_error('', undefined, `domain verify: ${name}: no kubeconfig yet: its cluster is not installed\n`);
|
|
70
|
+
const spec = context.store.spec_read(name)?.spec ?? null;
|
|
71
|
+
const shapes = context.region() === null ? [] : await context.driver.shapes_list(record.region).catch(() => []);
|
|
72
|
+
commandCancellation_enable();
|
|
73
|
+
const checks = await verify_run({
|
|
74
|
+
kube: reach.kube_make(readFileSync(kubeconfig.hostPath, 'utf-8')),
|
|
75
|
+
resolve: reach.resolve,
|
|
76
|
+
https_status: reach.https_status,
|
|
77
|
+
domain: name,
|
|
78
|
+
baseDomain: record.baseDomain,
|
|
79
|
+
shape: spec === null ? null : familyShape_find(shapes, spec.family)?.name ?? null,
|
|
80
|
+
expectedNodes: spec === null ? 0 : spec.controlPlane.count + spec.workers.count,
|
|
81
|
+
addresses: { api: record.ids['apiIp'], apiInt: record.ids['apiIntIp'], apps: record.ids['appsIp'] },
|
|
82
|
+
probe: surface_get().capabilities.tasks === true,
|
|
83
|
+
probeTimeoutMs: reach.probeTimeoutMs,
|
|
84
|
+
pollMs: reach.pollMs,
|
|
85
|
+
...(commandCancellation_signalGet() !== undefined ? { signal: commandCancellation_signalGet() } : {}),
|
|
86
|
+
});
|
|
87
|
+
const ran = checks.filter((check) => check.ok !== null);
|
|
88
|
+
const passed = ran.filter((check) => check.ok === true).length;
|
|
89
|
+
const failed = ran.length - passed;
|
|
90
|
+
const skipped = checks.length - ran.length;
|
|
91
|
+
const summary = `${passed} of ${checks.length} pass${failed > 0 ? `, ${failed} fail` : ''}${skipped > 0 ? `, ${skipped} not run (needs the tasks tier)` : ''}`;
|
|
92
|
+
context.store.record_update(name, (current) => {
|
|
93
|
+
current.steps['verify'] = { state: failed > 0 ? 'failed' : 'done', ended: new Date().toISOString(), note: summary };
|
|
94
|
+
});
|
|
95
|
+
const text = [`${chalk.bold(name)} ${name}.${record.baseDomain}`, ...checks.map(check_line), `verify: ${summary}`].join('\n');
|
|
96
|
+
return failed > 0 ? { ...envelope_error(`${text}\n`), renderedErr: '' } : envelope_ok(`${text}\n`);
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { JobRun } from '@fnndsc/brasa/core';
|
|
2
|
+
import { type HarborDomain } from '../domains/harborOci.js';
|
|
3
|
+
import type { CloudBuilder } from '../cloud/builder.js';
|
|
4
|
+
import { type DomainSpec } from './spec.js';
|
|
5
|
+
import type { DomainRecord, DomainStore } from './state.js';
|
|
6
|
+
/** What adoption reads from a checkout. */
|
|
7
|
+
export interface CheckoutRead {
|
|
8
|
+
readonly domain: HarborDomain;
|
|
9
|
+
readonly record: DomainRecord;
|
|
10
|
+
readonly spec: DomainSpec;
|
|
11
|
+
/** The stack's variables, each a string, as Resource Manager takes them. */
|
|
12
|
+
readonly variables: Record<string, string>;
|
|
13
|
+
/** Where the Terraform state is on the host. */
|
|
14
|
+
readonly stateFile: string;
|
|
15
|
+
/** The stack release the checkout pinned. */
|
|
16
|
+
readonly stackVersion: string;
|
|
17
|
+
/** The secrets to copy, by name and host path. */
|
|
18
|
+
readonly secrets: ReadonlyArray<{
|
|
19
|
+
name: string;
|
|
20
|
+
hostPath: string;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Terraform variables as Resource Manager takes them: every value a string.
|
|
25
|
+
*
|
|
26
|
+
* @param tfvars - The variables as `harbor-oci` wrote them.
|
|
27
|
+
* @returns The same, stringified.
|
|
28
|
+
*/
|
|
29
|
+
export declare function variables_stringify(tfvars: Readonly<Record<string, unknown>>): Record<string, string>;
|
|
30
|
+
/**
|
|
31
|
+
* Reads what adoption needs from a `harbor-oci` checkout.
|
|
32
|
+
*
|
|
33
|
+
* @param name - The domain's name here.
|
|
34
|
+
* @param root - The checkout.
|
|
35
|
+
* @param now - The clock.
|
|
36
|
+
* @returns What it read, or why it cannot be adopted.
|
|
37
|
+
*/
|
|
38
|
+
export declare function checkout_read(name: string, root: string, now: Date): CheckoutRead | {
|
|
39
|
+
error: string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Keeps an adopted domain here: its record, its spec, its secrets.
|
|
43
|
+
*
|
|
44
|
+
* @param store - The domains this daemon keeps.
|
|
45
|
+
* @param read - What was read from the checkout.
|
|
46
|
+
*/
|
|
47
|
+
export declare function adoption_keep(store: DomainStore, read: CheckoutRead): void;
|
|
48
|
+
/**
|
|
49
|
+
* The job that puts an adopted domain's stack in Resource Manager.
|
|
50
|
+
*
|
|
51
|
+
* @param store - The domains.
|
|
52
|
+
* @param builder - The cloud's build half, one that can adopt.
|
|
53
|
+
* @param name - The domain.
|
|
54
|
+
* @param variables - The stack's variables.
|
|
55
|
+
* @param stateFile - Where the Terraform state is on the host.
|
|
56
|
+
* @param compartmentId - Where the stack goes.
|
|
57
|
+
* @param stackVersion - The stack release the domain was built with.
|
|
58
|
+
* @returns The job's work.
|
|
59
|
+
*/
|
|
60
|
+
export declare function adoptRun_make(store: DomainStore, builder: CloudBuilder, name: string, variables: Record<string, string>, stateFile: string, compartmentId: string, stackVersion: string): JobRun;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Adopting a domain `harbor-oci` built (docs/stratus.adoc, D4, step 4b).
|
|
3
|
+
*
|
|
4
|
+
* Adoption has two halves. Read from the checkout, on the host: the ids the
|
|
5
|
+
* build recorded (the Assisted Installer's cluster and infra-env, the ISO
|
|
6
|
+
* object), the spec, the settings, the step evidence, and the secrets, copied
|
|
7
|
+
* into the domain's own `auth/` (mode 600, never served). Then, with the
|
|
8
|
+
* `tasks` tier: a Resource Manager stack of the pinned release with the
|
|
9
|
+
* domain's own variables, tagged with its name, and its Terraform state
|
|
10
|
+
* imported. From then on the domain's state is stratus's own and the
|
|
11
|
+
* checkout is no longer read.
|
|
12
|
+
*
|
|
13
|
+
* An adopted domain is planned against, never built on: its apply is refused
|
|
14
|
+
* until the operator allows it by name (decided 2026-10-22).
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { envFile_parse, harborDomain_read } from '../domains/harborOci.js';
|
|
21
|
+
import { specYaml_parse, SPEC_DEFAULT } from './spec.js';
|
|
22
|
+
import { STEPS_WRITTEN } from './steps.js';
|
|
23
|
+
/** The secrets a domain keeps from its checkout. */
|
|
24
|
+
const KEPT_SECRETS = ['kubeconfig', 'kubeadmin-password'];
|
|
25
|
+
/**
|
|
26
|
+
* Terraform variables as Resource Manager takes them: every value a string.
|
|
27
|
+
*
|
|
28
|
+
* @param tfvars - The variables as `harbor-oci` wrote them.
|
|
29
|
+
* @returns The same, stringified.
|
|
30
|
+
*/
|
|
31
|
+
export function variables_stringify(tfvars) {
|
|
32
|
+
const variables = {};
|
|
33
|
+
for (const [key, value] of Object.entries(tfvars))
|
|
34
|
+
variables[key] = typeof value === 'string' ? value : JSON.stringify(value);
|
|
35
|
+
return variables;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Reads what adoption needs from a `harbor-oci` checkout.
|
|
39
|
+
*
|
|
40
|
+
* @param name - The domain's name here.
|
|
41
|
+
* @param root - The checkout.
|
|
42
|
+
* @param now - The clock.
|
|
43
|
+
* @returns What it read, or why it cannot be adopted.
|
|
44
|
+
*/
|
|
45
|
+
export function checkout_read(name, root, now) {
|
|
46
|
+
if (!existsSync(join(root, 'config.env')))
|
|
47
|
+
return { error: `${root}: not a harbor-oci checkout (no config.env)` };
|
|
48
|
+
const domain = harborDomain_read({ name, root }, now);
|
|
49
|
+
const harbor = domain.state;
|
|
50
|
+
if (harbor === null)
|
|
51
|
+
return { error: `${root}: no state/harbor.json: nothing was built from it` };
|
|
52
|
+
const settings = {
|
|
53
|
+
...envFile_parse(existsSync(join(root, 'config.env')) ? readFileSync(join(root, 'config.env'), 'utf-8') : ''),
|
|
54
|
+
...envFile_parse(existsSync(join(root, 'config.local.env')) ? readFileSync(join(root, 'config.local.env'), 'utf-8') : ''),
|
|
55
|
+
};
|
|
56
|
+
const stackDir = harbor['stackDir'];
|
|
57
|
+
if (typeof stackDir !== 'string')
|
|
58
|
+
return { error: `${root}: state/harbor.json names no stackDir: infra-plan was never run` };
|
|
59
|
+
const stackPath = stackDir.startsWith('/') ? stackDir : join(root, stackDir);
|
|
60
|
+
const tfvarsFile = join(stackPath, 'harbor.auto.tfvars.json');
|
|
61
|
+
const stateFile = join(stackPath, 'terraform.tfstate');
|
|
62
|
+
if (!existsSync(tfvarsFile))
|
|
63
|
+
return { error: `${tfvarsFile}: missing` };
|
|
64
|
+
if (!existsSync(stateFile))
|
|
65
|
+
return { error: `${stateFile}: missing: infra-apply was never run` };
|
|
66
|
+
const region = settings['OCI_REGION'];
|
|
67
|
+
const baseDomain = settings['BASE_DOMAIN'];
|
|
68
|
+
const compartmentId = settings['OCI_COMPARTMENT_OCID'];
|
|
69
|
+
if (region === undefined || baseDomain === undefined || compartmentId === undefined)
|
|
70
|
+
return { error: `${root}: its settings lack OCI_REGION, BASE_DOMAIN or OCI_COMPARTMENT_OCID` };
|
|
71
|
+
const parsed = domain.spec === null ? { spec: SPEC_DEFAULT } : specYaml_parse(domain.spec);
|
|
72
|
+
if ('error' in parsed)
|
|
73
|
+
return { error: `${root}/spec.yaml: ${parsed.error}` };
|
|
74
|
+
const steps = {};
|
|
75
|
+
for (const step of domain.steps) {
|
|
76
|
+
// The cluster runs, so every step through run happened, whatever the host kept to show it.
|
|
77
|
+
if (STEPS_WRITTEN.includes(step.name))
|
|
78
|
+
steps[step.name] = { state: 'done', note: `done before adoption (harbor-oci: ${step.state}, ${step.evidence})` };
|
|
79
|
+
}
|
|
80
|
+
const ids = {};
|
|
81
|
+
for (const [key, field] of [['clusterId', 'assistedClusterId'], ['infraEnvId', 'assistedInfraEnvId'], ['isoObject', 'isoObjectName']]) {
|
|
82
|
+
if (typeof harbor[field] === 'string')
|
|
83
|
+
ids[key] = harbor[field];
|
|
84
|
+
}
|
|
85
|
+
// The load balancers' addresses, from the state's outputs, for the DNS check.
|
|
86
|
+
const outputs = JSON.parse(readFileSync(stateFile, 'utf-8')).outputs ?? {};
|
|
87
|
+
for (const [key, output] of [['apiIp', 'open_shift_api_lb_addr'], ['apiIntIp', 'open_shift_api_int_lb_addr'], ['appsIp', 'open_shift_apps_lb_addr']]) {
|
|
88
|
+
if (typeof outputs[output]?.value === 'string')
|
|
89
|
+
ids[key] = outputs[output].value;
|
|
90
|
+
}
|
|
91
|
+
const record = {
|
|
92
|
+
name, created: now.toISOString(), region, baseDomain, steps, ids, compartmentId,
|
|
93
|
+
adopted: { from: root, at: now.toISOString() },
|
|
94
|
+
};
|
|
95
|
+
const secrets = domain.secrets.filter((secret) => KEPT_SECRETS.includes(secret.name));
|
|
96
|
+
return {
|
|
97
|
+
domain, record, spec: parsed.spec, stateFile, secrets,
|
|
98
|
+
variables: variables_stringify(JSON.parse(readFileSync(tfvarsFile, 'utf-8'))),
|
|
99
|
+
stackVersion: settings['OCI_STACK_VERSION'] ?? '',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Keeps an adopted domain here: its record, its spec, its secrets.
|
|
104
|
+
*
|
|
105
|
+
* @param store - The domains this daemon keeps.
|
|
106
|
+
* @param read - What was read from the checkout.
|
|
107
|
+
*/
|
|
108
|
+
export function adoption_keep(store, read) {
|
|
109
|
+
store.create(read.record, read.spec);
|
|
110
|
+
for (const secret of read.secrets)
|
|
111
|
+
store.secret_write(read.record.name, secret.name, readFileSync(secret.hostPath, 'utf-8'));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The job that puts an adopted domain's stack in Resource Manager.
|
|
115
|
+
*
|
|
116
|
+
* @param store - The domains.
|
|
117
|
+
* @param builder - The cloud's build half, one that can adopt.
|
|
118
|
+
* @param name - The domain.
|
|
119
|
+
* @param variables - The stack's variables.
|
|
120
|
+
* @param stateFile - Where the Terraform state is on the host.
|
|
121
|
+
* @param compartmentId - Where the stack goes.
|
|
122
|
+
* @param stackVersion - The stack release the domain was built with.
|
|
123
|
+
* @returns The job's work.
|
|
124
|
+
*/
|
|
125
|
+
export function adoptRun_make(store, builder, name, variables, stateFile, compartmentId, stackVersion) {
|
|
126
|
+
return async (context) => {
|
|
127
|
+
const record = store.record_read(name);
|
|
128
|
+
if (record === null)
|
|
129
|
+
throw new Error(`${name}: no such domain`);
|
|
130
|
+
if (record.ids['stackRef'] !== undefined) {
|
|
131
|
+
context.log_write(`already in Resource Manager: ${record.ids['stackRef']}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (builder.stack_adopt === undefined)
|
|
135
|
+
throw new Error(`${builder.id}: this cloud cannot adopt a stack`);
|
|
136
|
+
const stackRef = await builder.stack_adopt({ domain: name, region: record.region, compartmentId, variables, state: readFileSync(stateFile), stackVersion }, { log: (line) => context.log_write(line), signal: context.signal });
|
|
137
|
+
if (context.signal.aborted)
|
|
138
|
+
return;
|
|
139
|
+
store.record_update(name, (current) => { current.ids['stackRef'] = stackRef; });
|
|
140
|
+
context.log_write(`adopted: stack ${stackRef}`);
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=adopt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adopt.js","sourceRoot":"","sources":["../../src/domain/adopt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { CloudDriver, CloudPrice, CloudShape } from '../cloud/driver.js';
|
|
2
|
+
import { type DomainSpec } from './spec.js';
|
|
3
|
+
/** One availability domain's fit. */
|
|
4
|
+
export interface ZoneFit {
|
|
5
|
+
zone: string;
|
|
6
|
+
/** The machines placed there. */
|
|
7
|
+
nodes: string[];
|
|
8
|
+
coresNeeded: number;
|
|
9
|
+
coresFree: number | null;
|
|
10
|
+
memoryNeeded: number;
|
|
11
|
+
memoryFree: number | null;
|
|
12
|
+
/** Null when the cloud did not say what is free. */
|
|
13
|
+
fits: boolean | null;
|
|
14
|
+
}
|
|
15
|
+
/** What a spec costs an hour. */
|
|
16
|
+
export interface SpecCost {
|
|
17
|
+
hourly: number;
|
|
18
|
+
currency: string;
|
|
19
|
+
/** What the figure covers. */
|
|
20
|
+
covers: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The flexible shape a spec's family runs on.
|
|
24
|
+
*
|
|
25
|
+
* @param shapes - The region's shapes.
|
|
26
|
+
* @param family - The family.
|
|
27
|
+
* @returns The shape, or undefined.
|
|
28
|
+
*/
|
|
29
|
+
export declare function familyShape_find(shapes: ReadonlyArray<CloudShape>, family: string): CloudShape | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* What a spec costs an hour in compute and memory.
|
|
32
|
+
*
|
|
33
|
+
* @param spec - The spec.
|
|
34
|
+
* @param shape - Its family's shape.
|
|
35
|
+
* @param prices - The price list.
|
|
36
|
+
* @returns The cost, or null when the shape is not priced.
|
|
37
|
+
*/
|
|
38
|
+
export declare function specCost_of(spec: DomainSpec, shape: CloudShape | undefined, prices: ReadonlyArray<CloudPrice>): SpecCost | null;
|
|
39
|
+
/**
|
|
40
|
+
* Money as the operator reads it.
|
|
41
|
+
*
|
|
42
|
+
* @param cost - The cost.
|
|
43
|
+
* @returns `$0.62 an hour (USD, compute and memory …)`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function cost_words(cost: SpecCost): string;
|
|
46
|
+
/**
|
|
47
|
+
* Whether a spec fits each availability domain, read from the limits.
|
|
48
|
+
*
|
|
49
|
+
* @param driver - The cloud.
|
|
50
|
+
* @param region - The region.
|
|
51
|
+
* @param domain - The domain's name, for its machines' names.
|
|
52
|
+
* @param spec - The spec.
|
|
53
|
+
* @returns One row per zone, in the region's order.
|
|
54
|
+
*/
|
|
55
|
+
export declare function specFit_read(driver: CloudDriver, region: string, domain: string, spec: DomainSpec): Promise<ZoneFit[]>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { capacityRows_read } from '../commands/capacity.js';
|
|
2
|
+
import { specNodes_of } from './spec.js';
|
|
3
|
+
/**
|
|
4
|
+
* The flexible shape a spec's family runs on.
|
|
5
|
+
*
|
|
6
|
+
* @param shapes - The region's shapes.
|
|
7
|
+
* @param family - The family.
|
|
8
|
+
* @returns The shape, or undefined.
|
|
9
|
+
*/
|
|
10
|
+
export function familyShape_find(shapes, family) {
|
|
11
|
+
const ofFamily = shapes.filter((shape) => shape.family.toLowerCase() === family.toLowerCase());
|
|
12
|
+
return ofFamily.find((shape) => shape.flexible) ?? ofFamily[0];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* What a spec costs an hour in compute and memory.
|
|
16
|
+
*
|
|
17
|
+
* @param spec - The spec.
|
|
18
|
+
* @param shape - Its family's shape.
|
|
19
|
+
* @param prices - The price list.
|
|
20
|
+
* @returns The cost, or null when the shape is not priced.
|
|
21
|
+
*/
|
|
22
|
+
export function specCost_of(spec, shape, prices) {
|
|
23
|
+
const price = shape === undefined ? undefined : prices.find((entry) => entry.shape === shape.name);
|
|
24
|
+
if (price === undefined)
|
|
25
|
+
return null;
|
|
26
|
+
const vcpu = spec.controlPlane.count * spec.controlPlane.vcpu + spec.workers.count * spec.workers.vcpu;
|
|
27
|
+
const gib = spec.controlPlane.count * spec.controlPlane.ramGib + spec.workers.count * spec.workers.ramGib;
|
|
28
|
+
return { hourly: vcpu * price.vcpuHour + gib * price.gibHour, currency: price.currency, covers: `compute and memory (${vcpu} vCPU, ${gib} GiB); volumes and load balancers are not priced yet` };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Money as the operator reads it.
|
|
32
|
+
*
|
|
33
|
+
* @param cost - The cost.
|
|
34
|
+
* @returns `$0.62 an hour (USD, compute and memory …)`.
|
|
35
|
+
*/
|
|
36
|
+
export function cost_words(cost) {
|
|
37
|
+
const symbol = cost.currency === 'USD' ? '$' : `${cost.currency} `;
|
|
38
|
+
return `${symbol}${cost.hourly.toFixed(2)} an hour (${cost.covers})`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Whether a spec fits each availability domain, read from the limits.
|
|
42
|
+
*
|
|
43
|
+
* @param driver - The cloud.
|
|
44
|
+
* @param region - The region.
|
|
45
|
+
* @param domain - The domain's name, for its machines' names.
|
|
46
|
+
* @param spec - The spec.
|
|
47
|
+
* @returns One row per zone, in the region's order.
|
|
48
|
+
*/
|
|
49
|
+
export async function specFit_read(driver, region, domain, spec) {
|
|
50
|
+
const zones = await driver.zones_list(region);
|
|
51
|
+
const shape = familyShape_find(await driver.shapes_list(region), spec.family);
|
|
52
|
+
const perCore = shape?.vcpuPerCore ?? 2;
|
|
53
|
+
const rows = await capacityRows_read(driver, region, [spec.family]);
|
|
54
|
+
const nodes = specNodes_of(domain, spec, zones.length);
|
|
55
|
+
return zones.map((zone, index) => {
|
|
56
|
+
const here = nodes.filter((node) => node.zoneIndex === index);
|
|
57
|
+
const free = (word) => rows.find((row) => row.zone === zone.name && row.limit.includes(word))?.available ?? null;
|
|
58
|
+
const coresNeeded = here.reduce((sum, node) => sum + node.vcpu / perCore, 0);
|
|
59
|
+
const memoryNeeded = here.reduce((sum, node) => sum + node.ramGib, 0);
|
|
60
|
+
const coresFree = free('core');
|
|
61
|
+
const memoryFree = free('memory');
|
|
62
|
+
const fits = coresFree === null || memoryFree === null ? null : coresNeeded <= coresFree && memoryNeeded <= memoryFree;
|
|
63
|
+
return { zone: zone.name, nodes: here.map((node) => node.name), coresNeeded, coresFree, memoryNeeded, memoryFree, fits };
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=sizing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sizing.js","sourceRoot":"","sources":["../../src/domain/sizing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file A domain's spec: the cluster and the ChRIS on it, in neutral units
|
|
3
|
+
* (vCPU and GiB), as `harbor-oci`'s `spec.yaml` holds them (docs/stratus.adoc,
|
|
4
|
+
* "Three layers, three tools, one spec").
|
|
5
|
+
*
|
|
6
|
+
* The file keeps `harbor-oci`'s shape, so a spec moves between the two: a
|
|
7
|
+
* family, then `controlPlane`, `workers` and `chris` sections of `key: value`
|
|
8
|
+
* lines. It is read and written here without a YAML library, because only
|
|
9
|
+
* that shape is ever read; anything else refuses by line.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
/** One pool of nodes. */
|
|
14
|
+
export interface NodePool {
|
|
15
|
+
count: number;
|
|
16
|
+
vcpu: number;
|
|
17
|
+
ramGib: number;
|
|
18
|
+
bootGib: number;
|
|
19
|
+
bootTier: string;
|
|
20
|
+
}
|
|
21
|
+
/** What ChRIS is given on the cluster. */
|
|
22
|
+
export interface ChrisSizing {
|
|
23
|
+
dataGib: number;
|
|
24
|
+
databaseGib: number;
|
|
25
|
+
queueGib: number;
|
|
26
|
+
}
|
|
27
|
+
/** A domain's spec. */
|
|
28
|
+
export interface DomainSpec {
|
|
29
|
+
family: string;
|
|
30
|
+
controlPlane: NodePool;
|
|
31
|
+
workers: NodePool;
|
|
32
|
+
chris: ChrisSizing;
|
|
33
|
+
}
|
|
34
|
+
/** The spec a new domain starts from: `harbor-oci`'s compact cluster. */
|
|
35
|
+
export declare const SPEC_DEFAULT: DomainSpec;
|
|
36
|
+
/**
|
|
37
|
+
* A copy of a spec, nothing shared.
|
|
38
|
+
*
|
|
39
|
+
* @param spec - The spec.
|
|
40
|
+
* @returns The copy.
|
|
41
|
+
*/
|
|
42
|
+
export declare function spec_copy(spec: DomainSpec): DomainSpec;
|
|
43
|
+
/**
|
|
44
|
+
* Writes a spec as `spec.yaml`.
|
|
45
|
+
*
|
|
46
|
+
* @param spec - The spec.
|
|
47
|
+
* @returns The file's text.
|
|
48
|
+
*/
|
|
49
|
+
export declare function specYaml_render(spec: DomainSpec): string;
|
|
50
|
+
/**
|
|
51
|
+
* Reads `spec.yaml`.
|
|
52
|
+
*
|
|
53
|
+
* @param text - The file's text.
|
|
54
|
+
* @returns The spec, or the line it could not read.
|
|
55
|
+
*/
|
|
56
|
+
export declare function specYaml_parse(text: string): {
|
|
57
|
+
spec: DomainSpec;
|
|
58
|
+
} | {
|
|
59
|
+
error: string;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Reads a pool written as `<count>x<vcpu>/<GiB>` (`3x8/24`).
|
|
63
|
+
*
|
|
64
|
+
* @param text - The words.
|
|
65
|
+
* @returns The count, vCPU and GiB, or null.
|
|
66
|
+
*/
|
|
67
|
+
export declare function poolArg_parse(text: string): {
|
|
68
|
+
count: number;
|
|
69
|
+
vcpu: number;
|
|
70
|
+
ramGib: number;
|
|
71
|
+
} | null;
|
|
72
|
+
/**
|
|
73
|
+
* Applies `domain new`'s sizing options to a spec.
|
|
74
|
+
*
|
|
75
|
+
* @param base - The spec to start from.
|
|
76
|
+
* @param options - `--family`, `--plane`, `--workers`, `--data`, `--database`, `--queue`, by name.
|
|
77
|
+
* @returns The spec, or what could not be read.
|
|
78
|
+
*/
|
|
79
|
+
export declare function specOptions_apply(base: DomainSpec, options: ReadonlyMap<string, string>): {
|
|
80
|
+
spec: DomainSpec;
|
|
81
|
+
} | {
|
|
82
|
+
error: string;
|
|
83
|
+
};
|
|
84
|
+
/** One node a spec asks for. */
|
|
85
|
+
export interface SpecNode {
|
|
86
|
+
name: string;
|
|
87
|
+
role: 'plane' | 'worker';
|
|
88
|
+
vcpu: number;
|
|
89
|
+
ramGib: number;
|
|
90
|
+
/** Which availability domain it is placed in, as an index into the region's zones. */
|
|
91
|
+
zoneIndex: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The nodes a spec asks for, placed one per zone in turn.
|
|
95
|
+
*
|
|
96
|
+
* @param domain - The domain's name, for the nodes' names.
|
|
97
|
+
* @param spec - The spec.
|
|
98
|
+
* @param zones - How many zones the region has.
|
|
99
|
+
* @returns The nodes.
|
|
100
|
+
*/
|
|
101
|
+
export declare function specNodes_of(domain: string, spec: DomainSpec, zones: number): SpecNode[];
|