-
Couldn't load subscription status.
- Fork 354
Feat: implement runtime CLI auth #3209
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: devel
Are you sure you want to change the base?
Changes from all commits
bc664fc
ebb7f53
113dace
8e6bead
1fcd144
c7007d8
23ba45e
e78d6ab
319f12f
0cc9a88
31f60c5
63541a9
b24a095
0e49596
e7c5cc4
d09e84c
ea246a2
6bfb331
836dfb7
83b871b
b16f7b6
5ec516c
f039184
9a12719
c756fa5
6442aea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| import argparse | ||
| import time | ||
| from typing import Optional | ||
| from dlt.common.configuration.plugins import SupportsCliCommand | ||
|
|
||
| from dlt._workspace._workspace_context import active | ||
| from dlt._workspace.exceptions import ( | ||
| LocalWorkspaceIdNotSet, | ||
| RuntimeNotAuthenticated, | ||
| WorkspaceIdMismatch, | ||
| ) | ||
| from dlt._workspace.runtime import RuntimeAuthService, get_auth_client | ||
| from dlt._workspace.runtime_clients.auth.api.github import github_oauth_complete, github_oauth_start | ||
| from dlt._workspace.cli import echo as fmt | ||
|
|
||
|
|
||
| class RuntimeCommand(SupportsCliCommand): | ||
| command = "runtime" | ||
| help_string = "Connect to dltHub Runtime and run your code remotely" | ||
| description = """ | ||
| Allows to connect to the dltHub Runtime, deploy and run local workspaces there. Requires dltHub license. | ||
| """ | ||
|
|
||
| def configure_parser(self, parser: argparse.ArgumentParser) -> None: | ||
| self.parser = parser | ||
|
|
||
| subparsers = parser.add_subparsers( | ||
| title="Available subcommands", dest="runtime_command", required=False | ||
| ) | ||
|
|
||
| subparsers.add_parser( | ||
| "login", | ||
| help="Login to the Runtime using Github OAuth", | ||
| description="Login to the Runtime using Github OAuth", | ||
| ) | ||
|
|
||
| subparsers.add_parser( | ||
| "logout", | ||
| help="Logout from the Runtime", | ||
| description="Logout from the Runtime", | ||
| ) | ||
|
|
||
| def execute(self, args: argparse.Namespace) -> None: | ||
| if args.runtime_command == "login": | ||
| login() | ||
| elif args.runtime_command == "logout": | ||
| logout() | ||
| else: | ||
| self.parser.print_usage() | ||
|
|
||
|
|
||
| def login() -> None: | ||
| auth_service = RuntimeAuthService(run_context=active()) | ||
| try: | ||
| auth_info = auth_service.authenticate() | ||
| fmt.echo("Already logged in as %s" % fmt.bold(auth_info.email)) | ||
| connect(auth_service=auth_service) | ||
| except RuntimeNotAuthenticated: | ||
| fmt.echo("Logging in with Github OAuth") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. makes sense to extract for testing. is it testable at all? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in the current “integration” tests, stdout of the process is tested, not a bad approach as it seems to me, although a lot of mocking, I must admit. I can extract unit tests for the Auth class, would you rather me do it instead of integration tests? |
||
| client = get_auth_client() | ||
|
|
||
| # start device flow | ||
| login_request = github_oauth_start.sync(client=client) | ||
| if not isinstance(login_request, github_oauth_start.GithubDeviceFlowStartResponse): | ||
| raise RuntimeError("Failed to log in with Github OAuth") | ||
| fmt.echo( | ||
| "Please go to %s and enter the code %s" | ||
| % (fmt.bold(login_request.verification_uri), fmt.bold(login_request.user_code)) | ||
| ) | ||
| fmt.echo("Waiting for response from Github...") | ||
|
|
||
| while True: | ||
| time.sleep(login_request.interval) | ||
| token_response = github_oauth_complete.sync( | ||
| client=client, | ||
| body=github_oauth_complete.GithubDeviceFlowLoginRequest( | ||
| device_code=login_request.device_code | ||
| ), | ||
| ) | ||
| if isinstance(token_response, github_oauth_complete.LoginResponse): | ||
| auth_info = auth_service.login(token_response.jwt) | ||
| fmt.echo("Logged in as %s" % fmt.bold(auth_info.email)) | ||
| connect(auth_service=auth_service) | ||
| break | ||
| elif isinstance(token_response, github_oauth_complete.ErrorResponse400): | ||
| raise RuntimeError("Failed to complete authentication with Github") | ||
|
|
||
|
|
||
| def logout() -> None: | ||
| auth_service = RuntimeAuthService(run_context=active()) | ||
| auth_service.logout() | ||
| fmt.echo("Logged out") | ||
|
|
||
|
|
||
| def connect(auth_service: Optional[RuntimeAuthService] = None) -> None: | ||
| if auth_service is None: | ||
| auth_service = RuntimeAuthService(run_context=active()) | ||
| auth_service.authenticate() | ||
|
|
||
| try: | ||
| auth_service.connect() | ||
| except LocalWorkspaceIdNotSet: | ||
| should_overwrite = fmt.confirm( | ||
| "No workspace id found in local config. Do you want to connect local workspace to the" | ||
| " remote one?", | ||
| default=True, | ||
| ) | ||
| if should_overwrite: | ||
| auth_service.overwrite_local_workspace_id() | ||
| fmt.echo("Using remote workspace id") | ||
| else: | ||
| raise RuntimeError("Local workspace is not connected to the remote one") | ||
| except WorkspaceIdMismatch as e: | ||
| fmt.warning( | ||
| "Workspace id in local config (%s) is not the same as remote workspace id (%s)" | ||
| % (e.local_workspace_id, e.remote_workspace_id) | ||
| ) | ||
| should_overwrite = fmt.confirm( | ||
| "Do you want to overwrite the local workspace id with the remote one?", | ||
| default=True, | ||
| ) | ||
| if should_overwrite: | ||
| auth_service.overwrite_local_workspace_id() | ||
| fmt.echo("Local workspace id overwritten with remote workspace id") | ||
| else: | ||
| raise RuntimeError("Unable to synchronise remote and local workspaces") | ||
| fmt.echo("Authorized to workspace %s" % fmt.bold(auth_service.workspace_id)) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please generate client from openapi specs that we keep in this repo and can always use to recreate old clients.
runtime_client/auth.spec.yamlor smthThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as far as I remember, we’ve discussed offline that we store specs and generated code in OSS _workspace for the time being to avoid generating a separate package, which is what I’m doing here. happy to change the path of saving the spec, or refactor otherwise - let’s discuss?