Skip to content

Commit

Permalink
Merge pull request #902 from sirosen/introduce-gcs-command
Browse files Browse the repository at this point in the history
Introduce 'globus gcs' as a collection of aliases for existing commands
  • Loading branch information
sirosen authored Dec 6, 2023
2 parents a2effdd + ee9a7ac commit 7db9486
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 72 deletions.
4 changes: 4 additions & 0 deletions changelog.d/20231204_165730_sirosen_introduce_gcs_command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Enhancements

* Introduce a new command, `globus gcs`, for GCSv5 Collection, Storage Gateway, and
User Credential management.
1 change: 1 addition & 0 deletions src/globus_cli/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"endpoint": ("endpoint", "endpoint_command"),
"flows": ("flows", "flows_command"),
"gcp": ("gcp", "gcp_command"),
"gcs": ("gcs", "gcs_command"),
"get-identities": ("get_identities", "get_identities_command"),
"group": ("group", "group_command"),
"list-commands": ("list_commands", "list_commands"),
Expand Down
13 changes: 13 additions & 0 deletions src/globus_cli/commands/gcs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from globus_cli.parsing import group


@group(
"gcs",
lazy_subcommands={
"collection": ("collection", "collection_command"),
"storage-gateway": ("endpoint.storage_gateway", "storage_gateway_command"),
"user-credential": ("endpoint.user_credential", "user_credential_command"),
},
)
def gcs_command() -> None:
"""Manage Globus Connect Server endpoints"""
90 changes: 66 additions & 24 deletions src/globus_cli/endpointish/errors.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,67 @@
from __future__ import annotations

import dataclasses

from .entity_type import EntityType

SHOULD_USE_MAP = {
"globus collection delete": [
("globus endpoint delete", EntityType.traditional_endpoints()),
],
"globus endpoint delete": [
("globus collection delete", EntityType.gcsv5_collections()),
],
"globus collection show": [
("globus endpoint show", EntityType.non_gcsv5_collection_types()),
],
"globus endpoint show": [
("globus collection show", EntityType.gcsv5_collections()),
],
"globus collection update": [
("globus endpoint update", EntityType.traditional_endpoints()),
],
"globus endpoint update": [
("globus collection update", EntityType.gcsv5_collections()),
],
}

@dataclasses.dataclass
class ShouldUse:
if_types: tuple[EntityType, ...]
src_commands: tuple[str, ...]
dst_command: str


# listed in precedence order; matching uses `if_types`+`src_commands`
SHOULD_USE_MAPPINGS = (
# update [gcp]
ShouldUse(
if_types=(EntityType.GCP_MAPPED,),
src_commands=("globus collection update", "globus gcs collection update"),
dst_command="globus gcp update mapped",
),
ShouldUse(
if_types=(EntityType.GCP_GUEST,),
src_commands=("globus collection update", "globus gcs collection update"),
dst_command="globus gcp update guest",
),
# update [gcsv4 endpoints (host/share)]
ShouldUse(
if_types=EntityType.traditional_endpoints(),
src_commands=("globus collection update", "globus gcs collection update"),
dst_command="globus endpoint update",
),
# update [gcsv5 collections]
ShouldUse(
if_types=EntityType.gcsv5_collections(),
src_commands=("globus endpoint update",),
dst_command="globus gcs collection update",
),
# delete [gcsv4 endpoints (host/share)]
ShouldUse(
if_types=EntityType.traditional_endpoints(),
src_commands=("globus collection delete", "globus gcs collection delete"),
dst_command="globus endpoint delete",
),
# delete [gcsv5 collections]
ShouldUse(
if_types=EntityType.gcsv5_collections(),
src_commands=("globus endpoint delete",),
dst_command="globus gcs collection delete",
),
# show [gcsv4 endpoints (host/share) + gcp + gcsv5 endpoint]
ShouldUse(
if_types=EntityType.non_gcsv5_collection_types(),
src_commands=("globus collection show", "globus gcs collection show"),
dst_command="globus endpoint show",
),
# show [gcsv5 collections]
ShouldUse(
if_types=EntityType.gcsv5_collections(),
src_commands=("globus endpoint show",),
dst_command="globus gcs collection show",
),
)


class WrongEntityTypeError(ValueError):
Expand Down Expand Up @@ -53,10 +93,12 @@ def _get_actual_message(self) -> str:
return f"Instead, found it was of type '{actual_str}'."

