gitd 0.7.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.
- gitd/__init__.py +42 -0
- gitd/client.py +124 -0
- gitd/connection.py +71 -0
- gitd/contract.json +1291 -0
- gitd/contract.py +50 -0
- gitd/errors.py +36 -0
- gitd/messages.py +30 -0
- gitd/methods.py +32 -0
- gitd/models.py +285 -0
- gitd/timestamps.py +22 -0
- gitd-0.7.0.dist-info/METADATA +77 -0
- gitd-0.7.0.dist-info/RECORD +13 -0
- gitd-0.7.0.dist-info/WHEEL +4 -0
gitd/__init__.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Client for gitd: instant, secretless git checkouts for agent sandboxes."""
|
|
2
|
+
|
|
3
|
+
from .client import Gitd
|
|
4
|
+
from .connection import DEFAULT_SOCKET, SOCKET_VARIABLE
|
|
5
|
+
from .errors import GitdError
|
|
6
|
+
from .models import (
|
|
7
|
+
Access,
|
|
8
|
+
AuditEntry,
|
|
9
|
+
Description,
|
|
10
|
+
Info,
|
|
11
|
+
Layers,
|
|
12
|
+
PendingSubmodule,
|
|
13
|
+
Probe,
|
|
14
|
+
Progress,
|
|
15
|
+
Published,
|
|
16
|
+
PublishedRepository,
|
|
17
|
+
Refusal,
|
|
18
|
+
Strategy,
|
|
19
|
+
Submodule,
|
|
20
|
+
Workspace,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"DEFAULT_SOCKET",
|
|
25
|
+
"SOCKET_VARIABLE",
|
|
26
|
+
"Access",
|
|
27
|
+
"AuditEntry",
|
|
28
|
+
"Description",
|
|
29
|
+
"Gitd",
|
|
30
|
+
"GitdError",
|
|
31
|
+
"Info",
|
|
32
|
+
"Layers",
|
|
33
|
+
"PendingSubmodule",
|
|
34
|
+
"Probe",
|
|
35
|
+
"Progress",
|
|
36
|
+
"Published",
|
|
37
|
+
"PublishedRepository",
|
|
38
|
+
"Refusal",
|
|
39
|
+
"Strategy",
|
|
40
|
+
"Submodule",
|
|
41
|
+
"Workspace",
|
|
42
|
+
]
|
gitd/client.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""The gitd client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from . import messages, methods
|
|
9
|
+
from .connection import Connection, OnProgress
|
|
10
|
+
from .models import AuditEntry, Description, Info, Probe, Published, Strategy, Workspace
|
|
11
|
+
from .timestamps import Timestamp
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Gitd:
|
|
15
|
+
"""A gitd on its Unix socket. No call takes a credential: gitd reads and publishes with
|
|
16
|
+
the ones it keeps.
|
|
17
|
+
|
|
18
|
+
with Gitd() as gitd:
|
|
19
|
+
workspace = gitd.prepare("https://github.com/acme/web", "/var/lib/gitd/workspaces/agent-1")
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, socket: str | None = None, *, timeout: float | None = None) -> None:
|
|
23
|
+
self.connection = Connection(socket, timeout)
|
|
24
|
+
|
|
25
|
+
def __enter__(self) -> Gitd:
|
|
26
|
+
return self
|
|
27
|
+
|
|
28
|
+
def __exit__(self, *exception: object) -> None:
|
|
29
|
+
self.close()
|
|
30
|
+
|
|
31
|
+
def close(self) -> None:
|
|
32
|
+
self.connection.close()
|
|
33
|
+
|
|
34
|
+
def info(self) -> Info:
|
|
35
|
+
"""Which gitd answers, and how it makes workspaces here and why."""
|
|
36
|
+
return Info.from_message(self.connection.call(methods.INFO, messages.InfoRequest()))
|
|
37
|
+
|
|
38
|
+
def probe(self, url: str) -> Probe:
|
|
39
|
+
"""Whether a repository can be read with the credentials gitd keeps, and its branches."""
|
|
40
|
+
return Probe.from_message(self.connection.call(methods.PROBE, messages.ProbeRequest(url=url)))
|
|
41
|
+
|
|
42
|
+
def prepare(
|
|
43
|
+
self,
|
|
44
|
+
url: str,
|
|
45
|
+
path: str,
|
|
46
|
+
*,
|
|
47
|
+
branch: str | None = None,
|
|
48
|
+
commit: str | None = None,
|
|
49
|
+
submodules: bool = False,
|
|
50
|
+
full_history: bool = False,
|
|
51
|
+
strategy: Strategy | None = None,
|
|
52
|
+
on_progress: OnProgress | None = None,
|
|
53
|
+
) -> Workspace:
|
|
54
|
+
"""Makes a workspace of a repository at a path under gitd's roots, at a commit, a
|
|
55
|
+
branch, or the default branch. strategy is a preference: gitd falls back to copy when
|
|
56
|
+
it cannot make one that way here."""
|
|
57
|
+
if commit:
|
|
58
|
+
revision = messages.Revision(commit=commit)
|
|
59
|
+
elif branch:
|
|
60
|
+
revision = messages.Revision(branch=branch)
|
|
61
|
+
else:
|
|
62
|
+
revision = None
|
|
63
|
+
request = messages.PrepareRequest(
|
|
64
|
+
url=url,
|
|
65
|
+
path=path,
|
|
66
|
+
revision=revision,
|
|
67
|
+
with_submodules=submodules,
|
|
68
|
+
full_history=full_history,
|
|
69
|
+
strategy=Strategy.preference(strategy),
|
|
70
|
+
)
|
|
71
|
+
return Workspace.from_message(path, self.connection.follow(methods.PREPARE, request, on_progress))
|
|
72
|
+
|
|
73
|
+
def update(
|
|
74
|
+
self,
|
|
75
|
+
path: str,
|
|
76
|
+
*,
|
|
77
|
+
branch: str | None = None,
|
|
78
|
+
submodules: bool = False,
|
|
79
|
+
on_progress: OnProgress | None = None,
|
|
80
|
+
) -> Workspace:
|
|
81
|
+
"""Brings a workspace to the tip of its branch, or of the branch given."""
|
|
82
|
+
request = messages.UpdateRequest(path=path, branch=branch or "", with_submodules=submodules)
|
|
83
|
+
return Workspace.from_message(path, self.connection.follow(methods.UPDATE, request, on_progress))
|
|
84
|
+
|
|
85
|
+
def checkout(
|
|
86
|
+
self,
|
|
87
|
+
path: str,
|
|
88
|
+
commit: str,
|
|
89
|
+
*,
|
|
90
|
+
submodules: bool = False,
|
|
91
|
+
on_progress: OnProgress | None = None,
|
|
92
|
+
) -> Workspace:
|
|
93
|
+
"""Moves a workspace to a commit, as a rollback does."""
|
|
94
|
+
request = messages.CheckoutRequest(path=path, commit=commit, with_submodules=submodules)
|
|
95
|
+
return Workspace.from_message(path, self.connection.follow(methods.CHECKOUT, request, on_progress))
|
|
96
|
+
|
|
97
|
+
def describe(self, path: str) -> Description:
|
|
98
|
+
"""What a workspace is on disk, without the network."""
|
|
99
|
+
response = self.connection.call(methods.DESCRIBE, messages.DescribeRequest(path=path))
|
|
100
|
+
return Description.from_message(response)
|
|
101
|
+
|
|
102
|
+
def publish(self, path: str, branch: str) -> Published:
|
|
103
|
+
"""Pushes what was committed in a workspace to a new branch, with the token gitd keeps."""
|
|
104
|
+
response = self.connection.call(methods.PUBLISH, messages.PublishRequest(path=path, branch=branch))
|
|
105
|
+
return Published.from_message(response)
|
|
106
|
+
|
|
107
|
+
def remove(self, path: str) -> None:
|
|
108
|
+
"""Deletes a workspace; its pool stays."""
|
|
109
|
+
self.connection.call(methods.REMOVE, messages.RemoveRequest(path=path))
|
|
110
|
+
|
|
111
|
+
def audit(
|
|
112
|
+
self,
|
|
113
|
+
*,
|
|
114
|
+
since: datetime | None = None,
|
|
115
|
+
url: str | None = None,
|
|
116
|
+
limit: int | None = None,
|
|
117
|
+
) -> list[AuditEntry]:
|
|
118
|
+
"""The audit log: every publish, done or refused, newest first; from since on, of
|
|
119
|
+
the repository at url, at most limit of them (100 by default, never more than 1000)."""
|
|
120
|
+
request: Any = messages.AuditRequest(url=url or "", limit=limit or 0)
|
|
121
|
+
if since is not None:
|
|
122
|
+
Timestamp.fill(request.since, since)
|
|
123
|
+
response = self.connection.call(methods.AUDIT, request)
|
|
124
|
+
return [AuditEntry.from_message(entry) for entry in response.entries]
|
gitd/connection.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""The channel to gitd's Unix socket, and how a call on it is made and read."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import grpc
|
|
10
|
+
from google.protobuf.message import Message
|
|
11
|
+
|
|
12
|
+
from .errors import GitdError
|
|
13
|
+
from .methods import Method
|
|
14
|
+
from .models import Progress
|
|
15
|
+
|
|
16
|
+
DEFAULT_SOCKET = "/run/gitd/gitd.sock"
|
|
17
|
+
SOCKET_VARIABLE = "GITD_SOCKET"
|
|
18
|
+
|
|
19
|
+
OnProgress = Callable[[Progress], None]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Connection:
|
|
23
|
+
"""A channel to gitd. The socket defaults to $GITD_SOCKET, then /run/gitd/gitd.sock; the
|
|
24
|
+
timeout, in seconds, bounds every call, which gitd otherwise bounds itself."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, socket: str | None, timeout: float | None) -> None:
|
|
27
|
+
self.socket = socket or os.environ.get(SOCKET_VARIABLE) or DEFAULT_SOCKET
|
|
28
|
+
self.timeout = timeout
|
|
29
|
+
self.channel = grpc.insecure_channel(f"unix:{self.socket}")
|
|
30
|
+
|
|
31
|
+
def close(self) -> None:
|
|
32
|
+
self.channel.close()
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def serialize(message: Message) -> bytes:
|
|
36
|
+
return message.SerializeToString()
|
|
37
|
+
|
|
38
|
+
def call(self, method: Method, request: Message) -> Any:
|
|
39
|
+
"""Makes a call that answers once."""
|
|
40
|
+
invoke: grpc.UnaryUnaryMultiCallable[Message, Any] = self.channel.unary_unary(
|
|
41
|
+
method.path,
|
|
42
|
+
request_serializer=self.serialize,
|
|
43
|
+
response_deserializer=method.response.FromString,
|
|
44
|
+
)
|
|
45
|
+
try:
|
|
46
|
+
return invoke(request, timeout=self.timeout)
|
|
47
|
+
except grpc.RpcError as error:
|
|
48
|
+
raise GitdError.from_rpc_error(error) from None
|
|
49
|
+
|
|
50
|
+
def follow(
|
|
51
|
+
self,
|
|
52
|
+
method: Method,
|
|
53
|
+
request: Message,
|
|
54
|
+
on_progress: OnProgress | None,
|
|
55
|
+
) -> Any:
|
|
56
|
+
"""Makes a call that streams its progress, and reads it to its result."""
|
|
57
|
+
invoke: grpc.UnaryStreamMultiCallable[Message, Any] = self.channel.unary_stream(
|
|
58
|
+
method.path,
|
|
59
|
+
request_serializer=self.serialize,
|
|
60
|
+
response_deserializer=method.response.FromString,
|
|
61
|
+
)
|
|
62
|
+
try:
|
|
63
|
+
for response in invoke(request, timeout=self.timeout):
|
|
64
|
+
event = response.WhichOneof("event")
|
|
65
|
+
if event == "progress" and on_progress is not None:
|
|
66
|
+
on_progress(Progress.from_message(response.progress))
|
|
67
|
+
elif event == "result":
|
|
68
|
+
return response.result
|
|
69
|
+
except grpc.RpcError as error:
|
|
70
|
+
raise GitdError.from_rpc_error(error) from None
|
|
71
|
+
raise GitdError("", "gitd ended the call without a result")
|