Skip to content
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

Add support for projects in service-accounts #2965

Merged
merged 4 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.D/2965.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support projects in service account CLI commands and SDK methods.
4 changes: 3 additions & 1 deletion CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -2983,7 +2983,9 @@ neuro service-account create [OPTIONS]
Name | Description|
|----|------------|
|_--help_|Show this message and exit.|
|_\--default-cluster CLUSTER_NAME_|Service account default cluster. Current cluster will be used if not specified|
|_\--default-cluster CLUSTER_|Service account default cluster. Current cluster will be used if not specified|
|_\--default-org ORG_|Service account default organization. Current org will be used if not specified|
|_\--default-project PROJECT_|Service account default project. Current project will be used if not specified|
|_--name NAME_|Optional service account name|


Expand Down
4 changes: 3 additions & 1 deletion neuro-cli/docs/service-account.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ Create a service account.
| Name | Description |
| :--- | :--- |
| _--help_ | Show this message and exit. |
| _--default-cluster CLUSTER\_NAME_ | Service account default cluster. Current cluster will be used if not specified |
| _--default-cluster CLUSTER_ | Service account default cluster. Current cluster will be used if not specified |
| _--default-org ORG_ | Service account default organization. Current org will be used if not specified |
| _--default-project PROJECT_ | Service account default project. Current project will be used if not specified |
| _--name NAME_ | Optional service account name |


Expand Down
1 change: 1 addition & 0 deletions neuro-cli/src/neuro_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def _print_welcome(root: Root, url: URL) -> None:
f"Logged into {url} as [u]{root.client.config.username}[/u]"
f", current cluster is [b]{root.client.config.cluster_name}[/b], "
f"org is [b]{root.client.config.org_name or ORG.NO_ORG_STR}[/b]",
f"project is [b]{root.client.config.project_name}[/b]",
markup=True,
)
else:
Expand Down
48 changes: 35 additions & 13 deletions neuro-cli/src/neuro_cli/formatters/service_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from neuro_sdk import ServiceAccount

from neuro_cli.click_types import ORG
from neuro_cli.formatters.utils import DatetimeFormatter


Expand Down Expand Up @@ -75,25 +76,46 @@ def __call__(self, account: ServiceAccount) -> RenderableType:
table.add_row("Role", account.role)
table.add_row("Owner", account.owner)
table.add_row("Default cluster", account.default_cluster)
table.add_row("Default org", account.default_org or ORG.NO_ORG_STR)
table.add_row("Default project", account.default_project)
table.add_row("Created at", self._datetime_formatter(account.created_at))
return table


def service_account_token_fmtr(token: str) -> RenderableType:
token_data = json.loads(base64.b64decode(token.encode()).decode())
def service_account_token_fmtr(token: str, account: ServiceAccount) -> RenderableType:
token_data: dict[str, str] = json.loads(base64.b64decode(token.encode()).decode())
auth_token = token_data["token"]