def should_use_command(self) -> str | None:
if self.from_command in SHOULD_USE_MAP:
for should_use, if_types in SHOULD_USE_MAP[self.from_command]:
if self.actual_type in if_types:
return should_use
for should_use_data in SHOULD_USE_MAPPINGS:
if (
self.from_command in should_use_data.src_commands
and self.actual_type in should_use_data.if_types
):
return should_use_data.dst_command
return None


Expand Down
27 changes: 17 additions & 10 deletions tests/functional/collection/test_collection_delete.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,49 @@
import pytest
from globus_sdk._testing import load_response_set


def test_guest_collection_delete(run_line, add_gcs_login):
# toggle between the (newer) 'gcs' variant and the 'bare' variant
@pytest.fixture(params=("gcs collection", "collection"))
def base_command(request):
return f"globus {request.param} delete"


def test_guest_collection_delete(run_line, add_gcs_login, base_command):
meta = load_response_set("cli.collection_operations").metadata
epid = meta["endpoint_id"]
cid = meta["guest_collection_id"]
add_gcs_login(epid)

result = run_line(f"globus collection delete {cid}")
result = run_line(f"{base_command} {cid}")
assert "success" in result.output


def test_mapped_collection_delete(run_line, add_gcs_login):
def test_mapped_collection_delete(run_line, add_gcs_login, base_command):
meta = load_response_set("cli.collection_operations").metadata
epid = meta["endpoint_id"]
cid = meta["mapped_collection_id"]
add_gcs_login(epid)

result = run_line(f"globus collection delete {cid}")
result = run_line(f"{base_command} {cid}")
assert "success" in result.output


def test_collection_delete_missing_login(run_line):
def test_collection_delete_missing_login(run_line, base_command):
meta = load_response_set("cli.collection_operations").metadata
epid = meta["endpoint_id"]
cid = meta["guest_collection_id"]

result = run_line(f"globus collection delete {cid}", assert_exit_code=4)
result = run_line(f"{base_command} {cid}", assert_exit_code=4)
assert "success" not in result.output
assert f"Missing login for {epid}" in result.stderr
assert f" globus login --gcs {epid}" in result.stderr


def test_collection_delete_on_gcsv5_host(run_line):
def test_collection_delete_on_gcsv5_host(run_line, base_command):
meta = load_response_set("cli.collection_operations").metadata
epid = meta["endpoint_id"]

