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 commands to manage roles #382

Merged
merged 1 commit into from
Dec 9, 2021
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
2 changes: 2 additions & 0 deletions CHANGES/382.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added commands to manage roles and their association with users and groups.
Added commands to add and remove users.
61 changes: 39 additions & 22 deletions pulpcore/cli/common/generic.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import gettext
import json
import re
from functools import lru_cache
from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Tuple, Type, TypeVar

import click
Expand Down Expand Up @@ -34,11 +35,21 @@ def __init__(
self,
*args: Any,
allowed_with_contexts: Optional[Tuple[Type[PulpEntityContext]]] = None,
needs_plugins: Optional[List[PluginRequirement]] = None,
**kwargs: Any,
):
self.allowed_with_contexts = allowed_with_contexts
self.needs_plugins = needs_plugins
super().__init__(*args, **kwargs)

def invoke(self, ctx: click.Context) -> Any:
if self.needs_plugins:
pulp_ctx = ctx.find_object(PulpContext)
assert pulp_ctx is not None
for plugin_requirement in self.needs_plugins:
pulp_ctx.needs_plugin(plugin_requirement)
return super().invoke(ctx)

def get_short_help_str(self, limit: int = 45) -> str:
return self.short_help or ""

Expand Down Expand Up @@ -178,23 +189,31 @@ def handle_parse_result(
# Option callbacks


def _href_callback(
ctx: click.Context, param: click.Parameter, value: Optional[str]
) -> Optional[str]:
if value is not None:
entity_ctx = ctx.find_object(PulpEntityContext)
assert entity_ctx is not None
entity_ctx.pulp_href = value
return value
@lru_cache(typed=True)
def lookup_callback(
attribute: str, context_class: Type[PulpEntityContext] = PulpEntityContext
) -> Callable[[click.Context, click.Parameter, Optional[str]], Optional[str]]:
def _callback(
ctx: click.Context, param: click.Parameter, value: Optional[str]
) -> Optional[str]:
if value is not None:
if value == "":
value = "null"
Comment on lines +200 to +201
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this fix the issue where we can't set some fields back to None ("null") after setting it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is related to the --object parameter below. Explanation there.

entity_ctx = ctx.find_object(context_class)
assert entity_ctx is not None
entity_ctx.entity = {attribute: value}
return value

return _callback


def _name_callback(
def _href_callback(
ctx: click.Context, param: click.Parameter, value: Optional[str]
) -> Optional[str]:
if value is not None:
entity_ctx = ctx.find_object(PulpEntityContext)
assert entity_ctx is not None
entity_ctx.entity = {"name": value}
entity_ctx.pulp_href = value
return value


Expand All @@ -208,16 +227,6 @@ def _repository_href_callback(
return value


def _repository_callback(
ctx: click.Context, param: click.Parameter, value: Optional[str]
) -> Optional[str]:
if value is not None:
repository_ctx = ctx.find_object(PulpRepositoryContext)
assert repository_ctx is not None
repository_ctx.entity = {"name": value}
return value


def _version_callback(
ctx: click.Context, param: click.Parameter, value: Optional[int]
) -> Optional[int]:
Expand Down Expand Up @@ -320,6 +329,14 @@ def parse_size_callback(ctx: click.Context, param: click.Parameter, value: str)
return int(float(number) * units[unit])


def null_callback(
ctx: click.Context, param: click.Parameter, value: Optional[str]
) -> Optional[str]:
if value == "":
return "null"
return value


##############################################################################
# Decorator common options

Expand Down Expand Up @@ -486,7 +503,7 @@ def _type_callback(ctx: click.Context, param: click.Parameter, value: Optional[s
name_option = pulp_option(
"--name",
help=_("Name of the {entity}"),
callback=_name_callback,
callback=lookup_callback("name"),
expose_value=False,
)

Expand All @@ -500,7 +517,7 @@ def _type_callback(ctx: click.Context, param: click.Parameter, value: Optional[s
repository_option = click.option(
"--repository",
help=_("Name of the repository"),
callback=_repository_callback,
callback=lookup_callback("name", PulpRepositoryContext),
expose_value=False,
)

Expand Down
2 changes: 1 addition & 1 deletion pulpcore/cli/common/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ def call(
self.debug_callback(1, f"{method} {request.url}")
for key, value in request.headers.items():
self.debug_callback(2, f" {key}: {value}")
if request.body:
if request.body is not None:
self.debug_callback(2, f"{request.body!r}")
if self.safe_calls_only and method.upper() not in SAFE_METHODS:
raise OpenAPIError(_("Call aborted due to safe mode"))
Expand Down
2 changes: 2 additions & 0 deletions pulpcore/cli/container/distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def distribution(ctx: click.Context, pulp_ctx: PulpContext, distribution_type: s
click.option(
"--version", type=int, help=_("a repository version number, leave blank for latest")
),
click.option("--private/--public", default=None),
]

distribution.add_command(list_command(decorators=filter_options))
Expand All @@ -84,6 +85,7 @@ def distribution(ctx: click.Context, pulp_ctx: PulpContext, distribution_type: s
@click.option("--base-path")
@repository_option
@click.option("--version", type=int, help=_("a repository version number, leave blank for latest"))
@click.option("--private/--public", default=None)
@pass_entity_context
@pass_pulp_context
def update(
Expand Down
2 changes: 2 additions & 0 deletions pulpcore/cli/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pulpcore.cli.core.orphan import orphan
from pulpcore.cli.core.orphans import orphans
from pulpcore.cli.core.repository import repository
from pulpcore.cli.core.role import role
from pulpcore.cli.core.show import show
from pulpcore.cli.core.signing_service import signing_service
from pulpcore.cli.core.status import status
Expand All @@ -34,6 +35,7 @@
main.add_command(orphan)
main.add_command(orphans) # This one is deprecated
main.add_command(repository)
main.add_command(role)
main.add_command(show)
main.add_command(signing_service)
main.add_command(status)
Expand Down
56 changes: 56 additions & 0 deletions pulpcore/cli/core/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,26 @@ def HREF(self) -> str: # type:ignore
return "groups_object_permission_href"


class PulpGroupRoleContext(PulpEntityContext):
ENTITY = _("group role")
ENTITIES = _("group roles")
HREF = "groups_group_role_href"
LIST_ID = "groups_roles_list"
READ_ID = "groups_roles_read"
CREATE_ID = "groups_roles_create"
DELETE_ID = "groups_roles_delete"
NULLABLES = {"content_object"}
group_ctx: PulpGroupContext

def __init__(self, pulp_ctx: PulpContext, group_ctx: PulpGroupContext) -> None:
super().__init__(pulp_ctx)
self.group_ctx = group_ctx

@property
def scope(self) -> Dict[str, Any]:
return {self.group_ctx.HREF: self.group_ctx.pulp_href}


class PulpGroupUserContext(PulpEntityContext):
ENTITY = _("group user")
ENTITIES = _("group users")
Expand Down Expand Up @@ -262,6 +282,18 @@ def remove(self, href: str, users: Optional[List[str]], groups: Optional[List[st
return self.pulp_ctx.call(self.REMOVE_ID, parameters={self.HREF: href}, body=body)


class PulpRoleContext(PulpEntityContext):
ENTITY = _("role")
ENTITIES = _("roles")
HREF = "role_href"
LIST_ID = "roles_list"
READ_ID = "roles_read"
CREATE_ID = "roles_create"
UPDATE_ID = "roles_partial_update"
DELETE_ID = "roles_delete"
NULLABLES = {"description"}


class PulpSigningServiceContext(PulpEntityContext):
ENTITY = _("signing service")
ENTITIES = _("signing services")
Expand Down Expand Up @@ -329,6 +361,30 @@ class PulpUserContext(PulpEntityContext):
HREF = "auth_user_href"
LIST_ID = "users_list"
READ_ID = "users_read"
CREATE_ID = "users_create"
UPDATE_ID = "users_partial_update"
DELETE_ID = "users_delete"
NULLABLES = {"password"}


class PulpUserRoleContext(PulpEntityContext):
ENTITY = _("user role")
ENTITIES = _("user roles")
HREF = "auth_users_user_role_href"
LIST_ID = "users_roles_list"
READ_ID = "users_roles_read"
CREATE_ID = "users_roles_create"
DELETE_ID = "users_roles_delete"
NULLABLES = {"content_object"}
user_ctx: PulpUserContext
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this not a ClassVar?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it it meant to hold a Context instance (with a specific user.pk)


def __init__(self, pulp_ctx: PulpContext, user_ctx: PulpUserContext) -> None:
super().__init__(pulp_ctx)
self.user_ctx = user_ctx

@property
def scope(self) -> Dict[str, Any]:
return {self.user_ctx.HREF: self.user_ctx.pulp_href}


class PulpWorkerContext(PulpEntityContext):
Expand Down
Loading