return Text.from_markup(
"\n".join(
org_name = token_data.get("org_name")
share_project_cmd_hint = (
f"[b]neuro admin add-project-user {f'--org {org_name}' if org_name else ''}"
f" {token_data.get('cluster')} "
f" {token_data.get('project_name')}"
f" {account.role}"
" reader|writer|manager|admin[/b]\n"
)

lines = [
"Full token with cluster and API url embedded (this value can "
"be used as [b]NEURO_PASSED_CONFIG[/b] environment variable):\n",
token,
"\nJust auth token (this value can be passed to [b]neuro config"
" login-with-token[/b]):\n",
auth_token,
"\n[b red]Save it to some secure place, you will be unable to "
"retrieve it later![/b red]",
"\nTo allow access to your current project for this service account, "
"perform:\n",
share_project_cmd_hint,
]
if org_name:
lines.extend(
[
"Full token with cluster and API url embedded (this value can "
"be used as [b]NEURO_PASSED_CONFIG[/b] environment variable):\n",
token,
"\nJust auth token (this value can be passed to [b]neuro config"
" login-with-token[/b]):\n",
auth_token,
"\n[b red]Save it to some secure place, you will be unable to "
"retrieve it later![/b red]",
"\nTo allow access to your current org for this service account, "
"perform:\n",
f"[b]neuro admin add-org-user {org_name} {account.role}"
" reader|writer|manager|admin[/b]\n",
]
)
)

return Text.from_markup("\n".join(lines))
24 changes: 21 additions & 3 deletions neuro-cli/src/neuro_cli/service_accounts.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional, Sequence

from neuro_cli.click_types import SERVICE_ACCOUNT
from neuro_cli.click_types import CLUSTER, ORG, PROJECT, SERVICE_ACCOUNT
from neuro_cli.formatters.service_accounts import (
BaseServiceAccountsFormatter,
ServiceAccountFormatter,
Expand Down Expand Up @@ -54,15 +54,31 @@ async def ls(root: Root) -> None:
)
@option(
"--default-cluster",
metavar="CLUSTER_NAME",
type=CLUSTER,
help="Service account default cluster. Current cluster will"
" be used if not specified",
default=None,
)
@option(
"--default-org",
type=ORG,
help="Service account default organization. Current org will"
" be used if not specified",
default=None,
)
@option(
"--default-project",
type=PROJECT,
help="Service account default project. Current project will"
" be used if not specified",
default=None,
)
async def create(
root: Root,
name: Optional[str],
default_cluster: Optional[str],
default_org: Optional[str],
default_project: Optional[str],
) -> None:
"""
Create a service account.
Expand All @@ -71,6 +87,8 @@ async def create(
account, token = await root.client.service_accounts.create(
name=name,
default_cluster=default_cluster,
default_org=default_org,
default_project=default_project,
)
fmtr = ServiceAccountFormatter(
datetime_formatter=get_datetime_formatter(root.iso_datetime_format)
Expand All @@ -81,7 +99,7 @@ async def create(
# No pager here as it can make it harder to copy generated token
root.print(fmtr(account))
root.print("")
root.print(service_account_token_fmtr(token), soft_wrap=True)
root.print(service_account_token_fmtr(token, account), soft_wrap=True)


@command()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Id account
Name test1
Role test-role
Owner user
Default cluster cluster
Default org NO_ORG
Default project some-project
Created at Mar 04 2017
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Id account
Name test1
Role test-role
Owner user
Default cluster cluster
Default org some-org
Default project some-project
Created at Mar 04 2017
12 changes: 11 additions & 1 deletion neuro-cli/tests/unit/formatters/test_service_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@
from neuro_cli.formatters.utils import format_datetime_human


def test_service_account_formatter(rich_cmp: Any) -> None:
@pytest.mark.parametrize(
"org",
[None, "some-org"],
)
def test_service_account_formatter(rich_cmp: Any, org: Any) -> None:
account = ServiceAccount(
id="account",
name="test1",
role="test-role",
owner="user",
default_cluster="cluster",
default_project="some-project",
default_org=org,
created_at=isoparse("2017-03-04T12:28:59.759433+00:00"),
)
fmtr = ServiceAccountFormatter(datetime_formatter=format_datetime_human)
Expand All @@ -35,6 +41,7 @@ def service_accounts_list() -> List[ServiceAccount]:
role="test-role",
owner="user",
default_cluster="cluster",
default_project="test-project",
created_at=isoparse("2017-03-04T12:28:59.759433+00:00"),
),
ServiceAccount(
Expand All @@ -43,6 +50,7 @@ def service_accounts_list() -> List[ServiceAccount]:
role="test-role",
owner="user",
default_cluster="cluster",
default_project="test-project",
created_at=isoparse("2017-03-04T12:28:59.759433+00:00"),
),
ServiceAccount(
Expand All @@ -51,6 +59,7 @@ def service_accounts_list() -> List[ServiceAccount]:
role="test-role",
owner="user",
default_cluster="cluster",
default_project="test-project",
created_at=isoparse("2017-03-04T12:28:59.759433+00:00"),
),
ServiceAccount(
Expand All @@ -59,6 +68,7 @@ def service_accounts_list() -> List[ServiceAccount]:
role="test-role",
owner="user",
default_cluster="cluster",
default_project="test-project",
created_at=isoparse("2017-03-04T12:28:59.759433+00:00"),
),
]
Expand Down
2 changes: 2 additions & 0 deletions neuro-cli/tests/unit/test_shell_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,7 @@ def test_service_account_autocomplete(run_autocomplete: _RunAC) -> None:
role="test-role-1",
owner="user",
default_cluster="cluster1",
default_project="user",
created_at=created_at,
),
ServiceAccount(
Expand All @@ -1295,6 +1296,7 @@ def test_service_account_autocomplete(run_autocomplete: _RunAC) -> None:
role="test-role-2",
owner="user",
default_cluster="cluster2",
default_project="project",
created_at=created_at,
),
]
Expand Down
10 changes: 10 additions & 0 deletions neuro-sdk/src/neuro_sdk/_service_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class ServiceAccount:
default_cluster: str
role: str
created_at: datetime
default_project: str
default_org: Optional[str] = None


@rewrite_module
Expand All @@ -38,6 +40,8 @@ def _parse_account_payload(self, payload: Mapping[str, Any]) -> ServiceAccount:
default_cluster=payload["default_cluster"],
role=payload["role"],
created_at=isoparse(payload["created_at"]),
default_project=payload["default_project"],
default_org=payload.get("default_org"),
)

@asyncgeneratorcontextmanager
Expand All @@ -53,13 +57,19 @@ async def create(
self,
name: Optional[str] = None,
default_cluster: Optional[str] = None,
default_org: Optional[str] = None,
default_project: Optional[str] = None,
) -> Tuple[ServiceAccount, str]:
url = self._config.service_accounts_url
auth = await self._config._api_auth()
data = {
"name": name,
"default_cluster": default_cluster or self._config.cluster_name,
"default_project": default_project or self._config.project_name_or_raise,
}
default_org = default_org or self._config.org_name
if default_org:
data["default_org"] = default_org
async with self._core.request("POST", url, auth=auth, json=data) as resp:
payload = await resp.json()
return self._parse_account_payload(payload), payload["token"]
Expand Down
30 changes: 30 additions & 0 deletions neuro-sdk/tests/test_service_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ async def handler(request: web.Request) -> web.Response:
"name": "test1",
"role": "test-role-1",
"default_cluster": "cluster1",
"default_project": "user",
"role_deleted": False,
"created_at": created_at.isoformat(),
},
Expand All @@ -35,9 +36,21 @@ async def handler(request: web.Request) -> web.Response:
"name": "test2",
"role": "test-role-2",
"default_cluster": "cluster2",
"default_project": "user",
"role_deleted": True,
"created_at": created_at.isoformat(),
},
{
"id": "account-3",
"owner": "user",
"name": "test3",
"role": "test-role-3",
"default_cluster": "cluster1",
"default_project": "test-project",
"default_org": "test-org",
"role_deleted": False,
"created_at": created_at.isoformat(),
},
]
)

Expand All @@ -60,6 +73,7 @@ async def handler(request: web.Request) -> web.Response:
role="test-role-1",
owner="user",
default_cluster="cluster1",
default_project="user",
created_at=created_at,
),
ServiceAccount(
Expand All @@ -68,6 +82,17 @@ async def handler(request: web.Request) -> web.Response:
role="test-role-2",
owner="user",
default_cluster="cluster2",
default_project="user",
created_at=created_at,
),
ServiceAccount(
id="account-3",
name="test3",
role="test-role-3",
owner="user",
default_cluster="cluster1",
default_project="test-project",
default_org="test-org",
created_at=created_at,
),
]
Expand All @@ -85,6 +110,7 @@ async def handler(request: web.Request) -> web.Response:
assert data == {
"name": "test-account",
"default_cluster": "cluster",
"default_project": "test-project",
}
return web.json_response(
{
Expand All @@ -93,6 +119,7 @@ async def handler(request: web.Request) -> web.Response:
"name": "test-account",
"role": "test-role",
"default_cluster": "cluster",
"default_project": "test-project",
"role_deleted": False,
"created_at": created_at.isoformat(),
"token": "fake-token",
Expand All @@ -114,6 +141,7 @@ async def handler(request: web.Request) -> web.Response:
role="test-role",
owner="user",
default_cluster="cluster",
default_project="test-project",
created_at=created_at,
)
assert token == "fake-token"
Expand All @@ -135,6 +163,7 @@ async def handler(request: web.Request) -> web.Response:
"name": "test-account",
"role": "test-role",
"default_cluster": "cluster",
"default_project": "test-project",
"role_deleted": False,
"created_at": created_at.isoformat(),
},
Expand All @@ -153,6 +182,7 @@ async def handler(request: web.Request) -> web.Response:
role="test-role",
owner="user",
default_cluster="cluster",
default_project="test-project",
created_at=created_at,
)

Expand Down