@toshicloud/sdk 0.5.0 → 0.6.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 +138 -41
- package/dist/client.d.ts +103 -4
- package/dist/client.js +43 -4
- package/dist/errors.d.ts +74 -22
- package/dist/errors.js +99 -27
- package/dist/gen/toshi/permission_pb.d.ts +207 -2
- package/dist/gen/toshi/permission_pb.js +54 -2
- package/dist/gen/toshi/workspace/v1/account_pb.d.ts +590 -0
- package/dist/gen/toshi/workspace/v1/account_pb.js +75 -0
- package/dist/gen/toshi/workspace/v1/artifacts_pb.d.ts +14 -8
- package/dist/gen/toshi/workspace/v1/artifacts_pb.js +9 -7
- package/dist/gen/toshi/workspace/v1/audit_events_pb.d.ts +191 -6
- package/dist/gen/toshi/workspace/v1/audit_events_pb.js +11 -1
- package/dist/gen/toshi/workspace/v1/channels_pb.d.ts +1194 -21
- package/dist/gen/toshi/workspace/v1/channels_pb.js +185 -39
- package/dist/gen/toshi/workspace/v1/connections_pb.d.ts +1616 -0
- package/dist/gen/toshi/workspace/v1/connections_pb.js +224 -0
- package/dist/gen/toshi/workspace/v1/discovery_pb.d.ts +291 -43
- package/dist/gen/toshi/workspace/v1/discovery_pb.js +79 -30
- package/dist/gen/toshi/workspace/v1/display_pb.d.ts +198 -15
- package/dist/gen/toshi/workspace/v1/display_pb.js +52 -2
- package/dist/gen/toshi/workspace/v1/domains_pb.d.ts +864 -0
- package/dist/gen/toshi/workspace/v1/domains_pb.js +152 -0
- package/dist/gen/toshi/workspace/v1/fillers_pb.d.ts +12 -4
- package/dist/gen/toshi/workspace/v1/fillers_pb.js +1 -1
- package/dist/gen/toshi/workspace/v1/grants_pb.d.ts +1297 -0
- package/dist/gen/toshi/workspace/v1/grants_pb.js +271 -0
- package/dist/gen/toshi/workspace/v1/hostjobs_pb.d.ts +329 -15
- package/dist/gen/toshi/workspace/v1/hostjobs_pb.js +10 -5
- package/dist/gen/toshi/workspace/v1/hosts_pb.d.ts +196 -7
- package/dist/gen/toshi/workspace/v1/hosts_pb.js +12 -7
- package/dist/gen/toshi/workspace/v1/images_pb.d.ts +1463 -0
- package/dist/gen/toshi/workspace/v1/images_pb.js +263 -0
- package/dist/gen/toshi/workspace/v1/products_pb.d.ts +99 -296
- package/dist/gen/toshi/workspace/v1/products_pb.js +7 -53
- package/dist/gen/toshi/workspace/v1/projects_pb.d.ts +1474 -99
- package/dist/gen/toshi/workspace/v1/projects_pb.js +150 -33
- package/dist/gen/toshi/workspace/v1/recordings_pb.d.ts +181 -14
- package/dist/gen/toshi/workspace/v1/recordings_pb.js +54 -14
- package/dist/gen/toshi/workspace/v1/roles_pb.d.ts +1994 -0
- package/dist/gen/toshi/workspace/v1/roles_pb.js +355 -0
- package/dist/gen/toshi/workspace/v1/secrets_pb.d.ts +314 -8
- package/dist/gen/toshi/workspace/v1/secrets_pb.js +42 -18
- package/dist/gen/toshi/workspace/v1/storage_providers_pb.d.ts +14 -0
- package/dist/gen/toshi/workspace/v1/storage_providers_pb.js +1 -1
- package/dist/gen/toshi/workspace/v1/tokens_pb.d.ts +434 -5
- package/dist/gen/toshi/workspace/v1/tokens_pb.js +65 -8
- package/dist/gen/toshi/workspace/v1/triggers_pb.d.ts +1099 -0
- package/dist/gen/toshi/workspace/v1/triggers_pb.js +185 -0
- package/dist/gen/toshi/workspace/v1/volumes_pb.d.ts +1052 -33
- package/dist/gen/toshi/workspace/v1/volumes_pb.js +169 -11
- package/dist/gen/toshi/workspace/v1/webhook_pb.d.ts +66 -8
- package/dist/gen/toshi/workspace/v1/webhook_pb.js +4 -4
- package/dist/gen/toshi/workspace/v1/workloads_pb.d.ts +2069 -54
- package/dist/gen/toshi/workspace/v1/workloads_pb.js +204 -54
- package/dist/gen/toshi/workspace/v1/workspace_pb.d.ts +6634 -933
- package/dist/gen/toshi/workspace/v1/workspace_pb.js +912 -93
- package/dist/gen/toshi/workspace/v1/workspace_service_pb.d.ts +3209 -1378
- package/dist/gen/toshi/workspace/v1/workspace_service_pb.js +211 -74
- package/dist/index.d.ts +8 -1
- package/dist/index.js +8 -1
- package/dist/interceptors.d.ts +6 -2
- package/dist/interceptors.js +13 -8
- package/dist/retry.d.ts +18 -0
- package/dist/retry.js +230 -23
- package/dist/test/alarms.d.ts +29 -0
- package/dist/test/alarms.js +144 -0
- package/dist/test/fake.js +31 -0
- package/dist/test/index.d.ts +12 -2
- package/dist/test/index.js +12 -2
- package/dist/test/procedures.js +96 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wait.d.ts +4 -0
- package/dist/wait.js +24 -1
- package/package.json +3 -2
package/dist/interceptors.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ScalarType } from "@bufbuild/protobuf";
|
|
2
2
|
import { Code, ConnectError } from "@connectrpc/connect";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { isTransportLoss, newRequestKey, procedurePath, retryClassFor, retryDelayMs, serverWaitMs, sleep, } from "./retry.js";
|
|
4
|
+
import { asTypedError } from "./errors.js";
|
|
5
5
|
/**
|
|
6
6
|
* Sends `Authorization: Bearer <token>` on every call and stream, on a copy of
|
|
7
7
|
* the request: it is the innermost interceptor, so none outside it sees the
|
|
@@ -79,7 +79,11 @@ export function keyInterceptor() {
|
|
|
79
79
|
return next(req);
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Retries a unary read or keyed write after a transport loss, with full jitter
|
|
84
|
+
* (retry.go:98-186), or after a refusal that names how long to wait
|
|
85
|
+
* (serverWaitMs), waiting at least that long.
|
|
86
|
+
*/
|
|
83
87
|
export function retryInterceptor(retry) {
|
|
84
88
|
if (retry === false) {
|
|
85
89
|
return (next) => next;
|
|
@@ -96,10 +100,11 @@ export function retryInterceptor(retry) {
|
|
|
96
100
|
return await next(req);
|
|
97
101
|
}
|
|
98
102
|
catch (err) {
|
|
99
|
-
|
|
103
|
+
const waitMs = serverWaitMs(err);
|
|
104
|
+
if (attempt + 1 >= policy.maxAttempts || (!isTransportLoss(err) && waitMs === undefined)) {
|
|
100
105
|
throw err;
|
|
101
106
|
}
|
|
102
|
-
await sleep(
|
|
107
|
+
await sleep(retryDelayMs(policy, attempt, waitMs), req.signal);
|
|
103
108
|
}
|
|
104
109
|
}
|
|
105
110
|
};
|
|
@@ -199,14 +204,14 @@ function isShared(msg, desc) {
|
|
|
199
204
|
* on a stream's messages. It is the outermost interceptor, so the retry and
|
|
200
205
|
* the helpers inside see the ConnectError they always did.
|
|
201
206
|
*/
|
|
202
|
-
export function
|
|
207
|
+
export function refusalInterceptor() {
|
|
203
208
|
return (next) => async (req) => {
|
|
204
209
|
let res;
|
|
205
210
|
try {
|
|
206
211
|
res = await next(req);
|
|
207
212
|
}
|
|
208
213
|
catch (err) {
|
|
209
|
-
throw
|
|
214
|
+
throw asTypedError(err);
|
|
210
215
|
}
|
|
211
216
|
if (!res.stream) {
|
|
212
217
|
return res;
|
|
@@ -219,6 +224,6 @@ async function* productStream(messages) {
|
|
|
219
224
|
yield* messages;
|
|
220
225
|
}
|
|
221
226
|
catch (err) {
|
|
222
|
-
throw
|
|
227
|
+
throw asTypedError(err);
|
|
223
228
|
}
|
|
224
229
|
}
|
package/dist/retry.d.ts
CHANGED
|
@@ -34,6 +34,24 @@ export declare function isTransportLoss(err: unknown): boolean;
|
|
|
34
34
|
export declare function isStreamLoss(err: unknown): boolean;
|
|
35
35
|
/** A uniform jittered delay in [0, min(maxBackoffMs, initialBackoffMs * 2**attempt)]. */
|
|
36
36
|
export declare function backoffDelay(policy: RetryPolicy, attempt: number): number;
|
|
37
|
+
/**
|
|
38
|
+
* How long the server asked the client to wait before a retry, in
|
|
39
|
+
* milliseconds, or undefined when it named no wait. A RESOURCE_EXHAUSTED
|
|
40
|
+
* refusal names the wait in a google.rpc.RetryInfo detail and in its
|
|
41
|
+
* Retry-After header, the detail winning when both are there; the API's rate
|
|
42
|
+
* limit sends both, with the same whole seconds. Such a refusal is retried
|
|
43
|
+
* only when it names a wait: one without, such as a plan's limit, does not
|
|
44
|
+
* pass on a retry. An HTTP 429 without a Connect error body reaches the
|
|
45
|
+
* client as Code.Unavailable with the message "HTTP 429" and the response's
|
|
46
|
+
* headers (connect 2.2.0 protocol-connect/validate-response.js:33), and names
|
|
47
|
+
* the wait in its Retry-After. Every other error names none.
|
|
48
|
+
*/
|
|
49
|
+
export declare function serverWaitMs(err: unknown): number | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* The delay before retry attempt + 1: the backoff's, or the server's wait when
|
|
52
|
+
* that is longer, never more than maxBackoffMs.
|
|
53
|
+
*/
|
|
54
|
+
export declare function retryDelayMs(policy: RetryPolicy, attempt: number, waitMs: number | undefined): number;
|
|
37
55
|
/** Sleeps, rejecting at once when the signal aborts (retry.go:186-200). */
|
|
38
56
|
export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
39
57
|
/** The signal's reason as an error, or a Canceled ConnectError. */
|
package/dist/retry.js
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
import { Code, ConnectError } from "@connectrpc/connect";
|
|
2
|
+
import { BinaryReader, WireType } from "@bufbuild/protobuf/wire";
|
|
2
3
|
/** Five attempts, a 200 ms initial backoff and a 10 s cap (options.go:79-85). */
|
|
3
4
|
export const defaultRetryPolicy = {
|
|
4
5
|
maxAttempts: 5,
|
|
5
6
|
initialBackoffMs: 200,
|
|
6
7
|
maxBackoffMs: 10_000,
|
|
7
8
|
};
|
|
8
|
-
// One entry per procedure of the Workspace API's services. A
|
|
9
|
-
//
|
|
10
|
-
//
|
|
9
|
+
// One entry per procedure of the Workspace API's services. A stream is never
|
|
10
|
+
// retried here; a call whose request takes a request_key is retried with that
|
|
11
|
+
// key, so the server answers a retry with the first result; a read, a call
|
|
12
|
+
// whose permission's level is read, is retried too; every other call is not.
|
|
13
|
+
// test/retry.test.ts derives each class from the generated descriptors and
|
|
14
|
+
// fails when a regeneration moves a procedure, so an entry follows the API and
|
|
15
|
+
// not a list someone remembered to update. An entry whose class needs saying
|
|
16
|
+
// more about has it above the entry.
|
|
11
17
|
const procedureRetryClass = {
|
|
18
|
+
"/toshi.workspace.v1.AccountService/GetAccount": "read",
|
|
19
|
+
"/toshi.workspace.v1.AccountService/ListIdentities": "read",
|
|
20
|
+
// A repeat answers NOT_FOUND ("an identity the caller does not have fails
|
|
21
|
+
// with NOT_FOUND"), so a retry would report failure for a call that worked;
|
|
22
|
+
// its permission's level is read although it changes state.
|
|
23
|
+
"/toshi.workspace.v1.AccountService/UnlinkIdentity": "none",
|
|
12
24
|
"/toshi.workspace.v1.WorkloadService/CreateJob": "keyed",
|
|
13
25
|
"/toshi.workspace.v1.WorkloadService/GetJob": "read",
|
|
14
26
|
"/toshi.workspace.v1.WorkloadService/ListJobs": "read",
|
|
@@ -21,7 +33,10 @@ const procedureRetryClass = {
|
|
|
21
33
|
"/toshi.workspace.v1.WorkloadService/WatchTelemetry": "none",
|
|
22
34
|
"/toshi.workspace.v1.WorkloadService/WatchGuestLog": "none",
|
|
23
35
|
"/toshi.workspace.v1.WorkloadService/Execute": "none",
|
|
36
|
+
"/toshi.workspace.v1.WorkloadService/ExecuteCommand": "none",
|
|
37
|
+
"/toshi.workspace.v1.WorkloadService/AnswerPrompt": "none",
|
|
24
38
|
"/toshi.workspace.v1.WorkloadService/CancelExecution": "keyed",
|
|
39
|
+
"/toshi.workspace.v1.WorkloadService/ExportEvents": "none",
|
|
25
40
|
"/toshi.workspace.v1.WorkloadService/GetOperation": "read",
|
|
26
41
|
"/toshi.workspace.v1.WorkloadService/WaitOperation": "read",
|
|
27
42
|
"/toshi.workspace.v1.WorkloadService/ListEvents": "read",
|
|
@@ -29,11 +44,16 @@ const procedureRetryClass = {
|
|
|
29
44
|
"/toshi.workspace.v1.WorkloadService/CreatePreviewShareLink": "none",
|
|
30
45
|
"/toshi.workspace.v1.WorkloadService/ListPreviewShareLinks": "read",
|
|
31
46
|
"/toshi.workspace.v1.WorkloadService/RevokePreviewShareLink": "none",
|
|
47
|
+
"/toshi.workspace.v1.WorkloadService/CreateServiceShareLink": "none",
|
|
48
|
+
"/toshi.workspace.v1.WorkloadService/ListServiceShareLinks": "read",
|
|
49
|
+
"/toshi.workspace.v1.WorkloadService/RevokeServiceShareLink": "none",
|
|
32
50
|
"/toshi.workspace.v1.WorkloadService/CreateSandbox": "keyed",
|
|
33
51
|
"/toshi.workspace.v1.WorkloadService/GetSandbox": "read",
|
|
34
52
|
"/toshi.workspace.v1.WorkloadService/ListSandboxes": "read",
|
|
35
53
|
"/toshi.workspace.v1.WorkloadService/UpdateSandbox": "keyed",
|
|
36
54
|
"/toshi.workspace.v1.WorkloadService/DeleteSandbox": "keyed",
|
|
55
|
+
"/toshi.workspace.v1.WorkloadService/StopSandbox": "keyed",
|
|
56
|
+
"/toshi.workspace.v1.WorkloadService/StartSandbox": "keyed",
|
|
37
57
|
"/toshi.workspace.v1.WorkloadService/WaitSandbox": "read",
|
|
38
58
|
"/toshi.workspace.v1.WorkloadService/WatchSandboxes": "none",
|
|
39
59
|
"/toshi.workspace.v1.WorkloadService/CreateService": "keyed",
|
|
@@ -43,14 +63,34 @@ const procedureRetryClass = {
|
|
|
43
63
|
"/toshi.workspace.v1.WorkloadService/DeleteService": "keyed",
|
|
44
64
|
"/toshi.workspace.v1.WorkloadService/WaitService": "read",
|
|
45
65
|
"/toshi.workspace.v1.WorkloadService/WatchServices": "none",
|
|
46
|
-
"/toshi.workspace.v1.WorkloadService/StartService": "
|
|
47
|
-
"/toshi.workspace.v1.WorkloadService/StopService": "
|
|
48
|
-
"/toshi.workspace.v1.WorkloadService/SetServiceMode": "
|
|
49
|
-
"/toshi.workspace.v1.WorkloadService/WakeService": "
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
"/toshi.workspace.v1.WorkloadService/StartService": "keyed",
|
|
67
|
+
"/toshi.workspace.v1.WorkloadService/StopService": "keyed",
|
|
68
|
+
"/toshi.workspace.v1.WorkloadService/SetServiceMode": "keyed",
|
|
69
|
+
"/toshi.workspace.v1.WorkloadService/WakeService": "keyed",
|
|
70
|
+
// Operator-only, and neither takes a request key: a lost answer is reported
|
|
71
|
+
// rather than repeated.
|
|
72
|
+
"/toshi.workspace.v1.WorkloadService/HoldWorkload": "none",
|
|
73
|
+
"/toshi.workspace.v1.WorkloadService/ReleaseWorkload": "none",
|
|
74
|
+
"/toshi.workspace.v1.WorkloadService/HoldService": "keyed",
|
|
75
|
+
"/toshi.workspace.v1.WorkloadService/ReleaseService": "keyed",
|
|
76
|
+
"/toshi.workspace.v1.WorkloadService/ListServiceRevisions": "read",
|
|
77
|
+
"/toshi.workspace.v1.WorkloadService/GetServiceRevision": "read",
|
|
78
|
+
"/toshi.workspace.v1.WorkloadService/RollbackService": "keyed",
|
|
79
|
+
"/toshi.workspace.v1.WorkloadService/ListServiceActivations": "read",
|
|
52
80
|
"/toshi.workspace.v1.WorkloadService/ListServiceRuns": "read",
|
|
53
|
-
"/toshi.workspace.v1.
|
|
81
|
+
"/toshi.workspace.v1.WorkloadService/EmitWorkloadEvent": "none",
|
|
82
|
+
"/toshi.workspace.v1.WorkloadService/SetServiceAlarm": "none",
|
|
83
|
+
"/toshi.workspace.v1.WorkloadService/GetServiceAlarm": "read",
|
|
84
|
+
"/toshi.workspace.v1.WorkloadService/ListServiceAlarms": "read",
|
|
85
|
+
"/toshi.workspace.v1.WorkloadService/DeleteServiceAlarm": "none",
|
|
86
|
+
"/toshi.workspace.v1.WorkloadService/PauseServiceAlarm": "none",
|
|
87
|
+
"/toshi.workspace.v1.WorkloadService/ResumeServiceAlarm": "none",
|
|
88
|
+
"/toshi.workspace.v1.WorkloadService/RunServiceAlarmNow": "keyed",
|
|
89
|
+
"/toshi.workspace.v1.WorkloadService/StartServiceSignIn": "none",
|
|
90
|
+
"/toshi.workspace.v1.WorkloadService/AnswerServiceSignIn": "none",
|
|
91
|
+
"/toshi.workspace.v1.WorkloadService/CancelServiceSignIn": "none",
|
|
92
|
+
"/toshi.workspace.v1.WorkloadService/GetServiceSignInStatus": "read",
|
|
93
|
+
"/toshi.workspace.v1.SecretService/CreateSecretSet": "keyed",
|
|
54
94
|
"/toshi.workspace.v1.SecretService/ListSecretSets": "read",
|
|
55
95
|
"/toshi.workspace.v1.SecretService/DeleteSecretSet": "none",
|
|
56
96
|
"/toshi.workspace.v1.SecretService/PutSecret": "none",
|
|
@@ -60,18 +100,28 @@ const procedureRetryClass = {
|
|
|
60
100
|
"/toshi.workspace.v1.SecretService/GrantSecretSet": "none",
|
|
61
101
|
"/toshi.workspace.v1.SecretService/RevokeSecretSet": "none",
|
|
62
102
|
"/toshi.workspace.v1.SecretService/ListSecretSetGrants": "read",
|
|
103
|
+
"/toshi.workspace.v1.SecretService/UpdateSecretSet": "none",
|
|
63
104
|
"/toshi.workspace.v1.ProjectService/ListProjects": "read",
|
|
105
|
+
"/toshi.workspace.v1.ProjectService/UpdateProject": "none",
|
|
64
106
|
"/toshi.workspace.v1.ProjectService/ListRepositories": "read",
|
|
65
107
|
"/toshi.workspace.v1.ProjectService/GetProject": "read",
|
|
66
108
|
"/toshi.workspace.v1.ProjectService/ListBranchRules": "read",
|
|
67
|
-
"/toshi.workspace.v1.ProjectService/CreateProject": "
|
|
109
|
+
"/toshi.workspace.v1.ProjectService/CreateProject": "keyed",
|
|
68
110
|
"/toshi.workspace.v1.ProjectService/ClaimRepository": "none",
|
|
69
|
-
"/toshi.workspace.v1.ProjectService/CreateEnvironment": "
|
|
111
|
+
"/toshi.workspace.v1.ProjectService/CreateEnvironment": "keyed",
|
|
70
112
|
"/toshi.workspace.v1.ProjectService/DeleteEnvironment": "none",
|
|
71
113
|
"/toshi.workspace.v1.ProjectService/SetBranchRules": "none",
|
|
72
114
|
"/toshi.workspace.v1.ProjectService/CreateEmbedToken": "none",
|
|
73
115
|
"/toshi.workspace.v1.ProjectService/ListEmbedTokens": "read",
|
|
74
116
|
"/toshi.workspace.v1.ProjectService/RevokeEmbedToken": "none",
|
|
117
|
+
"/toshi.workspace.v1.ProjectService/GetProjectNetwork": "read",
|
|
118
|
+
"/toshi.workspace.v1.ProjectService/SetProjectNetwork": "none",
|
|
119
|
+
"/toshi.workspace.v1.ProjectService/GetEnvironmentNetwork": "read",
|
|
120
|
+
"/toshi.workspace.v1.ProjectService/SetEnvironmentNetwork": "none",
|
|
121
|
+
"/toshi.workspace.v1.ProjectService/GetEnvironmentRetention": "read",
|
|
122
|
+
"/toshi.workspace.v1.ProjectService/SetEnvironmentRetention": "none",
|
|
123
|
+
"/toshi.workspace.v1.ProjectService/SetEnvironmentVolumes": "none",
|
|
124
|
+
"/toshi.workspace.v1.ProjectService/UpdateEnvironment": "none",
|
|
75
125
|
"/toshi.workspace.v1.WebhookService/CreateWebhookEndpoint": "keyed",
|
|
76
126
|
"/toshi.workspace.v1.WebhookService/GetWebhookEndpoint": "read",
|
|
77
127
|
"/toshi.workspace.v1.WebhookService/ListWebhookEndpoints": "read",
|
|
@@ -80,6 +130,9 @@ const procedureRetryClass = {
|
|
|
80
130
|
"/toshi.workspace.v1.WebhookService/RotateWebhookEndpointSecret": "keyed",
|
|
81
131
|
"/toshi.workspace.v1.WebhookService/ListWebhookDeliveries": "read",
|
|
82
132
|
"/toshi.workspace.v1.WebhookService/ReplayWebhookEvents": "keyed",
|
|
133
|
+
// A retry returns the session the first call opened, with replayed set and
|
|
134
|
+
// no token, and opens nothing, so it cannot give the caller a session it can
|
|
135
|
+
// use.
|
|
83
136
|
"/toshi.workspace.v1.DisplayService/OpenDisplaySession": "none",
|
|
84
137
|
"/toshi.workspace.v1.DisplayService/GetDisplaySession": "read",
|
|
85
138
|
"/toshi.workspace.v1.DisplayService/ListDisplaySessions": "read",
|
|
@@ -88,7 +141,7 @@ const procedureRetryClass = {
|
|
|
88
141
|
"/toshi.workspace.v1.DisplayService/SendDisplayPointer": "none",
|
|
89
142
|
"/toshi.workspace.v1.DisplayService/SetDisplayClipboard": "none",
|
|
90
143
|
"/toshi.workspace.v1.DisplayService/TakeDisplayScreenshot": "none",
|
|
91
|
-
"/toshi.workspace.v1.ChannelService/CreateChannel": "
|
|
144
|
+
"/toshi.workspace.v1.ChannelService/CreateChannel": "keyed",
|
|
92
145
|
"/toshi.workspace.v1.ChannelService/ListChannels": "read",
|
|
93
146
|
"/toshi.workspace.v1.ChannelService/GetChannel": "read",
|
|
94
147
|
"/toshi.workspace.v1.ChannelService/GetChannelPolicy": "read",
|
|
@@ -101,28 +154,34 @@ const procedureRetryClass = {
|
|
|
101
154
|
"/toshi.workspace.v1.ChannelService/DeleteChannel": "none",
|
|
102
155
|
"/toshi.workspace.v1.ChannelService/ListForwards": "read",
|
|
103
156
|
"/toshi.workspace.v1.ChannelService/GetLink": "read",
|
|
157
|
+
"/toshi.workspace.v1.ChannelService/ReplayChannelMessage": "none",
|
|
158
|
+
"/toshi.workspace.v1.ChannelService/SendChannelMessage": "keyed",
|
|
159
|
+
"/toshi.workspace.v1.ChannelService/TailChannelMessages": "none",
|
|
104
160
|
"/toshi.workspace.v1.RecordingService/ListRecordings": "read",
|
|
105
161
|
"/toshi.workspace.v1.RecordingService/GetRecording": "read",
|
|
106
162
|
"/toshi.workspace.v1.RecordingService/CreateRecordingShareLink": "none",
|
|
107
163
|
"/toshi.workspace.v1.RecordingService/ListRecordingShareLinks": "read",
|
|
108
164
|
"/toshi.workspace.v1.RecordingService/RevokeRecordingShareLink": "none",
|
|
109
165
|
"/toshi.workspace.v1.VolumeService/ListVolumes": "read",
|
|
166
|
+
"/toshi.workspace.v1.VolumeService/GetVolume": "read",
|
|
110
167
|
"/toshi.workspace.v1.VolumeService/AttachVolume": "none",
|
|
111
168
|
"/toshi.workspace.v1.VolumeService/DetachVolume": "none",
|
|
169
|
+
"/toshi.workspace.v1.VolumeService/ListVolumeVersions": "read",
|
|
170
|
+
"/toshi.workspace.v1.VolumeService/RestoreVolume": "none",
|
|
171
|
+
"/toshi.workspace.v1.VolumeService/CreateVolume": "keyed",
|
|
172
|
+
"/toshi.workspace.v1.VolumeService/UpdateVolume": "none",
|
|
173
|
+
"/toshi.workspace.v1.VolumeService/DeleteVolume": "none",
|
|
112
174
|
"/toshi.workspace.v1.ArtifactService/ListArtifacts": "read",
|
|
113
175
|
"/toshi.workspace.v1.ArtifactService/GetArtifact": "read",
|
|
114
176
|
"/toshi.workspace.v1.ArtifactService/GetArtifactDownloadURL": "read",
|
|
115
177
|
"/toshi.workspace.v1.ArtifactUploadService/ReserveUpload": "none",
|
|
116
178
|
"/toshi.workspace.v1.ArtifactUploadService/CompleteUpload": "none",
|
|
117
179
|
"/toshi.workspace.v1.WorkspaceService/GetWorkspace": "read",
|
|
180
|
+
"/toshi.workspace.v1.WorkspaceService/UpdateWorkspace": "none",
|
|
118
181
|
"/toshi.workspace.v1.WorkspaceService/ListWorkspaces": "read",
|
|
119
182
|
"/toshi.workspace.v1.WorkspaceService/GetWorkspaceBilling": "read",
|
|
120
183
|
"/toshi.workspace.v1.WorkspaceService/GetWorkspaceStatement": "read",
|
|
121
|
-
"/toshi.workspace.v1.WorkspaceService/
|
|
122
|
-
"/toshi.workspace.v1.WorkspaceService/SetWorkspaceAllowOverflow": "none",
|
|
123
|
-
"/toshi.workspace.v1.WorkspaceService/SetWorkspaceAllowCacheWarm": "none",
|
|
124
|
-
"/toshi.workspace.v1.WorkspaceService/SetWorkspaceCancelRunOnVmDeath": "none",
|
|
125
|
-
"/toshi.workspace.v1.WorkspaceService/SetWorkspaceSpendLimits": "none",
|
|
184
|
+
"/toshi.workspace.v1.WorkspaceService/GetWorkspaceUpcomingInvoice": "read",
|
|
126
185
|
"/toshi.workspace.v1.WorkspaceService/ListEgressIPs": "read",
|
|
127
186
|
"/toshi.workspace.v1.WorkspaceService/GetUsage": "read",
|
|
128
187
|
"/toshi.workspace.v1.WorkspaceService/GetQueueTimes": "read",
|
|
@@ -131,38 +190,99 @@ const procedureRetryClass = {
|
|
|
131
190
|
"/toshi.workspace.v1.WorkspaceService/CreateCheckoutSession": "none",
|
|
132
191
|
"/toshi.workspace.v1.WorkspaceService/CreateBillingPortalSession": "none",
|
|
133
192
|
"/toshi.workspace.v1.WorkspaceService/GetVersion": "read",
|
|
193
|
+
"/toshi.workspace.v1.WorkspaceService/ListSeats": "read",
|
|
194
|
+
"/toshi.workspace.v1.WorkspaceService/AssignSeat": "none",
|
|
195
|
+
"/toshi.workspace.v1.WorkspaceService/RemoveSeat": "none",
|
|
196
|
+
"/toshi.workspace.v1.WorkspaceService/DeleteWorkspace": "none",
|
|
197
|
+
"/toshi.workspace.v1.WorkspaceService/CancelWorkspaceDeletion": "none",
|
|
134
198
|
"/toshi.workspace.v1.HostJobService/ListHostJobs": "read",
|
|
135
199
|
"/toshi.workspace.v1.HostJobService/GetHostJob": "read",
|
|
136
200
|
"/toshi.workspace.v1.HostJobService/CancelHostJob": "none",
|
|
137
201
|
"/toshi.workspace.v1.HostJobService/GetHostJobFailure": "read",
|
|
138
202
|
"/toshi.workspace.v1.HostJobService/GetHostJobTelemetry": "read",
|
|
139
|
-
|
|
203
|
+
// Not a read: it hands out a debug key and a suspended workspace is refused
|
|
204
|
+
// it; the request carries no request_key, so it is never retried.
|
|
205
|
+
"/toshi.workspace.v1.HostJobService/GetHostJobDebugAccess": "none",
|
|
140
206
|
"/toshi.workspace.v1.HostJobService/StopHostJobDebug": "none",
|
|
141
207
|
"/toshi.workspace.v1.HostService/ListHosts": "read",
|
|
142
208
|
"/toshi.workspace.v1.HostService/ListHostStorage": "read",
|
|
143
209
|
"/toshi.workspace.v1.HostService/ListHostAlerts": "read",
|
|
144
210
|
"/toshi.workspace.v1.HostService/SetHostState": "none",
|
|
145
211
|
"/toshi.workspace.v1.HostService/RetireHost": "none",
|
|
146
|
-
"/toshi.workspace.v1.HostService/CreateCloudHost": "
|
|
212
|
+
"/toshi.workspace.v1.HostService/CreateCloudHost": "keyed",
|
|
147
213
|
"/toshi.workspace.v1.HostService/CheckCloudHost": "none",
|
|
148
214
|
"/toshi.workspace.v1.HostService/CreateEnrollmentToken": "none",
|
|
149
215
|
"/toshi.workspace.v1.TokenService/ListTokens": "read",
|
|
150
216
|
"/toshi.workspace.v1.TokenService/CreateWorkspaceToken": "none",
|
|
217
|
+
"/toshi.workspace.v1.TokenService/RollWorkspaceToken": "none",
|
|
218
|
+
"/toshi.workspace.v1.TokenService/UpdateToken": "none",
|
|
151
219
|
"/toshi.workspace.v1.TokenService/RevokeToken": "none",
|
|
152
220
|
"/toshi.workspace.v1.AuditEventService/ListAuditEvents": "read",
|
|
221
|
+
"/toshi.workspace.v1.AuditEventService/ExportAuditEvents": "none",
|
|
153
222
|
"/toshi.workspace.v1.FillerService/ListFillers": "read",
|
|
154
223
|
"/toshi.workspace.v1.FillerService/PutFiller": "none",
|
|
155
224
|
"/toshi.workspace.v1.FillerService/DeleteFiller": "none",
|
|
156
225
|
"/toshi.workspace.v1.FillerService/SetFillerEnabled": "none",
|
|
157
226
|
"/toshi.workspace.v1.FillerService/ListFillerRuns": "read",
|
|
227
|
+
"/toshi.workspace.v1.GrantService/CreateGrant": "keyed",
|
|
228
|
+
"/toshi.workspace.v1.GrantService/GetGrant": "read",
|
|
229
|
+
"/toshi.workspace.v1.GrantService/ListGrants": "read",
|
|
230
|
+
"/toshi.workspace.v1.GrantService/AcceptGrant": "none",
|
|
231
|
+
"/toshi.workspace.v1.GrantService/DeclineGrant": "none",
|
|
232
|
+
"/toshi.workspace.v1.GrantService/UpdateGrant": "none",
|
|
233
|
+
"/toshi.workspace.v1.GrantService/RevokeGrant": "none",
|
|
234
|
+
"/toshi.workspace.v1.GrantService/LeaveGrant": "none",
|
|
158
235
|
"/toshi.workspace.v1.StorageProviderService/ListStorageProviders": "read",
|
|
159
|
-
"/toshi.workspace.v1.StorageProviderService/CreateStorageProvider": "
|
|
236
|
+
"/toshi.workspace.v1.StorageProviderService/CreateStorageProvider": "keyed",
|
|
160
237
|
"/toshi.workspace.v1.StorageProviderService/CheckStorageProvider": "none",
|
|
161
238
|
"/toshi.workspace.v1.StorageProviderService/DeleteStorageProvider": "none",
|
|
162
239
|
"/toshi.workspace.v1.ProductService/ListProducts": "read",
|
|
163
240
|
"/toshi.workspace.v1.ProductService/UpdateProduct": "none",
|
|
164
|
-
"/toshi.workspace.v1.ProductService/RemoveProduct": "none",
|
|
165
241
|
"/toshi.workspace.v1.DiscoveryService/Discover": "read",
|
|
242
|
+
"/toshi.workspace.v1.ConnectionService/ListConnections": "read",
|
|
243
|
+
"/toshi.workspace.v1.ConnectionService/GetConnection": "read",
|
|
244
|
+
"/toshi.workspace.v1.ConnectionService/CreateConnection": "keyed",
|
|
245
|
+
"/toshi.workspace.v1.ConnectionService/DeleteConnection": "keyed",
|
|
246
|
+
"/toshi.workspace.v1.ConnectionService/RotateConnectionSecret": "keyed",
|
|
247
|
+
"/toshi.workspace.v1.ConnectionService/UpdateConnection": "none",
|
|
248
|
+
"/toshi.workspace.v1.ConnectionService/ListConnectionDeliveries": "read",
|
|
249
|
+
"/toshi.workspace.v1.ConnectionService/GetConnectionDelivery": "read",
|
|
250
|
+
"/toshi.workspace.v1.ConnectionService/ReplayConnectionDelivery": "keyed",
|
|
251
|
+
"/toshi.workspace.v1.ImageService/BuildImage": "none",
|
|
252
|
+
"/toshi.workspace.v1.ImageService/CreateImage": "keyed",
|
|
253
|
+
"/toshi.workspace.v1.ImageService/DeleteImage": "none",
|
|
254
|
+
"/toshi.workspace.v1.ImageService/GetImage": "read",
|
|
255
|
+
"/toshi.workspace.v1.ImageService/ListImages": "read",
|
|
256
|
+
"/toshi.workspace.v1.ImageService/UpdateImage": "none",
|
|
257
|
+
"/toshi.workspace.v1.RoleService/ListRoles": "read",
|
|
258
|
+
"/toshi.workspace.v1.RoleService/CreateRole": "keyed",
|
|
259
|
+
"/toshi.workspace.v1.RoleService/UpdateRole": "none",
|
|
260
|
+
"/toshi.workspace.v1.RoleService/DeleteRole": "none",
|
|
261
|
+
"/toshi.workspace.v1.RoleService/ListRoleGrants": "read",
|
|
262
|
+
"/toshi.workspace.v1.RoleService/GrantRole": "none",
|
|
263
|
+
"/toshi.workspace.v1.RoleService/RevokeRole": "none",
|
|
264
|
+
"/toshi.workspace.v1.RoleService/ListMembers": "read",
|
|
265
|
+
"/toshi.workspace.v1.RoleService/RemoveMember": "none",
|
|
266
|
+
"/toshi.workspace.v1.RoleService/CreateInvitation": "none",
|
|
267
|
+
"/toshi.workspace.v1.RoleService/ListInvitations": "read",
|
|
268
|
+
"/toshi.workspace.v1.RoleService/RevokeInvitation": "none",
|
|
269
|
+
// A repeat answers ALREADY_EXISTS ("a caller who is already a member fails
|
|
270
|
+
// with ALREADY_EXISTS"), so a retry would report failure for a call that
|
|
271
|
+
// worked; its permission's level is read although it changes state.
|
|
272
|
+
"/toshi.workspace.v1.RoleService/AcceptInvitation": "none",
|
|
273
|
+
"/toshi.workspace.v1.RoleService/ApproveMember": "none",
|
|
274
|
+
"/toshi.workspace.v1.RoleService/DeclineMember": "none",
|
|
275
|
+
"/toshi.workspace.v1.TriggerService/CreateTrigger": "keyed",
|
|
276
|
+
"/toshi.workspace.v1.TriggerService/GetTrigger": "read",
|
|
277
|
+
"/toshi.workspace.v1.TriggerService/ListTriggers": "read",
|
|
278
|
+
"/toshi.workspace.v1.TriggerService/UpdateTrigger": "keyed",
|
|
279
|
+
"/toshi.workspace.v1.TriggerService/DeleteTrigger": "keyed",
|
|
280
|
+
"/toshi.workspace.v1.DomainService/CreateDomain": "keyed",
|
|
281
|
+
"/toshi.workspace.v1.DomainService/GetDomain": "read",
|
|
282
|
+
"/toshi.workspace.v1.DomainService/ListDomains": "read",
|
|
283
|
+
"/toshi.workspace.v1.DomainService/VerifyDomain": "keyed",
|
|
284
|
+
"/toshi.workspace.v1.DomainService/IssueDomain": "keyed",
|
|
285
|
+
"/toshi.workspace.v1.DomainService/DeleteDomain": "none",
|
|
166
286
|
};
|
|
167
287
|
/** The retry class of a generated procedure path; an unknown path is "none". */
|
|
168
288
|
export function retryClassFor(procedure) {
|
|
@@ -263,9 +383,96 @@ export function backoffDelay(policy, attempt) {
|
|
|
263
383
|
if (limit <= 0) {
|
|
264
384
|
return 0;
|
|
265
385
|
}
|
|
266
|
-
|
|
386
|
+
// The jitter is not a secret, so a non-cryptographic source is enough.
|
|
387
|
+
const random = Math.random();
|
|
267
388
|
return Math.floor(random * (limit + 1));
|
|
268
389
|
}
|
|
390
|
+
/**
|
|
391
|
+
* How long the server asked the client to wait before a retry, in
|
|
392
|
+
* milliseconds, or undefined when it named no wait. A RESOURCE_EXHAUSTED
|
|
393
|
+
* refusal names the wait in a google.rpc.RetryInfo detail and in its
|
|
394
|
+
* Retry-After header, the detail winning when both are there; the API's rate
|
|
395
|
+
* limit sends both, with the same whole seconds. Such a refusal is retried
|
|
396
|
+
* only when it names a wait: one without, such as a plan's limit, does not
|
|
397
|
+
* pass on a retry. An HTTP 429 without a Connect error body reaches the
|
|
398
|
+
* client as Code.Unavailable with the message "HTTP 429" and the response's
|
|
399
|
+
* headers (connect 2.2.0 protocol-connect/validate-response.js:33), and names
|
|
400
|
+
* the wait in its Retry-After. Every other error names none.
|
|
401
|
+
*/
|
|
402
|
+
export function serverWaitMs(err) {
|
|
403
|
+
if (!(err instanceof ConnectError)) {
|
|
404
|
+
return undefined;
|
|
405
|
+
}
|
|
406
|
+
if (err.code === Code.ResourceExhausted) {
|
|
407
|
+
for (const detail of err.details) {
|
|
408
|
+
if (!("type" in detail) || detail.type !== "google.rpc.RetryInfo") {
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
const ms = decodeRetryInfoMs(detail.value);
|
|
412
|
+
if (ms !== undefined) {
|
|
413
|
+
return ms;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return parseRetryAfterMs(err.metadata.get("Retry-After"));
|
|
417
|
+
}
|
|
418
|
+
if (err.code === Code.Unavailable && err.rawMessage === "HTTP 429") {
|
|
419
|
+
return parseRetryAfterMs(err.metadata.get("Retry-After"));
|
|
420
|
+
}
|
|
421
|
+
return undefined;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* The delay before retry attempt + 1: the backoff's, or the server's wait when
|
|
425
|
+
* that is longer, never more than maxBackoffMs.
|
|
426
|
+
*/
|
|
427
|
+
export function retryDelayMs(policy, attempt, waitMs) {
|
|
428
|
+
return Math.min(Math.max(backoffDelay(policy, attempt), waitMs ?? 0), policy.maxBackoffMs);
|
|
429
|
+
}
|
|
430
|
+
/** A Retry-After of whole seconds, the form the API sends; a date or anything else names no wait. */
|
|
431
|
+
function parseRetryAfterMs(value) {
|
|
432
|
+
if (value === null || !/^\s*\d+\s*$/.test(value)) {
|
|
433
|
+
return undefined;
|
|
434
|
+
}
|
|
435
|
+
const ms = Number(value.trim()) * 1000;
|
|
436
|
+
return Number.isSafeInteger(ms) ? ms : undefined;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Reads google.rpc.RetryInfo's retry_delay (1), a google.protobuf.Duration of
|
|
440
|
+
* seconds (1) and nanos (2), as milliseconds, reading the wire format itself
|
|
441
|
+
* as errors.ts does for ErrorInfo.
|
|
442
|
+
*/
|
|
443
|
+
function decodeRetryInfoMs(bytes) {
|
|
444
|
+
try {
|
|
445
|
+
const reader = new BinaryReader(bytes);
|
|
446
|
+
while (reader.pos < reader.len) {
|
|
447
|
+
const [field, wireType] = reader.tag();
|
|
448
|
+
if (field !== 1 || wireType !== WireType.LengthDelimited) {
|
|
449
|
+
reader.skip(wireType);
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
const duration = new BinaryReader(reader.bytes());
|
|
453
|
+
let seconds = 0;
|
|
454
|
+
let nanos = 0;
|
|
455
|
+
while (duration.pos < duration.len) {
|
|
456
|
+
const [part, partType] = duration.tag();
|
|
457
|
+
if (part === 1 && partType === WireType.Varint) {
|
|
458
|
+
seconds = Number(duration.int64());
|
|
459
|
+
}
|
|
460
|
+
else if (part === 2 && partType === WireType.Varint) {
|
|
461
|
+
nanos = duration.int32();
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
duration.skip(partType);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
const ms = seconds * 1000 + Math.ceil(nanos / 1e6);
|
|
468
|
+
return seconds >= 0 && nanos >= 0 && Number.isSafeInteger(ms) ? ms : undefined;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
catch {
|
|
472
|
+
return undefined;
|
|
473
|
+
}
|
|
474
|
+
return undefined;
|
|
475
|
+
}
|
|
269
476
|
/** Sleeps, rejecting at once when the signal aborts (retry.go:186-200). */
|
|
270
477
|
export function sleep(ms, signal) {
|
|
271
478
|
return new Promise((resolve, reject) => {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { DeleteServiceAlarmRequest, DeleteServiceAlarmResponse, GetServiceAlarmRequest, GetServiceAlarmResponse, ListServiceAlarmsRequest, ListServiceAlarmsResponse, PauseServiceAlarmRequest, PauseServiceAlarmResponse, ResumeServiceAlarmRequest, ResumeServiceAlarmResponse, RunServiceAlarmNowRequest, RunServiceAlarmNowResponse, SetServiceAlarmRequest, SetServiceAlarmResponse } from "../gen/toshi/workspace/v1/workloads_pb.js";
|
|
2
|
+
/**
|
|
3
|
+
* The alarms the fake keeps for any service ID: the fake holds no services. It
|
|
4
|
+
* has no clock, so an alarm publishes nothing on its own and a cron alarm has
|
|
5
|
+
* no next instant.
|
|
6
|
+
*/
|
|
7
|
+
export declare class AlarmStore {
|
|
8
|
+
private readonly alarms;
|
|
9
|
+
private readonly runs;
|
|
10
|
+
/** Creates the alarm at generation 1, or replaces it at the next generation, resumed. */
|
|
11
|
+
set(request: SetServiceAlarmRequest): SetServiceAlarmResponse;
|
|
12
|
+
/** Returns the alarm and its most recent occurrences, newest first. */
|
|
13
|
+
get(request: GetServiceAlarmRequest): GetServiceAlarmResponse;
|
|
14
|
+
/** Returns every alarm of the service, by name. */
|
|
15
|
+
list(request: ListServiceAlarmsRequest): ListServiceAlarmsResponse;
|
|
16
|
+
/** Removes an alarm and its occurrences. */
|
|
17
|
+
delete(request: DeleteServiceAlarmRequest): DeleteServiceAlarmResponse;
|
|
18
|
+
/** Pauses an alarm; pausing a paused alarm changes nothing. */
|
|
19
|
+
pause(request: PauseServiceAlarmRequest): PauseServiceAlarmResponse;
|
|
20
|
+
/** Resumes an alarm; resuming a running alarm changes nothing. */
|
|
21
|
+
resume(request: ResumeServiceAlarmRequest): ResumeServiceAlarmResponse;
|
|
22
|
+
/**
|
|
23
|
+
* Records a manual occurrence at the alarm's generation. A request key the
|
|
24
|
+
* fake has already answered returns the same occurrence.
|
|
25
|
+
*/
|
|
26
|
+
runNow(request: RunServiceAlarmNowRequest): RunServiceAlarmNowResponse;
|
|
27
|
+
/** Returns the alarm of a service with this name. */
|
|
28
|
+
private alarm;
|
|
29
|
+
}
|