Skip to content
This repository has been archived by the owner on Nov 23, 2023. It is now read-only.

feat: Expand CLI #1142

Merged
merged 6 commits into from Nov 4, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Expand Up @@ -37,7 +37,7 @@ jobs:
run: |
python -m pip install --upgrade pip
python -m pip install poetry
python -m poetry install --extras=cli --no-dev --no-root
python -m poetry install --no-dev --no-root

- name: Build
run: poetry build
Expand Down
10 changes: 5 additions & 5 deletions USAGE.md
Expand Up @@ -119,6 +119,8 @@ Example of AWS service account authentication and authorization in to Geostore u

## Command line

To install the Geostore CLI, run `pip3 install geostore`.

The general synopsis is `geostore NOUN VERB [PARAMETER…]`. `NOUN` is the type of thing the command
is operating on, for example `version` when dealing with dataset versions. `VERB` is the action it
is telling the system to take, for example `list` to show a listing of the relevant objects, or
Expand Down Expand Up @@ -158,17 +160,15 @@ Examples:

```console
$ geostore dataset list
Dataset ID | Title | Description
Auckland_2020-01F9ZFRK12V0WFXJ94S0DHCP65 | Auckland_2020 | Aerial imagery from April 2020
Wellington_2020-01FJJDQJ2X0MPTPYPMM246DSH1 | Wellington_2020 | Aerial imagery from March 2020
Auckland_2020-01F9ZFRK12V0WFXJ94S0DHCP65
Wellington_2020-01FJJDQJ2X0MPTPYPMM246DSH1
```

- Filter to a single dataset:

```console
$ geostore dataset list --id=Auckland_2020-01F9ZFRK12V0WFXJ94S0DHCP65
Dataset ID | Title | Description
Auckland_2020-01F9ZFRK12V0WFXJ94S0DHCP65 | Auckland_2020 | Aerial imagery from April 2020
Auckland_2020-01F9ZFRK12V0WFXJ94S0DHCP65
```

#### Delete
Expand Down
140 changes: 119 additions & 21 deletions geostore/cli.py
Expand Up @@ -2,6 +2,7 @@
from enum import IntEnum
from http import HTTPStatus
from json import dumps, load
from typing import Callable, Optional, Union

import boto3
from botocore.exceptions import NoCredentialsError, NoRegionError
Expand All @@ -10,12 +11,28 @@

from .api_keys import MESSAGE_KEY
from .aws_keys import BODY_KEY, HTTP_METHOD_KEY, STATUS_CODE_KEY
from .dataset_keys import DATASET_KEY_SEPARATOR
from .resources import ResourceName
from .step_function_keys import DATASET_ID_SHORT_KEY, DESCRIPTION_KEY, TITLE_KEY
from .step_function_keys import (
DATASET_ID_SHORT_KEY,
DESCRIPTION_KEY,
EXECUTION_ARN_KEY,
METADATA_URL_KEY,
S3_ROLE_ARN_KEY,
TITLE_KEY,
VERSION_ID_KEY,
)
from .types import JsonList, JsonObject

HTTP_METHOD_CREATE = "POST"

app = Typer()
dataset_app = Typer()
dataset_version_app = Typer()
app.add_typer(dataset_app, name="dataset")
app.add_typer(dataset_version_app, name="version")

GetOutputFunctionType = Union[Callable[[JsonList], str], Callable[[JsonObject], str]]


class ExitCode(IntEnum):
Expand All @@ -29,6 +46,104 @@ class ExitCode(IntEnum):

@dataset_app.command(name="create")
def dataset_create(title: str = Option(...), description: str = Option(...)) -> None:
request_object = {
HTTP_METHOD_KEY: HTTP_METHOD_CREATE,
BODY_KEY: {TITLE_KEY: title, DESCRIPTION_KEY: description},
}

def get_output(response_body: JsonObject) -> str:
dataset_id: str = response_body[DATASET_ID_SHORT_KEY]
return dataset_id

handle_api_request(
ResourceName.DATASETS_ENDPOINT_FUNCTION_NAME.value, request_object, get_output
)


@dataset_app.command(name="list")
def dataset_list(id_: Optional[str] = Option(None, "--id")) -> None:
body = {}
get_output: GetOutputFunctionType

if id_ is None:

def get_list_output(response_body: JsonList) -> str:
lines = []
for entry in response_body:
lines.append(
f"{entry[TITLE_KEY]}{DATASET_KEY_SEPARATOR}{entry[DATASET_ID_SHORT_KEY]}"
)
return "\n".join(lines)

