microcosm-emitter-interface 0.5.0__py3-none-any.whl
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.
- microcosm_emitter_interface/__init__.py +28 -0
- microcosm_emitter_interface/client.py +355 -0
- microcosm_emitter_interface/fixtures/protocol-v1/host-close.json +10 -0
- microcosm_emitter_interface/fixtures/protocol-v1/host-hello.json +14 -0
- microcosm_emitter_interface/fixtures/protocol-v1/host-launch.json +22 -0
- microcosm_emitter_interface/fixtures/protocol-v1/host-message-envelope.json +13 -0
- microcosm_emitter_interface/fixtures/protocol-v1/host-ping.json +9 -0
- microcosm_emitter_interface/fixtures/protocol-v1/host-query-envelope.json +14 -0
- microcosm_emitter_interface/fixtures/protocol-v1/orrery-configuration.json +10 -0
- microcosm_emitter_interface/fixtures/protocol-v1/orrery-enqueue.json +35 -0
- microcosm_emitter_interface/fixtures/protocol-v1/orrery-inventory.json +22 -0
- microcosm_emitter_interface/fixtures/protocol-v1/orrery-receipt.json +13 -0
- microcosm_emitter_interface/fixtures/protocol-v1/orrery-retry.json +13 -0
- microcosm_emitter_interface/fixtures/protocol-v1/telemetry-calibration-progress.json +15 -0
- microcosm_emitter_interface/fixtures/protocol-v1/telemetry-complete.json +9 -0
- microcosm_emitter_interface/fixtures/protocol-v1/telemetry-configuration.json +22 -0
- microcosm_emitter_interface/fixtures/protocol-v1/telemetry-emit.json +16 -0
- microcosm_emitter_interface/fixtures/protocol-v1/telemetry-fail-without-code.json +13 -0
- microcosm_emitter_interface/fixtures/protocol-v1/telemetry-fail.json +14 -0
- microcosm_emitter_interface/fixtures/protocol-v1/telemetry-progress.json +16 -0
- microcosm_emitter_interface/fixtures/protocol-v1/telemetry-refusal.json +17 -0
- microcosm_emitter_interface/fixtures/protocol-v1/telemetry-stage.json +15 -0
- microcosm_emitter_interface/fixtures/protocol-v1/telemetry-transition-calibration-progress.json +15 -0
- microcosm_emitter_interface/fixtures/protocol-v1/telemetry-transition-stage.json +13 -0
- microcosm_emitter_interface/launcher.py +273 -0
- microcosm_emitter_interface/orrery.py +156 -0
- microcosm_emitter_interface/protocol.py +32 -0
- microcosm_emitter_interface/protocol_fixtures.py +140 -0
- microcosm_emitter_interface/py.typed +0 -0
- microcosm_emitter_interface/serialization.py +28 -0
- microcosm_emitter_interface/spool.py +11 -0
- microcosm_emitter_interface/telemetry.py +351 -0
- microcosm_emitter_interface/transport.py +139 -0
- microcosm_emitter_interface-0.5.0.dist-info/METADATA +45 -0
- microcosm_emitter_interface-0.5.0.dist-info/RECORD +37 -0
- microcosm_emitter_interface-0.5.0.dist-info/WHEEL +4 -0
- microcosm_emitter_interface-0.5.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Typed hand-overs from Microcosm builds to the separately installed emitter.
|
|
2
|
+
|
|
3
|
+
Importing this package starts no process and opens no socket or file.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from microcosm_emitter_interface.client import EmitterClient, RequestFailure
|
|
7
|
+
from microcosm_emitter_interface.launcher import (
|
|
8
|
+
COMMAND_ENVIRONMENT_VARIABLE,
|
|
9
|
+
DEFAULT_COMMAND,
|
|
10
|
+
EmitterStartupError,
|
|
11
|
+
)
|
|
12
|
+
from microcosm_emitter_interface.orrery import OrrerySettings
|
|
13
|
+
from microcosm_emitter_interface.protocol import PROTOCOL_VERSION
|
|
14
|
+
from microcosm_emitter_interface.spool import default_spool_path
|
|
15
|
+
from microcosm_emitter_interface.telemetry import TelemetryRun, TelemetrySettings
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"COMMAND_ENVIRONMENT_VARIABLE",
|
|
19
|
+
"DEFAULT_COMMAND",
|
|
20
|
+
"PROTOCOL_VERSION",
|
|
21
|
+
"EmitterClient",
|
|
22
|
+
"EmitterStartupError",
|
|
23
|
+
"OrrerySettings",
|
|
24
|
+
"RequestFailure",
|
|
25
|
+
"TelemetryRun",
|
|
26
|
+
"TelemetrySettings",
|
|
27
|
+
"default_spool_path",
|
|
28
|
+
]
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"""One typed call per kind of hand-over from a build to its local emitter.
|
|
2
|
+
|
|
3
|
+
Starting the client starts the installed ``microcosm-emitter`` command as this
|
|
4
|
+
process's child. No call raises into the build: not when the emitter is missing,
|
|
5
|
+
slow or refusing, and not when an argument cannot be converted to JSON (for
|
|
6
|
+
example a multi-element NumPy array or a self-referencing mapping). Each call
|
|
7
|
+
converts its arguments inside its error handling, reports that it was not
|
|
8
|
+
accepted, records why in ``EmitterClient.last_failure``, and the build continues.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import threading
|
|
16
|
+
import time
|
|
17
|
+
from collections.abc import Callable, Mapping
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, Literal
|
|
21
|
+
|
|
22
|
+
from microcosm_emitter_interface.launcher import (
|
|
23
|
+
EmitterProcess,
|
|
24
|
+
EmitterStartupError,
|
|
25
|
+
start_emitter,
|
|
26
|
+
)
|
|
27
|
+
from microcosm_emitter_interface.orrery import (
|
|
28
|
+
OrrerySettings,
|
|
29
|
+
PublicationFileRole,
|
|
30
|
+
PublicationInventory,
|
|
31
|
+
PublicationReceipt,
|
|
32
|
+
answered_inventory,
|
|
33
|
+
answered_receipt,
|
|
34
|
+
enqueue_message,
|
|
35
|
+
inventory_message,
|
|
36
|
+
publication_id_of,
|
|
37
|
+
receipt_message,
|
|
38
|
+
retry_message,
|
|
39
|
+
unavailable_receipt,
|
|
40
|
+
)
|
|
41
|
+
from microcosm_emitter_interface.protocol import (
|
|
42
|
+
ACKNOWLEDGEMENT_TIMEOUT_SECONDS,
|
|
43
|
+
FILE_REQUEST_TIMEOUT_SECONDS,
|
|
44
|
+
ORRERY_MODULE,
|
|
45
|
+
STARTUP_TIMEOUT_SECONDS,
|
|
46
|
+
TELEMETRY_MODULE,
|
|
47
|
+
)
|
|
48
|
+
from microcosm_emitter_interface.spool import default_spool_path
|
|
49
|
+
from microcosm_emitter_interface.telemetry import (
|
|
50
|
+
TelemetryCommand,
|
|
51
|
+
TelemetryRecorder,
|
|
52
|
+
TelemetrySettings,
|
|
53
|
+
telemetry_message,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
STARTUP_WARNING = (
|
|
57
|
+
"Local Microcosm emitter failed to start ({reason}); the build will continue."
|
|
58
|
+
)
|
|
59
|
+
REQUEST_WARNING = (
|
|
60
|
+
"The local Microcosm emitter did not accept a {kind} request "
|
|
61
|
+
"({reason}, {error_type}); the build will continue."
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
type RequestFailureReason = Literal[
|
|
65
|
+
# No emitter is running, or it did not load the call's module.
|
|
66
|
+
"emitter_unavailable",
|
|
67
|
+
# The call's arguments could not be converted to a protocol message.
|
|
68
|
+
"invalid_arguments",
|
|
69
|
+
# The emitter refused the request, or did not answer in time.
|
|
70
|
+
"not_accepted",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
type MessageBuilder = Callable[[], Mapping[str, Any]]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(frozen=True)
|
|
77
|
+
class RequestFailure:
|
|
78
|
+
"""Why a call reported that the emitter did not accept it.
|
|
79
|
+
|
|
80
|
+
``error_type`` is only the exception's type name, never its message.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
kind: str
|
|
84
|
+
reason: RequestFailureReason
|
|
85
|
+
error_type: str | None = None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class EmitterClient:
|
|
89
|
+
"""Typed hand-overs to one emitter process owned by this build."""
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
process: EmitterProcess | None = None,
|
|
94
|
+
*,
|
|
95
|
+
startup_error: EmitterStartupError | None = None,
|
|
96
|
+
) -> None:
|
|
97
|
+
self._process = process
|
|
98
|
+
self.startup_error = startup_error
|
|
99
|
+
self._closed = False
|
|
100
|
+
self._warned: set[str] = set()
|
|
101
|
+
self._warning_lock = threading.Lock()
|
|
102
|
+
#: The most recent call that reported failure, and why.
|
|
103
|
+
self.last_failure: RequestFailure | None = None
|
|
104
|
+
self.telemetry = TelemetryRecorder(self)
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def start(
|
|
108
|
+
cls,
|
|
109
|
+
*,
|
|
110
|
+
telemetry: TelemetrySettings | None = None,
|
|
111
|
+
orrery: OrrerySettings | None = None,
|
|
112
|
+
spool_path: Path | str | None = None,
|
|
113
|
+
command: str | os.PathLike[str] | None = None,
|
|
114
|
+
startup_timeout: float = STARTUP_TIMEOUT_SECONDS,
|
|
115
|
+
) -> EmitterClient:
|
|
116
|
+
"""Start the selected modules; return an unavailable client on failure."""
|
|
117
|
+
try:
|
|
118
|
+
path = Path(spool_path) if spool_path is not None else default_spool_path()
|
|
119
|
+
modules: dict[str, dict[str, Any]] = {}
|
|
120
|
+
if telemetry is not None:
|
|
121
|
+
modules[TELEMETRY_MODULE] = telemetry.configuration(path)
|
|
122
|
+
if orrery is not None:
|
|
123
|
+
modules[ORRERY_MODULE] = orrery.configuration(path)
|
|
124
|
+
except Exception:
|
|
125
|
+
failure = EmitterStartupError("invalid_configuration")
|
|
126
|
+
print(STARTUP_WARNING.format(reason=failure.reason), file=sys.stderr)
|
|
127
|
+
return cls(startup_error=failure)
|
|
128
|
+
try:
|
|
129
|
+
process = start_emitter(
|
|
130
|
+
modules, command=command, startup_timeout=startup_timeout
|
|
131
|
+
)
|
|
132
|
+
except EmitterStartupError as error:
|
|
133
|
+
print(STARTUP_WARNING.format(reason=error.reason), file=sys.stderr)
|
|
134
|
+
return cls(startup_error=error)
|
|
135
|
+
except Exception:
|
|
136
|
+
failure = EmitterStartupError("startup_failure")
|
|
137
|
+
print(STARTUP_WARNING.format(reason=failure.reason), file=sys.stderr)
|
|
138
|
+
return cls(startup_error=failure)
|
|
139
|
+
return cls(process)
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def available(self) -> bool:
|
|
143
|
+
return self._process is not None and not self._closed
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def modules(self) -> frozenset[str]:
|
|
147
|
+
"""The modules the running emitter loaded."""
|
|
148
|
+
if not self.available:
|
|
149
|
+
return frozenset()
|
|
150
|
+
assert self._process is not None
|
|
151
|
+
return frozenset(self._process.description.get("modules", ()))
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def emitter_version(self) -> str | None:
|
|
155
|
+
if self._process is None:
|
|
156
|
+
return None
|
|
157
|
+
version = self._process.description.get("emitter_version")
|
|
158
|
+
return version if isinstance(version, str) else None
|
|
159
|
+
|
|
160
|
+
def _fail(
|
|
161
|
+
self,
|
|
162
|
+
kind: str,
|
|
163
|
+
reason: RequestFailureReason,
|
|
164
|
+
error: BaseException | None = None,
|
|
165
|
+
) -> None:
|
|
166
|
+
"""Record why a call failed; warn once per kind of call.
|
|
167
|
+
|
|
168
|
+
A missing emitter was already reported when it failed to start, so
|
|
169
|
+
only refused requests and unconvertible arguments print a warning.
|
|
170
|
+
"""
|
|
171
|
+
error_type = None if error is None else type(error).__name__
|
|
172
|
+
self.last_failure = RequestFailure(kind, reason, error_type)
|
|
173
|
+
if reason == "emitter_unavailable":
|
|
174
|
+
return
|
|
175
|
+
with self._warning_lock:
|
|
176
|
+
if kind in self._warned:
|
|
177
|
+
return
|
|
178
|
+
self._warned.add(kind)
|
|
179
|
+
print(
|
|
180
|
+
REQUEST_WARNING.format(kind=kind, reason=reason, error_type=error_type),
|
|
181
|
+
file=sys.stderr,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def _message(
|
|
185
|
+
self, build_message: MessageBuilder, *, kind: str
|
|
186
|
+
) -> dict[str, Any] | None:
|
|
187
|
+
"""The converted message, or None after recording why it failed."""
|
|
188
|
+
try:
|
|
189
|
+
return dict(build_message())
|
|
190
|
+
except Exception as error:
|
|
191
|
+
self._fail(kind, "invalid_arguments", error)
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
def _send(self, module: str, build_message: MessageBuilder, *, kind: str) -> bool:
|
|
195
|
+
if module not in self.modules:
|
|
196
|
+
self._fail(kind, "emitter_unavailable")
|
|
197
|
+
return False
|
|
198
|
+
message = self._message(build_message, kind=kind)
|
|
199
|
+
if message is None:
|
|
200
|
+
return False
|
|
201
|
+
assert self._process is not None
|
|
202
|
+
try:
|
|
203
|
+
self._process.transport.send(
|
|
204
|
+
module, message, timeout=ACKNOWLEDGEMENT_TIMEOUT_SECONDS
|
|
205
|
+
)
|
|
206
|
+
except Exception as error:
|
|
207
|
+
self._fail(kind, "not_accepted", error)
|
|
208
|
+
return False
|
|
209
|
+
return True
|
|
210
|
+
|
|
211
|
+
def _request(
|
|
212
|
+
self,
|
|
213
|
+
module: str,
|
|
214
|
+
build_message: MessageBuilder,
|
|
215
|
+
*,
|
|
216
|
+
kind: str,
|
|
217
|
+
timeout: float = FILE_REQUEST_TIMEOUT_SECONDS,
|
|
218
|
+
) -> tuple[dict[str, Any] | None, RequestFailureReason | None]:
|
|
219
|
+
"""The module's answer, or None and why this call failed.
|
|
220
|
+
|
|
221
|
+
The reason belongs to this call only. ``last_failure`` is shared by
|
|
222
|
+
every call and thread, so a receipt's error code never reads it.
|
|
223
|
+
"""
|
|
224
|
+
if module not in self.modules:
|
|
225
|
+
self._fail(kind, "emitter_unavailable")
|
|
226
|
+
return None, "emitter_unavailable"
|
|
227
|
+
message = self._message(build_message, kind=kind)
|
|
228
|
+
if message is None:
|
|
229
|
+
return None, "invalid_arguments"
|
|
230
|
+
assert self._process is not None
|
|
231
|
+
try:
|
|
232
|
+
return self._process.transport.query(module, message, timeout=timeout), None
|
|
233
|
+
except Exception as error:
|
|
234
|
+
self._fail(kind, "not_accepted", error)
|
|
235
|
+
return None, "not_accepted"
|
|
236
|
+
|
|
237
|
+
def _query(
|
|
238
|
+
self,
|
|
239
|
+
module: str,
|
|
240
|
+
build_message: MessageBuilder,
|
|
241
|
+
*,
|
|
242
|
+
kind: str,
|
|
243
|
+
timeout: float = FILE_REQUEST_TIMEOUT_SECONDS,
|
|
244
|
+
) -> dict[str, Any] | None:
|
|
245
|
+
answer, _ = self._request(module, build_message, kind=kind, timeout=timeout)
|
|
246
|
+
return answer
|
|
247
|
+
|
|
248
|
+
def emit_telemetry(self, command: TelemetryCommand) -> bool:
|
|
249
|
+
"""Hand over one telemetry event; True once the emitter committed it."""
|
|
250
|
+
return self._send(
|
|
251
|
+
TELEMETRY_MODULE, lambda: telemetry_message(command), kind="telemetry"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
def orrery_inventory(
|
|
255
|
+
self,
|
|
256
|
+
directory: Path,
|
|
257
|
+
roles: Mapping[str, PublicationFileRole],
|
|
258
|
+
*,
|
|
259
|
+
publication_id: str | None = None,
|
|
260
|
+
) -> PublicationInventory | None:
|
|
261
|
+
"""Ask the emitter to measure the selected preserved Orrery input files."""
|
|
262
|
+
response = self._query(
|
|
263
|
+
ORRERY_MODULE,
|
|
264
|
+
lambda: inventory_message(directory, roles, publication_id),
|
|
265
|
+
kind="Orrery",
|
|
266
|
+
)
|
|
267
|
+
return answered_inventory(response)
|
|
268
|
+
|
|
269
|
+
def hand_over_orrery_inputs(
|
|
270
|
+
self,
|
|
271
|
+
directory: Path,
|
|
272
|
+
inventory: PublicationInventory,
|
|
273
|
+
*,
|
|
274
|
+
wait_seconds: float = 30.0,
|
|
275
|
+
) -> PublicationReceipt:
|
|
276
|
+
"""Queue the inventoried files; wait only after the durable enqueue."""
|
|
277
|
+
publication_id = publication_id_of(inventory)
|
|
278
|
+
if publication_id is None:
|
|
279
|
+
self._fail("Orrery", "invalid_arguments")
|
|
280
|
+
return unavailable_receipt(None, error_code="invalid_arguments")
|
|
281
|
+
receipt = self._orrery_receipt_request(
|
|
282
|
+
lambda: enqueue_message(directory, inventory), publication_id
|
|
283
|
+
)
|
|
284
|
+
deadline = time.monotonic() + _wait_seconds(wait_seconds)
|
|
285
|
+
while (
|
|
286
|
+
self.available
|
|
287
|
+
and receipt["status"] == "pending"
|
|
288
|
+
and not receipt.get("attempts", 0)
|
|
289
|
+
and time.monotonic() < deadline
|
|
290
|
+
):
|
|
291
|
+
time.sleep(0.1)
|
|
292
|
+
receipt = self.orrery_receipt(publication_id)
|
|
293
|
+
return receipt
|
|
294
|
+
|
|
295
|
+
def orrery_receipt(self, publication_id: str) -> PublicationReceipt:
|
|
296
|
+
return self._orrery_receipt_request(
|
|
297
|
+
lambda: receipt_message(publication_id), publication_id
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
def retry_orrery_inputs(
|
|
301
|
+
self, publication_id: str, *, wait_seconds: float = 0
|
|
302
|
+
) -> PublicationReceipt:
|
|
303
|
+
receipt = self._orrery_receipt_request(
|
|
304
|
+
lambda: retry_message(publication_id), publication_id
|
|
305
|
+
)
|
|
306
|
+
previous_attempts = receipt.get("attempts", 0)
|
|
307
|
+
deadline = time.monotonic() + _wait_seconds(wait_seconds)
|
|
308
|
+
while (
|
|
309
|
+
self.available
|
|
310
|
+
and receipt["status"] == "pending"
|
|
311
|
+
and receipt.get("attempts", 0) == previous_attempts
|
|
312
|
+
and time.monotonic() < deadline
|
|
313
|
+
):
|
|
314
|
+
time.sleep(0.1)
|
|
315
|
+
receipt = self.orrery_receipt(publication_id)
|
|
316
|
+
return receipt
|
|
317
|
+
|
|
318
|
+
def _orrery_receipt_request(
|
|
319
|
+
self, build_message: MessageBuilder, publication_id: str | None
|
|
320
|
+
) -> PublicationReceipt:
|
|
321
|
+
answer, failure = self._request(ORRERY_MODULE, build_message, kind="Orrery")
|
|
322
|
+
return answered_receipt(
|
|
323
|
+
answer, publication_id, error_code=_receipt_error_code(failure)
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
def close(self) -> None:
|
|
327
|
+
"""Ask the emitter to drain and stop; repeated calls do nothing."""
|
|
328
|
+
if self._closed:
|
|
329
|
+
return
|
|
330
|
+
self._closed = True
|
|
331
|
+
if self._process is not None:
|
|
332
|
+
try:
|
|
333
|
+
self._process.close()
|
|
334
|
+
except Exception as error:
|
|
335
|
+
self._fail("close", "not_accepted", error)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _receipt_error_code(failure: RequestFailureReason | None) -> str:
|
|
339
|
+
"""The error code of an unavailable receipt, from its own call's failure.
|
|
340
|
+
|
|
341
|
+
An answer without a receipt, or no answer, is ``local_service_unavailable``,
|
|
342
|
+
as in the emitter's in-process client.
|
|
343
|
+
"""
|
|
344
|
+
if failure == "invalid_arguments":
|
|
345
|
+
return "invalid_arguments"
|
|
346
|
+
return "local_service_unavailable"
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _wait_seconds(value: Any) -> float:
|
|
350
|
+
"""A usable waiting time; an unusable one means not waiting."""
|
|
351
|
+
try:
|
|
352
|
+
seconds = float(value)
|
|
353
|
+
except Exception:
|
|
354
|
+
return 0.0
|
|
355
|
+
return seconds if seconds > 0 else 0.0
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"protocol_version": 1,
|
|
3
|
+
"module": "host",
|
|
4
|
+
"kind": "launch",
|
|
5
|
+
"body": {
|
|
6
|
+
"arguments": [
|
|
7
|
+
"host",
|
|
8
|
+
"--socket",
|
|
9
|
+
"{socket_path}",
|
|
10
|
+
"--parent-pid",
|
|
11
|
+
"{parent_pid}",
|
|
12
|
+
"--skip-unavailable-modules"
|
|
13
|
+
],
|
|
14
|
+
"standard_input": {
|
|
15
|
+
"orrery": {
|
|
16
|
+
"spool_path": "{spool_path}",
|
|
17
|
+
"development_collector_url": "http://127.0.0.1:1",
|
|
18
|
+
"publication_origin": "http://127.0.0.1:1"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"protocol_version": 1,
|
|
3
|
+
"module": "orrery",
|
|
4
|
+
"kind": "query",
|
|
5
|
+
"sequence": 2,
|
|
6
|
+
"files": {
|
|
7
|
+
"graph.orrery.json": "{\"nodes\":[]}",
|
|
8
|
+
"graph.schema.json": "{\"schema\":true}"
|
|
9
|
+
},
|
|
10
|
+
"body": {
|
|
11
|
+
"command": "enqueue",
|
|
12
|
+
"directory": "{directory}",
|
|
13
|
+
"inventory": {
|
|
14
|
+
"version": 1,
|
|
15
|
+
"publication_id": "fixture-publication",
|
|
16
|
+
"files": [
|
|
17
|
+
{
|
|
18
|
+
"name": "graph.orrery.json",
|
|
19
|
+
"role": "graph",
|
|
20
|
+
"bytes": 12,
|
|
21
|
+
"sha256": "acf2fa576acb702442f9d0101673354c398db67315c066ca48be8db8e0d2c75b"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "graph.schema.json",
|
|
25
|
+
"role": "schema",
|
|
26
|
+
"bytes": 15,
|
|
27
|
+
"sha256": "e296bc1725afc9c1e1585549e7f3c3b0cf58acba82fc60188d8eda40b9e17a13"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"response_keys": [
|
|
33
|
+
"receipt"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"protocol_version": 1,
|
|
3
|
+
"module": "orrery",
|
|
4
|
+
"kind": "query",
|
|
5
|
+
"sequence": 1,
|
|
6
|
+
"files": {
|
|
7
|
+
"graph.orrery.json": "{\"nodes\":[]}",
|
|
8
|
+
"graph.schema.json": "{\"schema\":true}"
|
|
9
|
+
},
|
|
10
|
+
"body": {
|
|
11
|
+
"command": "inventory",
|
|
12
|
+
"directory": "{directory}",
|
|
13
|
+
"roles": {
|
|
14
|
+
"graph.orrery.json": "graph",
|
|
15
|
+
"graph.schema.json": "schema"
|
|
16
|
+
},
|
|
17
|
+
"publication_id": "fixture-publication"
|
|
18
|
+
},
|
|
19
|
+
"response_keys": [
|
|
20
|
+
"inventory"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"protocol_version": 1,
|
|
3
|
+
"module": "telemetry",
|
|
4
|
+
"kind": "configuration",
|
|
5
|
+
"body": {
|
|
6
|
+
"registration": {
|
|
7
|
+
"run_id": "fixture-run",
|
|
8
|
+
"producer_id": "fixture-producer",
|
|
9
|
+
"country_code": "UK",
|
|
10
|
+
"pipeline": "fixture-pipeline",
|
|
11
|
+
"candidate_id": null,
|
|
12
|
+
"release_id": null,
|
|
13
|
+
"run_kind": "build"
|
|
14
|
+
},
|
|
15
|
+
"spool_path": "{spool_path}",
|
|
16
|
+
"identity": {
|
|
17
|
+
"host": "fixture"
|
|
18
|
+
},
|
|
19
|
+
"development_collector_url": "http://127.0.0.1:1",
|
|
20
|
+
"heartbeat_seconds": 60.0
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"protocol_version": 1,
|
|
3
|
+
"module": "telemetry",
|
|
4
|
+
"kind": "message",
|
|
5
|
+
"sequence": 0,
|
|
6
|
+
"body": {
|
|
7
|
+
"command": "emit",
|
|
8
|
+
"event_type": "progress",
|
|
9
|
+
"stage_id": "compile",
|
|
10
|
+
"status": "progress",
|
|
11
|
+
"message": "Compiling.",
|
|
12
|
+
"details": {
|
|
13
|
+
"step": 1
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"protocol_version": 1,
|
|
3
|
+
"module": "telemetry",
|
|
4
|
+
"kind": "message",
|
|
5
|
+
"sequence": 0,
|
|
6
|
+
"body": {
|
|
7
|
+
"command": "fail",
|
|
8
|
+
"message": "Synthetic failure.",
|
|
9
|
+
"error_type": "RuntimeError",
|
|
10
|
+
"failed_during": "calibrating",
|
|
11
|
+
"failure_class": "build_failure",
|
|
12
|
+
"error_code": "BUILD_FAILED"
|
|
13
|
+
}
|
|
14
|
+
}
|