result = run_line(f"globus collection delete {epid}", assert_exit_code=3)
result = run_line(f"{base_command} {epid}", assert_exit_code=3)
assert "success" not in result.output
assert (
f"Expected {epid} to be a collection ID.\n"
Expand All @@ -45,11 +52,11 @@ def test_collection_delete_on_gcsv5_host(run_line):
assert "This operation is not supported on objects of this type." in result.stderr


def test_collection_delete_on_gcp(run_line):
def test_collection_delete_on_gcp(run_line, base_command):
meta = load_response_set("cli.collection_operations").metadata
epid = meta["gcp_endpoint_id"]

result = run_line(f"globus collection delete {epid}", assert_exit_code=3)
result = run_line(f"{base_command} {epid}", assert_exit_code=3)
assert "success" not in result.output
assert (
f"Expected {epid} to be a collection ID.\n"
Expand Down
31 changes: 20 additions & 11 deletions tests/functional/collection/test_collection_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
from globus_sdk._testing import load_response_set


# toggle between the (newer) 'gcs' variant and the 'bare' variant
@pytest.fixture(params=("gcs collection", "collection"))
def base_command(request):
return f"globus {request.param} list"


def get_last_gcs_call(gcs_addr):
try:
return next(
Expand All @@ -14,22 +20,22 @@ def get_last_gcs_call(gcs_addr):
return None


def test_collection_list(run_line, add_gcs_login):
def test_collection_list(run_line, add_gcs_login, base_command):
meta = load_response_set("cli.collection_operations").metadata
epid = meta["endpoint_id"]
add_gcs_login(epid)
result = run_line(f"globus collection list {epid}")
result = run_line(f"{base_command} {epid}")
collection_names = ["Happy Fun Collection Name 1", "Happy Fun Collection Name 2"]
for name in collection_names:
assert name in result.stdout


def test_collection_list_opts(run_line, add_gcs_login):
def test_collection_list_opts(run_line, add_gcs_login, base_command):
meta = load_response_set("cli.collection_operations").metadata
epid = meta["endpoint_id"]
add_gcs_login(epid)
cid = meta["mapped_collection_id"]
run_line(f"globus collection list --mapped-collection-id {cid} {epid}")
run_line(f"{base_command} --mapped-collection-id {cid} {epid}")

gcs_addr = meta["gcs_hostname"]
last_call = get_last_gcs_call(gcs_addr)
Expand All @@ -40,11 +46,11 @@ def test_collection_list_opts(run_line, add_gcs_login):
assert last_call.request.params["include"] == "private_policies"


def test_collection_list_on_gcp(run_line):
def test_collection_list_on_gcp(run_line, base_command):
meta = load_response_set("cli.collection_operations").metadata
epid = meta["gcp_endpoint_id"]

result = run_line(f"globus collection list {epid}", assert_exit_code=3)
result = run_line(f"{base_command} {epid}", assert_exit_code=3)
assert "success" not in result.output
assert (
f"Expected {epid} to be a Globus Connect Server v5 Endpoint.\n"
Expand All @@ -53,11 +59,11 @@ def test_collection_list_on_gcp(run_line):
assert "This operation is not supported on objects of this type." in result.stderr


def test_collection_list_on_mapped_collection(run_line):
def test_collection_list_on_mapped_collection(run_line, base_command):
meta = load_response_set("cli.collection_operations").metadata
epid = meta["mapped_collection_id"]

result = run_line(f"globus collection list {epid}", assert_exit_code=3)
result = run_line(f"{base_command} {epid}", assert_exit_code=3)
assert "success" not in result.output
assert (
f"Expected {epid} to be a Globus Connect Server v5 Endpoint.\n"
Expand All @@ -78,14 +84,17 @@ def test_collection_list_on_mapped_collection(run_line):
["mapped-collections", "managed-by_me", "created-by-me"],
],
)
def test_collection_list_filters(run_line, add_gcs_login, filter_val):
def test_collection_list_filters(run_line, add_gcs_login, filter_val, base_command):
meta = load_response_set("cli.collection_operations").metadata
epid = meta["endpoint_id"]
add_gcs_login(epid)
if not isinstance(filter_val, list):
filter_val = [filter_val]
filter_str = " ".join(f"--filter {f}" for f in filter_val)
run_line(f"globus collection list {filter_str} {epid}")
filters = []
for f in filter_val:
filters.extend(["--filter", f])

run_line(base_command.split() + filters + [epid])
filter_params = {v.lower().replace("-", "_") for v in filter_val}

gcs_addr = meta["gcs_hostname"]
Expand Down
18 changes: 12 additions & 6 deletions tests/functional/collection/test_collection_show.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@
from globus_sdk._testing import load_response_set


def test_collection_show(run_line, add_gcs_login):
# toggle between the (newer) 'gcs' variant and the 'bare' variant
@pytest.fixture(params=("gcs collection", "collection"))
def base_command(request):
return f"globus {request.param} show"


def test_collection_show(run_line, add_gcs_login, base_command):
meta = load_response_set("cli.collection_operations").metadata
cid = meta["mapped_collection_id"]
username = meta["username"]
epid = meta["endpoint_id"]
add_gcs_login(epid)

run_line(
f"globus collection show {cid}",
f"{base_command} {cid}",
search_stdout=[
("Display Name", "Happy Fun Collection Name"),
("Owner", username),
Expand All @@ -21,15 +27,15 @@ def test_collection_show(run_line, add_gcs_login):
)


def test_collection_show_private_policies(run_line, add_gcs_login):
def test_collection_show_private_policies(run_line, add_gcs_login, base_command):
meta = load_response_set("cli.collection_show_private_policies").metadata
cid = meta["collection_id"]
username = meta["username"]
epid = meta["endpoint_id"]
add_gcs_login(epid)

run_line(
f"globus collection show --include-private-policies {cid}",
f"{base_command} --include-private-policies {cid}",
search_stdout=[
("Display Name", "Happy Fun Collection Name"),
("Owner", username),
Expand All @@ -52,11 +58,11 @@ def test_collection_show_private_policies(run_line, add_gcs_login):
("endpoint_id", "Globus Connect Server v5 Endpoint"),
],
)
def test_collection_show_on_non_collection(run_line, epid_key, ep_type):
def test_collection_show_on_non_collection(run_line, base_command, epid_key, ep_type):
meta = load_response_set("cli.collection_operations").metadata
epid = meta[epid_key]

result = run_line(f"globus collection show {epid}", assert_exit_code=3)
result = run_line(f"{base_command} {epid}", assert_exit_code=3)
assert (
f"Expected {epid} to be a collection ID.\n"
f"Instead, found it was of type '{ep_type}'."
Expand Down
Loading

0 comments on commit 7db9486

Please sign in to comment.