get_output = get_list_output

else:

def get_single_output(response_body: JsonObject) -> str:
return f"{response_body['title']}{DATASET_KEY_SEPARATOR}{response_body['id']}"

body[DATASET_ID_SHORT_KEY] = id_
get_output = get_single_output

handle_api_request(
ResourceName.DATASETS_ENDPOINT_FUNCTION_NAME.value,
{HTTP_METHOD_KEY: "GET", BODY_KEY: body},
get_output,
)


@dataset_app.command(name="delete")
def dataset_delete(id_: str = Option(..., "--id")) -> None:
handle_api_request(
ResourceName.DATASETS_ENDPOINT_FUNCTION_NAME.value,
{HTTP_METHOD_KEY: "DELETE", BODY_KEY: {DATASET_ID_SHORT_KEY: id_}},
None,
)


@dataset_version_app.command(name="create")
def dataset_version_create(
dataset_id: str = Option(...), metadata_url: str = Option(...), s3_role_arn: str = Option(...)
) -> None:
def get_output(response_body: JsonObject) -> str:
return f"{response_body[VERSION_ID_KEY]}\t{response_body[EXECUTION_ARN_KEY]}"

handle_api_request(
ResourceName.DATASET_VERSIONS_ENDPOINT_FUNCTION_NAME.value,
{
HTTP_METHOD_KEY: HTTP_METHOD_CREATE,
BODY_KEY: {
DATASET_ID_SHORT_KEY: dataset_id,
METADATA_URL_KEY: metadata_url,
S3_ROLE_ARN_KEY: s3_role_arn,
},
},
get_output,
)


def handle_api_request(
function_name: str, request_object: JsonObject, get_output: Optional[GetOutputFunctionType]
) -> None:
response_payload = invoke_lambda(function_name, request_object)
status_code = response_payload[STATUS_CODE_KEY]
response_body = response_payload[BODY_KEY]

if status_code in [HTTPStatus.OK, HTTPStatus.CREATED, HTTPStatus.NO_CONTENT]:
if get_output is not None:
output = get_output(response_body)
secho(output, fg=GREEN)
sys.exit(ExitCode.SUCCESS)

if status_code == HTTPStatus.CONFLICT:
secho(response_body[MESSAGE_KEY], err=True, fg=YELLOW)
sys.exit(ExitCode.CONFLICT)

secho(dumps(response_body), err=True, fg=RED)
sys.exit(ExitCode.UNKNOWN)


def invoke_lambda(function_name: str, request_object: JsonObject) -> JsonObject:
try:
client = boto3.client("lambda")
except NoRegionError:
Expand All @@ -39,35 +154,18 @@ def dataset_create(title: str = Option(...), description: str = Option(...)) ->
)
sys.exit(ExitCode.NO_REGION_SETTING)

request_object = {
HTTP_METHOD_KEY: "POST",
BODY_KEY: {TITLE_KEY: title, DESCRIPTION_KEY: description},
}
request_payload = dumps(request_object).encode()

try:
response = client.invoke(
FunctionName=ResourceName.DATASETS_ENDPOINT_FUNCTION_NAME.value, Payload=request_payload
)
response = client.invoke(FunctionName=function_name, Payload=request_payload)
except NoCredentialsError:
secho(
"Unable to locate credentials. Make sure to log in to AWS first.", err=True, fg=YELLOW
)
sys.exit(ExitCode.NO_CREDENTIALS)

response_payload = load(response["Payload"])
exit_code = {HTTPStatus.CREATED: ExitCode.SUCCESS, HTTPStatus.CONFLICT: ExitCode.CONFLICT}.get(
response_payload[STATUS_CODE_KEY], ExitCode.UNKNOWN
)
color = {ExitCode.SUCCESS: GREEN, ExitCode.UNKNOWN: RED}.get(exit_code, YELLOW)
response_body = response_payload[BODY_KEY]
output = response_body.get(
DATASET_ID_SHORT_KEY, response_body.get(MESSAGE_KEY, dumps(response_body))
)

secho(output, err=exit_code != ExitCode.SUCCESS, fg=color)

sys.exit(exit_code)
response_payload: JsonObject = load(response["Payload"])
return response_payload


if __name__ == "__main__":
Expand Down
35 changes: 17 additions & 18 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.