-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsync-plugin.wit
More file actions
248 lines (230 loc) · 13 KB
/
Copy pathsync-plugin.wit
File metadata and controls
248 lines (230 loc) · 13 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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
package icp:sync-plugin@0.2.0;
/// Types shared between the host runtime and sync plugins.
interface types {
/// Whether a canister call is an update or a query.
enum call-type { update, query }
/// A directory the host made readable for the plugin.
record dir-input {
/// The map key this directory was declared under in the manifest, or
/// `none` when `dirs` was written as a plain list. Several entries share
/// one key when a key maps to a list of directories.
key: option<string>,
/// Path of the directory as declared in the manifest (relative to the
/// canister directory). It is readable at this same path. Entries may
/// repeat a path or name a directory inside another entry's; the host
/// preopens each distinct tree once, so such an entry is read through
/// the preopen that covers it.
path: string,
}
/// A file the host read on behalf of the plugin.
record file-input {
/// The map key this file was declared under in the manifest, or `none`
/// when `files` was written as a plain list. Several entries share one
/// key when a key maps to a list of files.
key: option<string>,
/// Path of the file as declared in the manifest (relative to
/// the canister directory).
name: string,
/// UTF-8 contents of the file.
content: string,
}
/// A key-value field declared in the manifest step's `fields` setting.
/// The host passes the plugin's declared fields inline; how they are
/// interpreted is up to the plugin.
record field-input {
/// Field name, as spelled in the manifest.
name: string,
/// Field value.
value: string,
}
/// An entry in the project's canister ID mapping table: a canister name
/// and the textual principal it resolves to in the environment being synced.
record canister-id-entry {
/// The canister's fully-qualified project key: the subproject it belongs
/// to (a path relative to the app root) joined to its local name by a
/// single colon, e.g. "services/open-accounts:backend". A canister
/// defined directly in the app root has no subproject prefix and appears
/// as its bare local name, e.g. "backend".
///
/// Every canister the subproject being synced can name for itself is
/// additionally listed under that name (a duplicate entry with the same
/// `id`): its own canisters under their bare local names, and the
/// canisters of subprojects nested below it under the
/// `subproject:local` key those would have if it were the app root —
/// e.g. for "services/crm:backend", the canister
/// "services/crm/vendor/ledger:ledger" is also listed as
/// "vendor/ledger:ledger". These are the keys the subproject's own
/// manifest uses, so a plugin addresses the same canister by the same
/// name whether the subproject is deployed standalone or vendored into
/// a workspace. Such a name always means what the subproject means by
/// it, so a canister elsewhere in the workspace whose key is spelled
/// the same way is not listed under it.
name: string,
/// Textual principal the name resolves to for this environment.
id: string,
}
/// Which canister a `canister-call-request` targets.
///
/// A plugin may target the canister being synced, or any canister listed in
/// the sync step's `canisters` list — always by that canister's name.
/// Targeting a canister that was not listed is rejected by the host.
variant call-target {
/// The canister being synced (`sync-exec-input.canister-id`). Always
/// permitted, whether or not it also appears in `canisters`.
host,
/// A canister from the `canisters` list, identified by name, spelled
/// exactly as it appears in `sync-exec-input.canister-ids` — for a
/// canister the synced canister's own subproject names, the name that
/// subproject uses; otherwise the app-root-relative `subproject:local`
/// key. The host resolves it against that mapping table.
name(string),
}
/// Input passed by the runtime to the plugin's exec() export.
record sync-exec-input {
/// Textual principal of the canister being synced.
canister-id: string,
/// Name of the environment being synced (e.g. "production", "local").
environment: string,
/// Directories declared in the manifest step's `dirs` setting.
/// The host makes each entry readable via WASI preopens; the plugin
/// traverses them with standard `wasi:filesystem` (e.g. Rust's `std::fs`).
/// Each entry carries the map key it was declared under, if any (see
/// `dir-input`).
dirs: list<dir-input>,
/// Files declared in the manifest step's `files` setting, read by
/// the host and passed inline. The plugin decides how to use them.
/// Each entry carries the map key it was declared under, if any (see
/// `file-input`).
files: list<file-input>,
/// Key-value fields declared in the manifest step's `fields` setting,
/// passed inline. The plugin decides how to use them.
fields: list<field-input>,
/// Textual principal of the signing identity used for canister calls.
identity-principal: string,
/// Textual principal of the proxy canister, if one was configured via
/// `--proxy`. None when no proxy is in use.
proxy-canister-id: option<string>,
/// Name→principal mapping for every named canister in the project for
/// the environment being synced, sorted by name. Informational: the
/// plugin may use it to resolve canister names it knows about. Being
/// listed here does not grant permission to call a canister — that
/// still requires listing it in `canisters` (see `call-target`).
canister-ids: list<canister-id-entry>,
}
/// A request to call a method on a canister.
record canister-call-request {
/// Which canister to call. `host` targets the canister being synced;
/// `name` targets a canister listed in the sync step's `canisters`
/// list.
target: call-target,
/// The canister method to call.
method: string,
/// Candid-encoded argument bytes. The plugin is responsible for
/// encoding; the host forwards these bytes unchanged.
arg: list<u8>,
/// Whether to perform an `update` or `query` call.
call-type: call-type,
/// When true, the call bypasses any proxy canister configured via
/// `--proxy`, going directly to the target canister. When false
/// (the default), update calls are routed through the proxy if one
/// is configured; query calls always go directly to the target
/// canister regardless of this flag.
direct: bool,
/// Cycles to attach to a proxied update call. Only meaningful when
/// `direct` is `false`, a proxy canister is configured, and
/// `call-type` is `update`; silently ignored for direct calls and
/// for query calls.
cycles: u64,
}
/// A request to read a canister's metadata section.
record metadata-section-request {
/// Which canister to read from. The same rule as
/// `canister-call-request.target` applies: `host` is always permitted,
/// a `name` must appear in the sync step's `canisters` list.
target: call-target,
/// Name of the metadata section, as spelled in the wasm module's custom
/// section minus the `icp:public `/`icp:private ` prefix — e.g.
/// `candid:service`.
name: string,
/// When true, the section is read straight from the target canister
/// with a certified `read_state` request signed by the sync identity,
/// which reaches a private section only if that identity controls the
/// target. When false (the default), the read is routed through the
/// proxy canister configured via `--proxy` — as a call to the
/// management canister's `canister_metadata` method, so a private
/// section gated on the proxy's control is readable. With no proxy
/// configured the read goes directly either way.
direct: bool,
}
/// A request to set one of a canister's environment variables.
record set-environment-variable-request {
/// Which canister to set the variable on. The same rule as
/// `canister-call-request.target` applies: `host` is always permitted,
/// a `name` must appear in the sync step's `canisters` list.
target: call-target,
/// Name of the environment variable, spelled as the canister reads it.
name: string,
/// Value to set it to, replacing whatever value the target currently
/// has under this name.
value: string,
/// When true, the update is signed by the sync identity, which must
/// control the target for it to be accepted. When false (the default),
/// it is made by the proxy canister configured via `--proxy`, and it is
/// the proxy that must control the target — the same arrangement
/// proxied update calls rely on. With no proxy configured the update is
/// signed by the sync identity either way.
direct: bool,
}
}
/// The complete interface of a sync plugin.
world sync-plugin {
use types.{sync-exec-input, canister-call-request, metadata-section-request, set-environment-variable-request, call-target, canister-id-entry, dir-input, file-input, field-input};
// -------------------------------------------------------------------------
// Host functions (imports) — provided by icp-cli, called by the plugin
// -------------------------------------------------------------------------
/// Make an update or query call to a canister.
/// The `req.target` selects the canister: the one being synced (`host`), or
/// a canister listed in the sync step's `canisters` list, by name. A target
/// that was not listed is rejected without making a call.
/// Returns the raw Candid-encoded response bytes on success or an error
/// message on failure. The plugin is responsible for decoding.
import canister-call: func(req: canister-call-request) -> result<list<u8>, string>;
/// Read a metadata section from a canister.
/// The `req.target` selects the canister under the same rule as
/// `canister-call`: the canister being synced (`host`), or one listed in
/// the sync step's `canisters` list, by name.
/// A direct read is a certified `read_state` request signed by the sync
/// identity; a proxied read (`direct` false, with `--proxy` configured) is
/// a call to the management canister's `canister_metadata` method made by
/// the proxy, which reaches sections private to the proxy's control.
/// Returns the section's raw bytes on success, or `none` when the target
/// provably has no section by that name — including when it has no module
/// installed at all, and so no sections. A section the reader may not have
/// is an error, as is a canister that does not exist or any other failed
/// read. The plugin is responsible for interpreting the bytes.
import canister-metadata-section: func(req: metadata-section-request) -> result<option<list<u8>>, string>;
/// Set an environment variable on a canister, leaving the target's other
/// environment variables — and the rest of its settings — as they are.
/// The `req.target` selects the canister under the same rule as
/// `canister-call`: the canister being synced (`host`), or one listed in
/// the sync step's `canisters` list, by name.
/// Returns an error message on failure.
import canister-set-environment-variable: func(req: set-environment-variable-request) -> result<_, string>;
// The plugin's stdout is captured and shown as transient progress in
// the rolling step view of icp-cli; it is discarded when the step ends.
//
// The plugin's stderr is captured and shown in the rolling step view AND
// printed persistently after the step completes successfully. (On
// failure, the error message and the rolling-view dump already surface
// stderr, so it is not reprinted.)
//
// Use stdout for in-flight progress chatter the user doesn't need to see
// once the step is done. Use stderr for messages the user must still see
// after the step completes — warnings, summaries, deprecation notices.
// -------------------------------------------------------------------------
// Plugin exports — implemented by the plugin, called by the host
// -------------------------------------------------------------------------
/// Execute the sync plugin for the canister being synced. Returns an
/// error message on failure.
export exec: func(input: sync-exec-input) -> result<_, string>;
}