gitd 0.8.0__tar.gz → 0.9.0__tar.gz
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.
- {gitd-0.8.0 → gitd-0.9.0}/PKG-INFO +1 -1
- {gitd-0.8.0 → gitd-0.9.0}/pyproject.toml +1 -1
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/__init__.py +5 -0
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/client.py +26 -7
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/connection.py +53 -8
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/contract.json +214 -0
- gitd-0.9.0/src/gitd/endpoint_calls.py +52 -0
- gitd-0.9.0/src/gitd/endpoint_tokens.py +55 -0
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/messages.py +6 -0
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/methods.py +3 -0
- {gitd-0.8.0 → gitd-0.9.0}/.gitignore +0 -0
- {gitd-0.8.0 → gitd-0.9.0}/README.md +0 -0
- {gitd-0.8.0 → gitd-0.9.0}/examples/agent.py +0 -0
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/changes.py +0 -0
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/comparison.py +0 -0
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/contract.py +0 -0
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/credentials_calls.py +0 -0
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/errors.py +0 -0
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/kept_credentials.py +0 -0
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/models.py +0 -0
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/publishing_calls.py +0 -0
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/publishing_settings.py +0 -0
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/timestamps.py +0 -0
- {gitd-0.8.0 → gitd-0.9.0}/src/gitd/warm_layers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: gitd
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Client for gitd: instant, secretless git checkouts for agent sandboxes
|
|
5
5
|
Project-URL: Homepage, https://github.com/dooservice/gitd
|
|
6
6
|
Project-URL: Source, https://github.com/dooservice/gitd/tree/main/sdk/python
|
|
@@ -4,6 +4,8 @@ from .changes import ChangedFile, ChangeKind, Changes, Diff
|
|
|
4
4
|
from .client import Gitd
|
|
5
5
|
from .comparison import Comparison, Conflict, Event, EventKind, Side
|
|
6
6
|
from .connection import DEFAULT_SOCKET, SOCKET_VARIABLE
|
|
7
|
+
from .endpoint_calls import Endpoint
|
|
8
|
+
from .endpoint_tokens import IssuedToken, TokenInfo
|
|
7
9
|
from .errors import GitdError
|
|
8
10
|
from .kept_credentials import AppToCreate, Connected, CredentialKind, KeptCredential, SignIn
|
|
9
11
|
from .models import (
|
|
@@ -78,4 +80,7 @@ __all__ = [
|
|
|
78
80
|
"Submodule",
|
|
79
81
|
"Workspace",
|
|
80
82
|
"WarmLayer",
|
|
83
|
+
"Endpoint",
|
|
84
|
+
"IssuedToken",
|
|
85
|
+
"TokenInfo",
|
|
81
86
|
]
|
|
@@ -11,6 +11,7 @@ from .changes import Changes, Diff
|
|
|
11
11
|
from .comparison import Comparison, Conflict, Event, Side
|
|
12
12
|
from .connection import Connection, OnProgress
|
|
13
13
|
from .credentials_calls import Credentials
|
|
14
|
+
from .endpoint_calls import Endpoint
|
|
14
15
|
from .models import AuditEntry, Description, Info, Probe, Published, Strategy, Workspace
|
|
15
16
|
from .publishing_calls import Publishing
|
|
16
17
|
from .publishing_settings import Provenance, PullRequestOptions
|
|
@@ -19,18 +20,32 @@ from .warm_layers import WarmLayer
|
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class Gitd:
|
|
22
|
-
"""A gitd on its Unix socket
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
"""A gitd on its Unix socket, or at url on the network with key naming the caller's
|
|
24
|
+
tenant. No call on workspaces takes a credential: gitd reads and publishes with the ones
|
|
25
|
+
it keeps, which a platform manages through gitd.credentials, and publishing follows the
|
|
26
|
+
rules it sets through gitd.publishing. Sandboxes elsewhere get tokens of gitd's own
|
|
27
|
+
through gitd.endpoint.
|
|
25
28
|
|
|
26
29
|
with Gitd() as gitd:
|
|
27
30
|
workspace = gitd.prepare("https://github.com/acme/web", "/var/lib/gitd/workspaces/agent-1")
|
|
31
|
+
|
|
32
|
+
with Gitd(url="https://gitd.internal:9420", key=open("/run/secrets/acme").read()) as gitd:
|
|
33
|
+
...
|
|
28
34
|
"""
|
|
29
35
|
|
|
30
|
-
def __init__(
|
|
31
|
-
self
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
socket: str | None = None,
|
|
39
|
+
*,
|
|
40
|
+
timeout: float | None = None,
|
|
41
|
+
url: str | None = None,
|
|
42
|
+
key: str | None = None,
|
|
43
|
+
ca_file: str | None = None,
|
|
44
|
+
) -> None:
|
|
45
|
+
self.connection = Connection(socket, timeout, url=url, key=key, ca_file=ca_file)
|
|
32
46
|
self.credentials = Credentials(self.connection)
|
|
33
47
|
self.publishing = Publishing(self.connection)
|
|
48
|
+
self.endpoint = Endpoint(self.connection)
|
|
34
49
|
|
|
35
50
|
def __enter__(self) -> Gitd:
|
|
36
51
|
return self
|
|
@@ -121,11 +136,15 @@ class Gitd:
|
|
|
121
136
|
requested_by: str = "",
|
|
122
137
|
pull_request: PullRequestOptions | None = None,
|
|
123
138
|
provenance: Provenance | None = None,
|
|
139
|
+
sandbox: str = "",
|
|
124
140
|
) -> Published:
|
|
125
141
|
"""Pushes what was committed in a workspace to a new branch, with the credential gitd
|
|
126
142
|
keeps, and opens its pull request when the publishing settings or pull_request say
|
|
127
|
-
so. requested_by is who asked for the work, as their account on the host.
|
|
128
|
-
|
|
143
|
+
so. requested_by is who asked for the work, as their account on the host. With
|
|
144
|
+
sandbox, and path empty, publishes the branch a sandbox pushed through the endpoint."""
|
|
145
|
+
request: Any = messages.PublishRequest(
|
|
146
|
+
path=path, branch=branch, requested_by=requested_by, sandbox=sandbox
|
|
147
|
+
)
|
|
129
148
|
if pull_request is not None:
|
|
130
149
|
request.pull_request.CopyFrom(pull_request.to_message())
|
|
131
150
|
if provenance is not None:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
"""The channel to gitd
|
|
1
|
+
"""The channel to gitd — its Unix socket, or its address on the network — and how a call on
|
|
2
|
+
it is made and read."""
|
|
2
3
|
|
|
3
4
|
from __future__ import annotations
|
|
4
5
|
|
|
@@ -15,18 +16,62 @@ from .models import Progress
|
|
|
15
16
|
|
|
16
17
|
DEFAULT_SOCKET = "/run/gitd/gitd.sock"
|
|
17
18
|
SOCKET_VARIABLE = "GITD_SOCKET"
|
|
19
|
+
URL_VARIABLE = "GITD_URL"
|
|
20
|
+
KEY_VARIABLE = "GITD_KEY"
|
|
21
|
+
KEY_FILE_VARIABLE = "GITD_KEY_FILE"
|
|
22
|
+
CA_FILE_VARIABLE = "GITD_CA_FILE"
|
|
18
23
|
|
|
19
24
|
OnProgress = Callable[[Progress], None]
|
|
20
25
|
|
|
21
26
|
|
|
22
27
|
class Connection:
|
|
23
|
-
"""A channel to gitd
|
|
24
|
-
|
|
28
|
+
"""A channel to gitd: at url on the network (default $GITD_URL: https://host:port, or
|
|
29
|
+
http:// on a private network), else on its socket (default $GITD_SOCKET, then
|
|
30
|
+
/run/gitd/gitd.sock). key names the caller's tenant (default the file $GITD_KEY_FILE names,
|
|
31
|
+
then $GITD_KEY); ca_file is the authority of a certificate the system does not trust
|
|
32
|
+
(default $GITD_CA_FILE). The timeout, in seconds, bounds every call, which gitd otherwise
|
|
33
|
+
bounds itself."""
|
|
25
34
|
|
|
26
|
-
def __init__(
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
socket: str | None,
|
|
38
|
+
timeout: float | None,
|
|
39
|
+
url: str | None = None,
|
|
40
|
+
key: str | None = None,
|
|
41
|
+
ca_file: str | None = None,
|
|
42
|
+
) -> None:
|
|
27
43
|
self.socket = socket or os.environ.get(SOCKET_VARIABLE) or DEFAULT_SOCKET
|
|
28
44
|
self.timeout = timeout
|
|
29
|
-
|
|
45
|
+
key = key or self.key_from_environment()
|
|
46
|
+
self.metadata = (("authorization", f"Bearer {key}"),) if key else ()
|
|
47
|
+
url = url or os.environ.get(URL_VARIABLE)
|
|
48
|
+
if not url:
|
|
49
|
+
self.channel = grpc.insecure_channel(f"unix:{self.socket}")
|
|
50
|
+
return
|
|
51
|
+
secure, target = self.target_of(url)
|
|
52
|
+
if not secure:
|
|
53
|
+
self.channel = grpc.insecure_channel(target)
|
|
54
|
+
return
|
|
55
|
+
ca_file = ca_file or os.environ.get(CA_FILE_VARIABLE)
|
|
56
|
+
roots = open(ca_file, "rb").read() if ca_file else None # noqa: SIM115
|
|
57
|
+
self.channel = grpc.secure_channel(target, grpc.ssl_channel_credentials(root_certificates=roots))
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def key_from_environment() -> str:
|
|
61
|
+
key_file = os.environ.get(KEY_FILE_VARIABLE)
|
|
62
|
+
if key_file:
|
|
63
|
+
with open(key_file) as opened:
|
|
64
|
+
return opened.read().strip()
|
|
65
|
+
return os.environ.get(KEY_VARIABLE, "").strip()
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def target_of(url: str) -> tuple[bool, str]:
|
|
69
|
+
"""Whether url is served over TLS, and the host:port gRPC dials."""
|
|
70
|
+
for scheme, secure, port in (("https://", True, 443), ("http://", False, 80)):
|
|
71
|
+
if url.startswith(scheme):
|
|
72
|
+
host = url[len(scheme) :].rstrip("/")
|
|
73
|
+
return secure, host if ":" in host else f"{host}:{port}"
|
|
74
|
+
raise GitdError("BAD_ADDRESS", f"{url!r} is not the address of a gitd: https://host:port")
|
|
30
75
|
|
|
31
76
|
def close(self) -> None:
|
|
32
77
|
self.channel.close()
|
|
@@ -43,7 +88,7 @@ class Connection:
|
|
|
43
88
|
response_deserializer=method.response.FromString,
|
|
44
89
|
)
|
|
45
90
|
try:
|
|
46
|
-
return invoke(request, timeout=self.timeout)
|
|
91
|
+
return invoke(request, timeout=self.timeout, metadata=self.metadata)
|
|
47
92
|
except grpc.RpcError as error:
|
|
48
93
|
raise GitdError.from_rpc_error(error) from None
|
|
49
94
|
|
|
@@ -55,7 +100,7 @@ class Connection:
|
|
|
55
100
|
response_deserializer=method.response.FromString,
|
|
56
101
|
)
|
|
57
102
|
try:
|
|
58
|
-
yield from invoke(request, timeout=self.timeout)
|
|
103
|
+
yield from invoke(request, timeout=self.timeout, metadata=self.metadata)
|
|
59
104
|
except grpc.RpcError as error:
|
|
60
105
|
raise GitdError.from_rpc_error(error) from None
|
|
61
106
|
|
|
@@ -72,7 +117,7 @@ class Connection:
|
|
|
72
117
|
response_deserializer=method.response.FromString,
|
|
73
118
|
)
|
|
74
119
|
try:
|
|
75
|
-
for response in invoke(request, timeout=self.timeout):
|
|
120
|
+
for response in invoke(request, timeout=self.timeout, metadata=self.metadata):
|
|
76
121
|
event = response.WhichOneof("event")
|
|
77
122
|
if event == "progress" and on_progress is not None:
|
|
78
123
|
on_progress(Progress.from_message(response.progress))
|
|
@@ -283,6 +283,10 @@
|
|
|
283
283
|
{
|
|
284
284
|
"name": "REASON_UNSUPPORTED",
|
|
285
285
|
"number": 14
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"name": "REASON_LIMIT_REACHED",
|
|
289
|
+
"number": 15
|
|
286
290
|
}
|
|
287
291
|
]
|
|
288
292
|
}
|
|
@@ -349,6 +353,13 @@
|
|
|
349
353
|
"label": "LABEL_OPTIONAL",
|
|
350
354
|
"type": "TYPE_STRING",
|
|
351
355
|
"jsonName": "warm"
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"name": "pool_inside",
|
|
359
|
+
"number": 9,
|
|
360
|
+
"label": "LABEL_OPTIONAL",
|
|
361
|
+
"type": "TYPE_BOOL",
|
|
362
|
+
"jsonName": "poolInside"
|
|
352
363
|
}
|
|
353
364
|
]
|
|
354
365
|
},
|
|
@@ -460,6 +471,13 @@
|
|
|
460
471
|
"label": "LABEL_REPEATED",
|
|
461
472
|
"type": "TYPE_STRING",
|
|
462
473
|
"jsonName": "lfsPatterns"
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
"name": "pool_objects",
|
|
477
|
+
"number": 7,
|
|
478
|
+
"label": "LABEL_OPTIONAL",
|
|
479
|
+
"type": "TYPE_STRING",
|
|
480
|
+
"jsonName": "poolObjects"
|
|
463
481
|
}
|
|
464
482
|
]
|
|
465
483
|
},
|
|
@@ -964,6 +982,13 @@
|
|
|
964
982
|
"type": "TYPE_MESSAGE",
|
|
965
983
|
"typeName": ".gitd.v1.Provenance",
|
|
966
984
|
"jsonName": "provenance"
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
"name": "sandbox",
|
|
988
|
+
"number": 7,
|
|
989
|
+
"label": "LABEL_OPTIONAL",
|
|
990
|
+
"type": "TYPE_STRING",
|
|
991
|
+
"jsonName": "sandbox"
|
|
967
992
|
}
|
|
968
993
|
]
|
|
969
994
|
},
|
|
@@ -1900,6 +1925,13 @@
|
|
|
1900
1925
|
"type": "TYPE_MESSAGE",
|
|
1901
1926
|
"typeName": ".gitd.v1.Provenance",
|
|
1902
1927
|
"jsonName": "provenance"
|
|
1928
|
+
},
|
|
1929
|
+
{
|
|
1930
|
+
"name": "sandbox",
|
|
1931
|
+
"number": 13,
|
|
1932
|
+
"label": "LABEL_OPTIONAL",
|
|
1933
|
+
"type": "TYPE_STRING",
|
|
1934
|
+
"jsonName": "sandbox"
|
|
1903
1935
|
}
|
|
1904
1936
|
]
|
|
1905
1937
|
},
|
|
@@ -1960,6 +1992,14 @@
|
|
|
1960
1992
|
{
|
|
1961
1993
|
"name": "ACTION_PRESET_REMOVED",
|
|
1962
1994
|
"number": 6
|
|
1995
|
+
},
|
|
1996
|
+
{
|
|
1997
|
+
"name": "ACTION_TOKEN_ISSUED",
|
|
1998
|
+
"number": 7
|
|
1999
|
+
},
|
|
2000
|
+
{
|
|
2001
|
+
"name": "ACTION_TOKEN_REVOKED",
|
|
2002
|
+
"number": 8
|
|
1963
2003
|
}
|
|
1964
2004
|
]
|
|
1965
2005
|
}
|
|
@@ -2667,6 +2707,160 @@
|
|
|
2667
2707
|
},
|
|
2668
2708
|
{
|
|
2669
2709
|
"name": "RemovePresetResponse"
|
|
2710
|
+
},
|
|
2711
|
+
{
|
|
2712
|
+
"name": "IssueTokenRequest",
|
|
2713
|
+
"field": [
|
|
2714
|
+
{
|
|
2715
|
+
"name": "url",
|
|
2716
|
+
"number": 1,
|
|
2717
|
+
"label": "LABEL_OPTIONAL",
|
|
2718
|
+
"type": "TYPE_STRING",
|
|
2719
|
+
"jsonName": "url"
|
|
2720
|
+
},
|
|
2721
|
+
{
|
|
2722
|
+
"name": "branch",
|
|
2723
|
+
"number": 2,
|
|
2724
|
+
"label": "LABEL_OPTIONAL",
|
|
2725
|
+
"type": "TYPE_STRING",
|
|
2726
|
+
"jsonName": "branch"
|
|
2727
|
+
},
|
|
2728
|
+
{
|
|
2729
|
+
"name": "lifetime_seconds",
|
|
2730
|
+
"number": 3,
|
|
2731
|
+
"label": "LABEL_OPTIONAL",
|
|
2732
|
+
"type": "TYPE_INT64",
|
|
2733
|
+
"jsonName": "lifetimeSeconds"
|
|
2734
|
+
},
|
|
2735
|
+
{
|
|
2736
|
+
"name": "read_only",
|
|
2737
|
+
"number": 4,
|
|
2738
|
+
"label": "LABEL_OPTIONAL",
|
|
2739
|
+
"type": "TYPE_BOOL",
|
|
2740
|
+
"jsonName": "readOnly"
|
|
2741
|
+
},
|
|
2742
|
+
{
|
|
2743
|
+
"name": "sandbox",
|
|
2744
|
+
"number": 5,
|
|
2745
|
+
"label": "LABEL_OPTIONAL",
|
|
2746
|
+
"type": "TYPE_STRING",
|
|
2747
|
+
"jsonName": "sandbox"
|
|
2748
|
+
}
|
|
2749
|
+
]
|
|
2750
|
+
},
|
|
2751
|
+
{
|
|
2752
|
+
"name": "IssueTokenResponse",
|
|
2753
|
+
"field": [
|
|
2754
|
+
{
|
|
2755
|
+
"name": "token",
|
|
2756
|
+
"number": 1,
|
|
2757
|
+
"label": "LABEL_OPTIONAL",
|
|
2758
|
+
"type": "TYPE_STRING",
|
|
2759
|
+
"jsonName": "token"
|
|
2760
|
+
},
|
|
2761
|
+
{
|
|
2762
|
+
"name": "info",
|
|
2763
|
+
"number": 2,
|
|
2764
|
+
"label": "LABEL_OPTIONAL",
|
|
2765
|
+
"type": "TYPE_MESSAGE",
|
|
2766
|
+
"typeName": ".gitd.v1.TokenInfo",
|
|
2767
|
+
"jsonName": "info"
|
|
2768
|
+
},
|
|
2769
|
+
{
|
|
2770
|
+
"name": "clone_url",
|
|
2771
|
+
"number": 3,
|
|
2772
|
+
"label": "LABEL_OPTIONAL",
|
|
2773
|
+
"type": "TYPE_STRING",
|
|
2774
|
+
"jsonName": "cloneUrl"
|
|
2775
|
+
}
|
|
2776
|
+
]
|
|
2777
|
+
},
|
|
2778
|
+
{
|
|
2779
|
+
"name": "TokenInfo",
|
|
2780
|
+
"field": [
|
|
2781
|
+
{
|
|
2782
|
+
"name": "id",
|
|
2783
|
+
"number": 1,
|
|
2784
|
+
"label": "LABEL_OPTIONAL",
|
|
2785
|
+
"type": "TYPE_STRING",
|
|
2786
|
+
"jsonName": "id"
|
|
2787
|
+
},
|
|
2788
|
+
{
|
|
2789
|
+
"name": "url",
|
|
2790
|
+
"number": 2,
|
|
2791
|
+
"label": "LABEL_OPTIONAL",
|
|
2792
|
+
"type": "TYPE_STRING",
|
|
2793
|
+
"jsonName": "url"
|
|
2794
|
+
},
|
|
2795
|
+
{
|
|
2796
|
+
"name": "sandbox",
|
|
2797
|
+
"number": 3,
|
|
2798
|
+
"label": "LABEL_OPTIONAL",
|
|
2799
|
+
"type": "TYPE_STRING",
|
|
2800
|
+
"jsonName": "sandbox"
|
|
2801
|
+
},
|
|
2802
|
+
{
|
|
2803
|
+
"name": "writable",
|
|
2804
|
+
"number": 4,
|
|
2805
|
+
"label": "LABEL_OPTIONAL",
|
|
2806
|
+
"type": "TYPE_BOOL",
|
|
2807
|
+
"jsonName": "writable"
|
|
2808
|
+
},
|
|
2809
|
+
{
|
|
2810
|
+
"name": "issued_at",
|
|
2811
|
+
"number": 5,
|
|
2812
|
+
"label": "LABEL_OPTIONAL",
|
|
2813
|
+
"type": "TYPE_MESSAGE",
|
|
2814
|
+
"typeName": ".google.protobuf.Timestamp",
|
|
2815
|
+
"jsonName": "issuedAt"
|
|
2816
|
+
},
|
|
2817
|
+
{
|
|
2818
|
+
"name": "expires_at",
|
|
2819
|
+
"number": 6,
|
|
2820
|
+
"label": "LABEL_OPTIONAL",
|
|
2821
|
+
"type": "TYPE_MESSAGE",
|
|
2822
|
+
"typeName": ".google.protobuf.Timestamp",
|
|
2823
|
+
"jsonName": "expiresAt"
|
|
2824
|
+
}
|
|
2825
|
+
]
|
|
2826
|
+
},
|
|
2827
|
+
{
|
|
2828
|
+
"name": "RevokeTokenRequest",
|
|
2829
|
+
"field": [
|
|
2830
|
+
{
|
|
2831
|
+
"name": "id",
|
|
2832
|
+
"number": 1,
|
|
2833
|
+
"label": "LABEL_OPTIONAL",
|
|
2834
|
+
"type": "TYPE_STRING",
|
|
2835
|
+
"jsonName": "id"
|
|
2836
|
+
},
|
|
2837
|
+
{
|
|
2838
|
+
"name": "remove_sandbox",
|
|
2839
|
+
"number": 2,
|
|
2840
|
+
"label": "LABEL_OPTIONAL",
|
|
2841
|
+
"type": "TYPE_BOOL",
|
|
2842
|
+
"jsonName": "removeSandbox"
|
|
2843
|
+
}
|
|
2844
|
+
]
|
|
2845
|
+
},
|
|
2846
|
+
{
|
|
2847
|
+
"name": "RevokeTokenResponse"
|
|
2848
|
+
},
|
|
2849
|
+
{
|
|
2850
|
+
"name": "ListTokensRequest"
|
|
2851
|
+
},
|
|
2852
|
+
{
|
|
2853
|
+
"name": "ListTokensResponse",
|
|
2854
|
+
"field": [
|
|
2855
|
+
{
|
|
2856
|
+
"name": "tokens",
|
|
2857
|
+
"number": 1,
|
|
2858
|
+
"label": "LABEL_REPEATED",
|
|
2859
|
+
"type": "TYPE_MESSAGE",
|
|
2860
|
+
"typeName": ".gitd.v1.TokenInfo",
|
|
2861
|
+
"jsonName": "tokens"
|
|
2862
|
+
}
|
|
2863
|
+
]
|
|
2670
2864
|
}
|
|
2671
2865
|
],
|
|
2672
2866
|
"enumType": [
|
|
@@ -2888,6 +3082,26 @@
|
|
|
2888
3082
|
"outputType": ".gitd.v1.RemovePresetResponse"
|
|
2889
3083
|
}
|
|
2890
3084
|
]
|
|
3085
|
+
},
|
|
3086
|
+
{
|
|
3087
|
+
"name": "EndpointService",
|
|
3088
|
+
"method": [
|
|
3089
|
+
{
|
|
3090
|
+
"name": "IssueToken",
|
|
3091
|
+
"inputType": ".gitd.v1.IssueTokenRequest",
|
|
3092
|
+
"outputType": ".gitd.v1.IssueTokenResponse"
|
|
3093
|
+
},
|
|
3094
|
+
{
|
|
3095
|
+
"name": "RevokeToken",
|
|
3096
|
+
"inputType": ".gitd.v1.RevokeTokenRequest",
|
|
3097
|
+
"outputType": ".gitd.v1.RevokeTokenResponse"
|
|
3098
|
+
},
|
|
3099
|
+
{
|
|
3100
|
+
"name": "ListTokens",
|
|
3101
|
+
"inputType": ".gitd.v1.ListTokensRequest",
|
|
3102
|
+
"outputType": ".gitd.v1.ListTokensResponse"
|
|
3103
|
+
}
|
|
3104
|
+
]
|
|
2891
3105
|
}
|
|
2892
3106
|
],
|
|
2893
3107
|
"options": {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""The calls of gitd's endpoint: tokens for sandboxes elsewhere. What a sandbox pushes is
|
|
2
|
+
published with Gitd.publish and its sandbox."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from datetime import timedelta
|
|
7
|
+
|
|
8
|
+
from . import messages, methods
|
|
9
|
+
from .connection import Connection
|
|
10
|
+
from .endpoint_tokens import IssuedToken, TokenInfo
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Endpoint:
|
|
14
|
+
"""gitd's endpoint, as gitd.endpoint.
|
|
15
|
+
|
|
16
|
+
issued = gitd.endpoint.issue("https://github.com/acme/web", lifetime=timedelta(hours=2))
|
|
17
|
+
# in the sandbox: git clone issued.authenticated_url() …; git push origin HEAD:agents/fix
|
|
18
|
+
gitd.publish("", "agents/fix", sandbox=issued.info.sandbox)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, connection: Connection) -> None:
|
|
22
|
+
self.connection = connection
|
|
23
|
+
|
|
24
|
+
def issue(
|
|
25
|
+
self,
|
|
26
|
+
url: str,
|
|
27
|
+
*,
|
|
28
|
+
branch: str = "",
|
|
29
|
+
lifetime: timedelta | None = None,
|
|
30
|
+
read_only: bool = False,
|
|
31
|
+
sandbox: str = "",
|
|
32
|
+
) -> IssuedToken:
|
|
33
|
+
"""A token for a sandbox: it reads url and pushes to its own sandbox repository (an
|
|
34
|
+
earlier one when sandbox names it), for lifetime (an hour by default, a day at most)."""
|
|
35
|
+
request = messages.IssueTokenRequest(
|
|
36
|
+
url=url,
|
|
37
|
+
branch=branch,
|
|
38
|
+
lifetime_seconds=int(lifetime.total_seconds()) if lifetime else 0,
|
|
39
|
+
read_only=read_only,
|
|
40
|
+
sandbox=sandbox,
|
|
41
|
+
)
|
|
42
|
+
return IssuedToken.from_message(self.connection.call(methods.ISSUE_TOKEN, request))
|
|
43
|
+
|
|
44
|
+
def revoke(self, token_id: str, *, remove_sandbox: bool = False) -> None:
|
|
45
|
+
"""Ends a token now, and removes its sandbox repository when asked."""
|
|
46
|
+
request = messages.RevokeTokenRequest(id=token_id, remove_sandbox=remove_sandbox)
|
|
47
|
+
self.connection.call(methods.REVOKE_TOKEN, request)
|
|
48
|
+
|
|
49
|
+
def tokens(self) -> list[TokenInfo]:
|
|
50
|
+
"""The tokens alive, never their secrets."""
|
|
51
|
+
response = self.connection.call(methods.LIST_TOKENS, messages.ListTokensRequest())
|
|
52
|
+
return [TokenInfo.from_message(info) for info in response.tokens]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Tokens of gitd's endpoint, for sandboxes that clone over HTTP instead of mounting a
|
|
2
|
+
workspace: a microVM, a remote sandbox, a CI runner elsewhere."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from .timestamps import Timestamp
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class TokenInfo:
|
|
15
|
+
"""A token as gitd shows it, never its secret: what it reads, the sandbox repository it
|
|
16
|
+
pushes to when writable, and until when."""
|
|
17
|
+
|
|
18
|
+
id: str
|
|
19
|
+
url: str
|
|
20
|
+
sandbox: str
|
|
21
|
+
writable: bool
|
|
22
|
+
issued_at: datetime
|
|
23
|
+
expires_at: datetime
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def from_message(cls, info: Any) -> TokenInfo:
|
|
27
|
+
return cls(
|
|
28
|
+
id=info.id,
|
|
29
|
+
url=info.url,
|
|
30
|
+
sandbox=info.sandbox,
|
|
31
|
+
writable=info.writable,
|
|
32
|
+
issued_at=Timestamp.to_datetime(info.issued_at),
|
|
33
|
+
expires_at=Timestamp.to_datetime(info.expires_at),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class IssuedToken:
|
|
39
|
+
"""A token just issued: its secret, shown this once, and where the sandbox clones from —
|
|
40
|
+
git clone https://x:<secret>@… — never the git host's credential."""
|
|
41
|
+
|
|
42
|
+
secret: str
|
|
43
|
+
clone_url: str
|
|
44
|
+
info: TokenInfo
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def from_message(cls, response: Any) -> IssuedToken:
|
|
48
|
+
return cls(
|
|
49
|
+
secret=response.token, clone_url=response.clone_url, info=TokenInfo.from_message(response.info)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def authenticated_url(self) -> str:
|
|
53
|
+
"""The clone URL with the secret in it, for the sandbox's git."""
|
|
54
|
+
scheme, rest = self.clone_url.split("://", 1)
|
|
55
|
+
return f"{scheme}://x:{self.secret}@{rest}"
|
|
@@ -41,6 +41,12 @@ SealRequest = contract.message("SealRequest")
|
|
|
41
41
|
SealResponse = contract.message("SealResponse")
|
|
42
42
|
ListWarmRequest = contract.message("ListWarmRequest")
|
|
43
43
|
ListWarmResponse = contract.message("ListWarmResponse")
|
|
44
|
+
IssueTokenRequest = contract.message("IssueTokenRequest")
|
|
45
|
+
IssueTokenResponse = contract.message("IssueTokenResponse")
|
|
46
|
+
RevokeTokenRequest = contract.message("RevokeTokenRequest")
|
|
47
|
+
RevokeTokenResponse = contract.message("RevokeTokenResponse")
|
|
48
|
+
ListTokensRequest = contract.message("ListTokensRequest")
|
|
49
|
+
ListTokensResponse = contract.message("ListTokensResponse")
|
|
44
50
|
|
|
45
51
|
ListConnectionsRequest = contract.message("ListConnectionsRequest")
|
|
46
52
|
ListConnectionsResponse = contract.message("ListConnectionsResponse")
|
|
@@ -37,6 +37,9 @@ CONFLICTS = Method("WorkspaceService", "Conflicts", messages.ConflictsResponse)
|
|
|
37
37
|
WATCH = Method("WorkspaceService", "Watch", messages.WatchResponse)
|
|
38
38
|
SEAL = Method("WorkspaceService", "Seal", messages.SealResponse)
|
|
39
39
|
LIST_WARM = Method("WorkspaceService", "ListWarm", messages.ListWarmResponse)
|
|
40
|
+
ISSUE_TOKEN = Method("EndpointService", "IssueToken", messages.IssueTokenResponse)
|
|
41
|
+
REVOKE_TOKEN = Method("EndpointService", "RevokeToken", messages.RevokeTokenResponse)
|
|
42
|
+
LIST_TOKENS = Method("EndpointService", "ListTokens", messages.ListTokensResponse)
|
|
40
43
|
|
|
41
44
|
LIST_CONNECTIONS = Method("ConnectionService", "ListConnections", messages.ListConnectionsResponse)
|
|
42
45
|
REMOVE_CONNECTION = Method("ConnectionService", "RemoveConnection", messages.RemoveConnectionResponse)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|