-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtypes.ts
More file actions
171 lines (150 loc) · 6.28 KB
/
Copy pathtypes.ts
File metadata and controls
171 lines (150 loc) · 6.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/**
* VPS provider abstraction.
*
* groundflare talks to Hetzner / DigitalOcean / Linode / etc. through a
* single Provider interface, so the bootstrap orchestrator and the CLI
* stay provider-agnostic. This file defines the shared types.
*
* Keep the surface minimal — anything that's provider-specific (custom
* disk volumes, snapshots, private networking) lives outside this
* interface and the operator can fall back to the provider's own CLI.
*
* See design/provider.md for the ADR explaining why we don't pull in
* Pulumi / Terraform here.
*/
export type ProviderName =
| 'hetzner'
| 'digitalocean'
| 'linode'
| 'vultr'
// ─── Errors ────────────────────────────────────────────────────────
/**
* Normalized error type. Provider implementations translate their HTTP
* errors into this shape so callers can branch on retryability without
* sniffing per-provider error codes.
*/
export class ProviderError extends Error {
constructor(
message: string,
/** Stable machine-readable code (e.g. `unauthorized`, `quota_exceeded`). */
public readonly code: string,
/** Underlying HTTP status, if applicable. */
public readonly status: number | undefined,
/** Whether the caller should retry after a backoff. */
public readonly retryable: boolean,
/** Optional underlying error (network failure, parse error, etc.). */
options?: { cause?: unknown },
) {
super(message, options ? { cause: options.cause } : undefined)
this.name = 'ProviderError'
}
}
// ─── Discovery types ───────────────────────────────────────────────
export interface Account {
/** Provider-assigned identifier, e.g. project ID. */
readonly id: string
/** Human-readable name shown in CLI output. */
readonly name: string
/** Optional contact email if the API exposes it. */
readonly email?: string
}
export interface Size {
/** Provider-specific size identifier, e.g. `cx22`. */
readonly id: string
readonly name: string
readonly cpuCores: number
readonly ramGiB: number
readonly diskGiB: number
/** Monthly list price in cents of the provider's primary currency (EUR for Hetzner, USD elsewhere). */
readonly priceMonthlyCents: number
/** Free included egress per month, if applicable. */
readonly egressFreeTb?: number
/** Region IDs in which this size is currently available. Empty = unknown. */
readonly availableInRegions: readonly string[]
}
export interface Region {
/** Provider-specific region identifier, e.g. `hel1`. */
readonly id: string
readonly name: string
readonly country?: string
readonly city?: string
}
// ─── SSH keys ──────────────────────────────────────────────────────
export interface SSHKey {
readonly id: string
readonly name: string
/** SHA-256 fingerprint, lowercase hex with colons. */
readonly fingerprint: string
}
export interface SSHKeyOptions {
readonly name: string
/** OpenSSH-format public key (`ssh-ed25519 ...` or similar). */
readonly publicKey: string
}
// ─── VPS lifecycle ─────────────────────────────────────────────────
export interface ProvisionOptions {
readonly name: string
readonly size: string
readonly region: string
/** OS image identifier. Defaults to the provider's current Ubuntu LTS. */
readonly image?: string
/** SSH key IDs (from `uploadSSHKey`) to install on the new VPS. */
readonly sshKeyIds: readonly string[]
/** cloud-init user-data YAML applied at first boot. */
readonly userData?: string
/** Optional labels for tracking/auditing. */
readonly labels?: Record<string, string>
}
export type VPSStatus =
| 'initializing'
| 'running'
| 'stopped'
| 'deleting'
| 'unknown'
export interface VPS {
readonly id: string
readonly name: string
readonly status: VPSStatus
readonly publicIPv4?: string
readonly publicIPv6?: string
/**
* Non-standard SSH port. Real providers always serve on 22 so omit this;
* the test-only DockerTestProvider sets it to the host-forwarded port.
*/
readonly sshPort?: number
/** Size identifier (e.g. `cx22`). */
readonly size: string
/** Region identifier (e.g. `hel1`). */
readonly region: string
/** ISO 8601 timestamp from the provider. */
readonly createdAt: string
readonly labels?: Record<string, string>
}
// ─── Provider interface ────────────────────────────────────────────
export interface Provider {
readonly name: ProviderName
readonly displayName: string
/**
* Verify the token works. Returns a normalized Account for the project.
* Throws ProviderError(`unauthorized`) on bad credentials.
*/
authenticate(token: string): Promise<Account>
// ─── Discovery ─────────────────────────────────────────────────
listSizes(region?: string): Promise<readonly Size[]>
listRegions(): Promise<readonly Region[]>
// ─── SSH keys ──────────────────────────────────────────────────
uploadSSHKey(opts: SSHKeyOptions): Promise<SSHKey>
listSSHKeys(): Promise<readonly SSHKey[]>
deleteSSHKey(id: string): Promise<void>
// ─── VPS lifecycle ─────────────────────────────────────────────
createVPS(opts: ProvisionOptions): Promise<VPS>
/** Returns null if the VPS isn't found (vs. throwing for transport errors). */
getVPS(id: string): Promise<VPS | null>
listVPS(): Promise<readonly VPS[]>
destroyVPS(id: string): Promise<void>
/**
* Synchronous price lookup using a baked-in price table. Returns 0 when
* the size/region pair is unknown — callers should treat 0 as "no quote".
*/
estimateMonthlyCost(opts: { size: string; region: string }): number
}