gitd 0.8.0__tar.gz → 0.10.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: gitd
3
- Version: 0.8.0
3
+ Version: 0.10.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,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "gitd"
7
- version = "0.8.0"
7
+ version = "0.10.0"
8
8
  description = "Client for gitd: instant, secretless git checkouts for agent sandboxes"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -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. No call on workspaces takes a credential: gitd reads and
23
- publishes with the ones it keeps, which a platform manages through gitd.credentials, and
24
- publishing follows the rules it sets through gitd.publishing.
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__(self, socket: str | None = None, *, timeout: float | None = None) -> None:
31
- self.connection = Connection(socket, timeout)
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
- request: Any = messages.PublishRequest(path=path, branch=branch, requested_by=requested_by)
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's Unix socket, and how a call on it is made and read."""
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. The socket defaults to $GITD_SOCKET, then /run/gitd/gitd.sock; the
24
- timeout, in seconds, bounds every call, which gitd otherwise bounds itself."""
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__(self, socket: str | None, timeout: float | None) -> None:
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
- self.channel = grpc.insecure_channel(f"unix:{self.socket}")
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