@absolutejs/deploy 0.6.0 → 0.8.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 +104 -3
- package/dist/cloudTarget.d.ts +8 -8
- package/dist/digitalocean.d.ts +1 -1
- package/dist/digitalocean.js.map +3 -3
- package/dist/digitaloceanDns.d.ts +22 -0
- package/dist/digitaloceanDns.js +526 -0
- package/dist/digitaloceanDns.js.map +13 -0
- package/dist/env.d.ts +112 -0
- package/dist/env.js +203 -0
- package/dist/env.js.map +10 -0
- package/dist/hetzner.js.map +2 -2
- package/dist/hetznerDns.d.ts +43 -0
- package/dist/hetznerDns.js +169 -0
- package/dist/hetznerDns.js.map +10 -0
- package/dist/linode.d.ts +99 -0
- package/dist/linode.js +428 -0
- package/dist/linode.js.map +12 -0
- package/dist/vultr.d.ts +95 -0
- package/dist/vultr.js +420 -0
- package/dist/vultr.js.map +12 -0
- package/package.json +27 -2
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/hetznerDns.ts
|
|
3
|
+
var HETZNER_DNS_API_BASE = "https://dns.hetzner.com/api/v1";
|
|
4
|
+
|
|
5
|
+
class HetznerDnsError extends Error {
|
|
6
|
+
status;
|
|
7
|
+
body;
|
|
8
|
+
constructor(message, status, body) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "HetznerDnsError";
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.body = body;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
var createHetznerDnsClient = (token, options = {}) => {
|
|
16
|
+
const base = options.baseUrl ?? HETZNER_DNS_API_BASE;
|
|
17
|
+
const f = options.fetch ?? fetch;
|
|
18
|
+
return {
|
|
19
|
+
request: async (method, path, body) => {
|
|
20
|
+
const init = {
|
|
21
|
+
headers: {
|
|
22
|
+
"auth-api-token": token,
|
|
23
|
+
"content-type": "application/json"
|
|
24
|
+
},
|
|
25
|
+
method
|
|
26
|
+
};
|
|
27
|
+
if (body !== undefined)
|
|
28
|
+
init.body = JSON.stringify(body);
|
|
29
|
+
const response = await f(`${base}${path}`, init);
|
|
30
|
+
if (response.status === 204)
|
|
31
|
+
return;
|
|
32
|
+
const text = await response.text();
|
|
33
|
+
const parsed = text.length > 0 ? JSON.parse(text) : undefined;
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new HetznerDnsError(`Hetzner DNS API ${method} ${path} failed: ${response.status} ${response.statusText}`, response.status, parsed);
|
|
36
|
+
}
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
var resolveClient = (options) => {
|
|
42
|
+
if (options.client !== undefined)
|
|
43
|
+
return options.client;
|
|
44
|
+
if (options.token !== undefined && options.token.length > 0) {
|
|
45
|
+
return createHetznerDnsClient(options.token);
|
|
46
|
+
}
|
|
47
|
+
throw new Error("[deploy/hetzner-dns] either `token` or `client` must be provided");
|
|
48
|
+
};
|
|
49
|
+
var fqdnFromRelative = (relativeName, zoneName) => {
|
|
50
|
+
if (relativeName === "@")
|
|
51
|
+
return zoneName;
|
|
52
|
+
if (zoneName === "")
|
|
53
|
+
return relativeName;
|
|
54
|
+
if (relativeName.endsWith(`.${zoneName}`))
|
|
55
|
+
return relativeName;
|
|
56
|
+
return `${relativeName}.${zoneName}`;
|
|
57
|
+
};
|
|
58
|
+
var relativeFromFqdn = (fqdn, zoneName) => {
|
|
59
|
+
if (zoneName === "")
|
|
60
|
+
return fqdn;
|
|
61
|
+
const noTrailing = fqdn.endsWith(".") ? fqdn.slice(0, -1) : fqdn;
|
|
62
|
+
if (noTrailing === zoneName)
|
|
63
|
+
return "@";
|
|
64
|
+
if (noTrailing.endsWith(`.${zoneName}`)) {
|
|
65
|
+
return noTrailing.slice(0, -zoneName.length - 1);
|
|
66
|
+
}
|
|
67
|
+
return noTrailing;
|
|
68
|
+
};
|
|
69
|
+
var specMatchesRecord = (spec, record) => {
|
|
70
|
+
if (record.content !== spec.content)
|
|
71
|
+
return false;
|
|
72
|
+
if (spec.ttl !== undefined && record.ttl !== spec.ttl)
|
|
73
|
+
return false;
|
|
74
|
+
return true;
|
|
75
|
+
};
|
|
76
|
+
var hetznerDnsProvider = (options) => {
|
|
77
|
+
const client = resolveClient(options);
|
|
78
|
+
const { zoneId } = options;
|
|
79
|
+
const zoneName = options.zoneName ?? "";
|
|
80
|
+
const zoneLabel = zoneName !== "" ? zoneName : zoneId;
|
|
81
|
+
const toDnsRecord = (raw) => ({
|
|
82
|
+
content: raw.value,
|
|
83
|
+
id: raw.id,
|
|
84
|
+
name: fqdnFromRelative(raw.name, zoneName),
|
|
85
|
+
type: raw.type,
|
|
86
|
+
...raw.ttl !== undefined ? { ttl: raw.ttl } : {}
|
|
87
|
+
});
|
|
88
|
+
const fetchAll = async () => {
|
|
89
|
+
const body = await client.request("GET", `/records?zone_id=${encodeURIComponent(zoneId)}&per_page=200`);
|
|
90
|
+
return body.records.map(toDnsRecord);
|
|
91
|
+
};
|
|
92
|
+
const list = async (filter) => {
|
|
93
|
+
const all = await fetchAll();
|
|
94
|
+
return all.filter((record) => {
|
|
95
|
+
if (filter?.name !== undefined && record.name !== filter.name) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
if (filter?.type !== undefined && record.type !== filter.type) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
const find = async (key) => {
|
|
105
|
+
const matches = (await fetchAll()).filter((record) => record.name === key.name && record.type === key.type);
|
|
106
|
+
if (matches.length === 0)
|
|
107
|
+
return;
|
|
108
|
+
if (matches.length > 1) {
|
|
109
|
+
throw new Error(`[deploy/hetzner-dns] multiple ${key.type} records for "${key.name}" in zone ${zoneLabel} \u2014 drifted state; resolve manually before upsert.`);
|
|
110
|
+
}
|
|
111
|
+
return matches[0];
|
|
112
|
+
};
|
|
113
|
+
const create = async (spec) => {
|
|
114
|
+
const relative = relativeFromFqdn(spec.name, zoneName);
|
|
115
|
+
const response = await client.request("POST", "/records", {
|
|
116
|
+
name: relative,
|
|
117
|
+
type: spec.type,
|
|
118
|
+
value: spec.content,
|
|
119
|
+
zone_id: zoneId,
|
|
120
|
+
...spec.ttl !== undefined ? { ttl: spec.ttl } : {}
|
|
121
|
+
});
|
|
122
|
+
return toDnsRecord(response.record);
|
|
123
|
+
};
|
|
124
|
+
const update = async (id, spec) => {
|
|
125
|
+
const relative = relativeFromFqdn(spec.name, zoneName);
|
|
126
|
+
const response = await client.request("PUT", `/records/${id}`, {
|
|
127
|
+
name: relative,
|
|
128
|
+
type: spec.type,
|
|
129
|
+
value: spec.content,
|
|
130
|
+
zone_id: zoneId,
|
|
131
|
+
...spec.ttl !== undefined ? { ttl: spec.ttl } : {}
|
|
132
|
+
});
|
|
133
|
+
return toDnsRecord(response.record);
|
|
134
|
+
};
|
|
135
|
+
const deleteRecord = async (id) => {
|
|
136
|
+
try {
|
|
137
|
+
await client.request("DELETE", `/records/${id}`);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (error instanceof HetznerDnsError && error.status === 404)
|
|
140
|
+
return;
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const upsert = async (spec) => {
|
|
145
|
+
const existing = await find({ name: spec.name, type: spec.type });
|
|
146
|
+
if (existing === undefined)
|
|
147
|
+
return create(spec);
|
|
148
|
+
if (specMatchesRecord(spec, existing))
|
|
149
|
+
return existing;
|
|
150
|
+
return update(existing.id, spec);
|
|
151
|
+
};
|
|
152
|
+
return {
|
|
153
|
+
create,
|
|
154
|
+
delete: deleteRecord,
|
|
155
|
+
description: `hetzner dns zone "${zoneLabel}"`,
|
|
156
|
+
find,
|
|
157
|
+
list,
|
|
158
|
+
update,
|
|
159
|
+
upsert
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
export {
|
|
163
|
+
hetznerDnsProvider,
|
|
164
|
+
createHetznerDnsClient,
|
|
165
|
+
HetznerDnsError
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
//# debugId=46CBC2F7E260682464756E2164756E21
|
|
169
|
+
//# sourceMappingURL=hetznerDns.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/hetznerDns.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @absolutejs/deploy/hetzner-dns — Hetzner DNS implementing the\n * {@link DnsProvider} contract from `./dns`.\n *\n * NOTE: Hetzner DNS is a SEPARATE service from Hetzner Cloud (compute).\n * - Compute API: https://api.hetzner.cloud/v1, token = Bearer\n * - DNS API: https://dns.hetzner.com/api/v1, token = Auth-API-Token header\n *\n * Different auth header AND different base URL — get them confused\n * and every request fails. We use a separate client type +\n * `createHetznerDnsClient` factory to keep them distinct.\n *\n * Scope: one provider instance is bound to one zone (referenced by\n * `zoneId`). DNS record names in Hetzner are FQDN-style for the\n * apex (`'@'`) and relative for subdomains (`'api'` not\n * `'api.example.com'`).\n */\n\nimport type {\n\tDnsProvider,\n\tDnsRecord,\n\tDnsRecordFilter,\n\tDnsRecordSpec,\n\tDnsRecordType\n} from './dns';\n\nconst HETZNER_DNS_API_BASE = 'https://dns.hetzner.com/api/v1';\n\nexport type HetznerDnsClientLike = {\n\trequest: <T = unknown>(\n\t\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE',\n\t\tpath: string,\n\t\tbody?: unknown\n\t) => Promise<T>;\n};\n\ntype HetznerDnsRecordRaw = {\n\tid: string;\n\ttype: string;\n\tname: string;\n\tvalue: string;\n\tttl?: number;\n\tzone_id: string;\n};\n\nexport class HetznerDnsError extends Error {\n\treadonly status: number;\n\treadonly body: unknown;\n\tconstructor(message: string, status: number, body: unknown) {\n\t\tsuper(message);\n\t\tthis.name = 'HetznerDnsError';\n\t\tthis.status = status;\n\t\tthis.body = body;\n\t}\n}\n\n/**\n * fetch-backed default client. Uses Hetzner DNS's `Auth-API-Token`\n * header — NOT the Bearer-style auth used by Hetzner Cloud compute.\n */\nexport const createHetznerDnsClient = (\n\ttoken: string,\n\toptions: { baseUrl?: string; fetch?: typeof fetch } = {}\n): HetznerDnsClientLike => {\n\tconst base = options.baseUrl ?? HETZNER_DNS_API_BASE;\n\tconst f = options.fetch ?? fetch;\n\treturn {\n\t\trequest: async <T>(\n\t\t\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE',\n\t\t\tpath: string,\n\t\t\tbody?: unknown\n\t\t): Promise<T> => {\n\t\t\tconst init: RequestInit = {\n\t\t\t\theaders: {\n\t\t\t\t\t'auth-api-token': token,\n\t\t\t\t\t'content-type': 'application/json'\n\t\t\t\t},\n\t\t\t\tmethod\n\t\t\t};\n\t\t\tif (body !== undefined) init.body = JSON.stringify(body);\n\t\t\tconst response = await f(`${base}${path}`, init);\n\t\t\tif (response.status === 204) return undefined as T;\n\t\t\tconst text = await response.text();\n\t\t\tconst parsed = text.length > 0 ? JSON.parse(text) : undefined;\n\t\t\tif (!response.ok) {\n\t\t\t\tthrow new HetznerDnsError(\n\t\t\t\t\t`Hetzner DNS API ${method} ${path} failed: ${response.status} ${response.statusText}`,\n\t\t\t\t\tresponse.status,\n\t\t\t\t\tparsed\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn parsed as T;\n\t\t}\n\t};\n};\n\nexport type HetznerDnsProviderOptions = {\n\ttoken?: string;\n\tclient?: HetznerDnsClientLike;\n\t/** Hetzner DNS zone id (UUID-shaped). */\n\tzoneId: string;\n\t/** Optional zone name for log + record-name conversion. */\n\tzoneName?: string;\n};\n\nconst resolveClient = (\n\toptions: Pick<HetznerDnsProviderOptions, 'client' | 'token'>\n): HetznerDnsClientLike => {\n\tif (options.client !== undefined) return options.client;\n\tif (options.token !== undefined && options.token.length > 0) {\n\t\treturn createHetznerDnsClient(options.token);\n\t}\n\tthrow new Error(\n\t\t'[deploy/hetzner-dns] either `token` or `client` must be provided'\n\t);\n};\n\nconst fqdnFromRelative = (relativeName: string, zoneName: string): string => {\n\tif (relativeName === '@') return zoneName;\n\tif (zoneName === '') return relativeName;\n\tif (relativeName.endsWith(`.${zoneName}`)) return relativeName;\n\treturn `${relativeName}.${zoneName}`;\n};\n\nconst relativeFromFqdn = (fqdn: string, zoneName: string): string => {\n\tif (zoneName === '') return fqdn;\n\tconst noTrailing = fqdn.endsWith('.') ? fqdn.slice(0, -1) : fqdn;\n\tif (noTrailing === zoneName) return '@';\n\tif (noTrailing.endsWith(`.${zoneName}`)) {\n\t\treturn noTrailing.slice(0, -zoneName.length - 1);\n\t}\n\treturn noTrailing;\n};\n\nconst specMatchesRecord = (spec: DnsRecordSpec, record: DnsRecord): boolean => {\n\tif (record.content !== spec.content) return false;\n\tif (spec.ttl !== undefined && record.ttl !== spec.ttl) return false;\n\treturn true;\n};\n\nexport const hetznerDnsProvider = (\n\toptions: HetznerDnsProviderOptions\n): DnsProvider => {\n\tconst client = resolveClient(options);\n\tconst { zoneId } = options;\n\tconst zoneName = options.zoneName ?? '';\n\tconst zoneLabel = zoneName !== '' ? zoneName : zoneId;\n\n\tconst toDnsRecord = (raw: HetznerDnsRecordRaw): DnsRecord => ({\n\t\tcontent: raw.value,\n\t\tid: raw.id,\n\t\tname: fqdnFromRelative(raw.name, zoneName),\n\t\ttype: raw.type as DnsRecordType,\n\t\t...(raw.ttl !== undefined ? { ttl: raw.ttl } : {})\n\t});\n\n\tconst fetchAll = async (): Promise<DnsRecord[]> => {\n\t\tconst body = await client.request<{ records: HetznerDnsRecordRaw[] }>(\n\t\t\t'GET',\n\t\t\t`/records?zone_id=${encodeURIComponent(zoneId)}&per_page=200`\n\t\t);\n\t\treturn body.records.map(toDnsRecord);\n\t};\n\n\tconst list = async (filter?: DnsRecordFilter): Promise<DnsRecord[]> => {\n\t\tconst all = await fetchAll();\n\t\treturn all.filter((record) => {\n\t\t\tif (filter?.name !== undefined && record.name !== filter.name) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (filter?.type !== undefined && record.type !== filter.type) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn true;\n\t\t});\n\t};\n\n\tconst find = async (key: {\n\t\tname: string;\n\t\ttype: DnsRecordType;\n\t}): Promise<DnsRecord | undefined> => {\n\t\tconst matches = (await fetchAll()).filter(\n\t\t\t(record) => record.name === key.name && record.type === key.type\n\t\t);\n\t\tif (matches.length === 0) return undefined;\n\t\tif (matches.length > 1) {\n\t\t\tthrow new Error(\n\t\t\t\t`[deploy/hetzner-dns] multiple ${key.type} records for \"${key.name}\" in zone ${zoneLabel} — drifted state; resolve manually before upsert.`\n\t\t\t);\n\t\t}\n\t\treturn matches[0];\n\t};\n\n\tconst create = async (spec: DnsRecordSpec): Promise<DnsRecord> => {\n\t\tconst relative = relativeFromFqdn(spec.name, zoneName);\n\t\tconst response = await client.request<{ record: HetznerDnsRecordRaw }>(\n\t\t\t'POST',\n\t\t\t'/records',\n\t\t\t{\n\t\t\t\tname: relative,\n\t\t\t\ttype: spec.type,\n\t\t\t\tvalue: spec.content,\n\t\t\t\tzone_id: zoneId,\n\t\t\t\t...(spec.ttl !== undefined ? { ttl: spec.ttl } : {})\n\t\t\t}\n\t\t);\n\t\treturn toDnsRecord(response.record);\n\t};\n\n\tconst update = async (\n\t\tid: string,\n\t\tspec: DnsRecordSpec\n\t): Promise<DnsRecord> => {\n\t\tconst relative = relativeFromFqdn(spec.name, zoneName);\n\t\tconst response = await client.request<{ record: HetznerDnsRecordRaw }>(\n\t\t\t'PUT',\n\t\t\t`/records/${id}`,\n\t\t\t{\n\t\t\t\tname: relative,\n\t\t\t\ttype: spec.type,\n\t\t\t\tvalue: spec.content,\n\t\t\t\tzone_id: zoneId,\n\t\t\t\t...(spec.ttl !== undefined ? { ttl: spec.ttl } : {})\n\t\t\t}\n\t\t);\n\t\treturn toDnsRecord(response.record);\n\t};\n\n\tconst deleteRecord = async (id: string): Promise<void> => {\n\t\ttry {\n\t\t\tawait client.request('DELETE', `/records/${id}`);\n\t\t} catch (error) {\n\t\t\tif (error instanceof HetznerDnsError && error.status === 404) return;\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\tconst upsert = async (spec: DnsRecordSpec): Promise<DnsRecord> => {\n\t\tconst existing = await find({ name: spec.name, type: spec.type });\n\t\tif (existing === undefined) return create(spec);\n\t\tif (specMatchesRecord(spec, existing)) return existing;\n\t\treturn update(existing.id, spec);\n\t};\n\n\treturn {\n\t\tcreate,\n\t\tdelete: deleteRecord,\n\t\tdescription: `hetzner dns zone \"${zoneLabel}\"`,\n\t\tfind,\n\t\tlist,\n\t\tupdate,\n\t\tupsert\n\t};\n};\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "46CBC2F7E260682464756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/dist/linode.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @absolutejs/deploy/linode — provision-or-reuse Target adapter for
|
|
3
|
+
* Linode (Akamai Cloud) instances. Sibling to digitalOceanTarget +
|
|
4
|
+
* hetznerTarget; same shape, Linode v4 API mappings.
|
|
5
|
+
*
|
|
6
|
+
* Idempotent by label — Linode allows duplicate labels per account
|
|
7
|
+
* but we treat duplicates as drifted state (throw on the second).
|
|
8
|
+
*
|
|
9
|
+
* Narrow LinodeClientLike interface keeps `@linode/api-v4` out as a
|
|
10
|
+
* hard dep. Default client uses `fetch` against `api.linode.com/v4`.
|
|
11
|
+
*/
|
|
12
|
+
import type { Target } from './targets';
|
|
13
|
+
export type LinodeClientLike = {
|
|
14
|
+
request: <T = unknown>(method: 'GET' | 'POST' | 'DELETE', path: string, body?: unknown) => Promise<T>;
|
|
15
|
+
};
|
|
16
|
+
/** A Linode instance, narrowed to what we inspect. */
|
|
17
|
+
export type LinodeInstance = {
|
|
18
|
+
id: number;
|
|
19
|
+
label: string;
|
|
20
|
+
status: 'running' | 'offline' | 'booting' | 'rebooting' | 'shutting_down' | 'provisioning' | 'deleting' | 'migrating' | 'rebuilding' | 'cloning' | 'restoring' | 'stopped';
|
|
21
|
+
/** Linode returns IPv4 as a flat string array; public IPs first. */
|
|
22
|
+
ipv4: string[];
|
|
23
|
+
region?: string;
|
|
24
|
+
type?: string;
|
|
25
|
+
tags?: string[];
|
|
26
|
+
};
|
|
27
|
+
export type LinodeTargetOptions = {
|
|
28
|
+
/** Linode Personal Access Token. Required unless `client` is set. */
|
|
29
|
+
token?: string;
|
|
30
|
+
/** Custom client. Overrides token-built default. */
|
|
31
|
+
client?: LinodeClientLike;
|
|
32
|
+
/** Instance label. Idempotency key. */
|
|
33
|
+
name: string;
|
|
34
|
+
/** Region slug — `'us-east'`, `'us-west'`, `'ap-south'`, etc. */
|
|
35
|
+
region: string;
|
|
36
|
+
/** Type slug — `'g6-nanode-1'`, `'g6-standard-1'`, etc. */
|
|
37
|
+
type: string;
|
|
38
|
+
/** Image slug — e.g. `'linode/ubuntu22.04'`. */
|
|
39
|
+
image: string;
|
|
40
|
+
/**
|
|
41
|
+
* SSH public keys (raw `ssh-rsa AAAA…` / `ssh-ed25519 AAAA…` strings)
|
|
42
|
+
* authorized for root login. At least one required for a usable target.
|
|
43
|
+
*/
|
|
44
|
+
sshKeys: ReadonlyArray<string>;
|
|
45
|
+
/** Tags applied to the instance. */
|
|
46
|
+
tags?: ReadonlyArray<string>;
|
|
47
|
+
/** Cloud-init / StackScript user data, base64-encoded by us. */
|
|
48
|
+
userData?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Root password. Linode requires a password at creation. Defaults to
|
|
51
|
+
* a random 32-char string — you should rely on `sshKeys` and never
|
|
52
|
+
* type this. Override only if you have a specific reason.
|
|
53
|
+
*/
|
|
54
|
+
rootPass?: string;
|
|
55
|
+
/** Enable private IP. Default false. */
|
|
56
|
+
privateIp?: boolean;
|
|
57
|
+
/** Enable backups. Default false. */
|
|
58
|
+
backupsEnabled?: boolean;
|
|
59
|
+
/** SSH login user. Default `'root'`. */
|
|
60
|
+
user?: string;
|
|
61
|
+
/** SSH identity file. */
|
|
62
|
+
identity?: string;
|
|
63
|
+
/** SSH port. Default 22. */
|
|
64
|
+
port?: number;
|
|
65
|
+
provisionTimeoutMs?: number;
|
|
66
|
+
sshReadinessTimeoutMs?: number;
|
|
67
|
+
pollIntervalMs?: number;
|
|
68
|
+
onLog?: (line: string) => void;
|
|
69
|
+
probeSsh?: (host: string, port: number) => Promise<boolean>;
|
|
70
|
+
sleep?: (ms: number) => Promise<void>;
|
|
71
|
+
now?: () => number;
|
|
72
|
+
};
|
|
73
|
+
export type LinodeTarget = Target & {
|
|
74
|
+
readonly instanceId: number;
|
|
75
|
+
readonly ipv4: string;
|
|
76
|
+
destroy: () => Promise<void>;
|
|
77
|
+
};
|
|
78
|
+
export declare class LinodeError extends Error {
|
|
79
|
+
readonly status: number;
|
|
80
|
+
readonly body: unknown;
|
|
81
|
+
constructor(message: string, status: number, body: unknown);
|
|
82
|
+
}
|
|
83
|
+
export declare const createLinodeClient: (token: string, options?: {
|
|
84
|
+
baseUrl?: string;
|
|
85
|
+
fetch?: typeof fetch;
|
|
86
|
+
}) => LinodeClientLike;
|
|
87
|
+
/** Find an instance by label. Throws on ambiguous duplicates. */
|
|
88
|
+
export declare const findLinodeInstance: (client: LinodeClientLike, name: string) => Promise<LinodeInstance | undefined>;
|
|
89
|
+
export declare const listLinodeInstances: (options: {
|
|
90
|
+
token?: string;
|
|
91
|
+
client?: LinodeClientLike;
|
|
92
|
+
tag?: string;
|
|
93
|
+
}) => Promise<LinodeInstance[]>;
|
|
94
|
+
export declare const destroyLinodeInstance: (options: {
|
|
95
|
+
token?: string;
|
|
96
|
+
client?: LinodeClientLike;
|
|
97
|
+
id: number;
|
|
98
|
+
}) => Promise<void>;
|
|
99
|
+
export declare const linodeTarget: (options: LinodeTargetOptions) => Promise<LinodeTarget>;
|