Skip to content

Commit 88bfbb2

Browse files
committed
🐙 octavia-cli: add command to list existing sources, destinations and connections
1 parent 0dfbfdc commit 88bfbb2

9 files changed

+444
-281
lines changed

octavia-cli/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ We welcome community contributions!
3838

3939
| Date | Milestone |
4040
|------------|-------------------------------------|
41+
| 2022-01-19 | Implement `octavia list workspace sources`, `octavia list workspace destinations`, `octavia list workspace connections`|
4142
| 2022-01-17 | Implement `octavia list connectors source` and `octavia list connectors destinations`|
4243
| 2022-01-17 | Generate an API Python client from our Open API spec |
4344
| 2021-12-22 | Bootstrapping the project's code base |

octavia-cli/octavia_cli/list/commands.py

+39-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import click
88

9-
from .connectors_definitions import DestinationConnectorsDefinitions, SourceConnectorsDefinitions
9+
from .listings import Connections, DestinationConnectorsDefinitions, Destinations, SourceConnectorsDefinitions, Sources
1010

1111

1212
@click.group("list", help="List existing Airbyte resources.")
@@ -21,23 +21,56 @@ def connectors(ctx: click.Context): # pragma: no cover
2121
pass
2222

2323

24-
@connectors.command(help="Latest information on supported sources.")
24+
@click.group("workspace", help="Latest information on workspace's sources and destinations.")
2525
@click.pass_context
26-
def sources(ctx: click.Context):
26+
def workspace(ctx: click.Context): # pragma: no cover
27+
pass
28+
29+
30+
@connectors.command(name="sources", help="Latest information on supported sources.")
31+
@click.pass_context
32+
def sources_connectors(ctx: click.Context):
2733
api_client = ctx.obj["API_CLIENT"]
2834
definitions = SourceConnectorsDefinitions(api_client)
2935
click.echo(definitions)
3036

3137

32-
@connectors.command(help="Latest information on supported destinations.")
38+
@connectors.command(name="destination", help="Latest information on supported destinations.")
3339
@click.pass_context
34-
def destinations(ctx: click.Context):
40+
def destinations_connectors(ctx: click.Context):
3541
api_client = ctx.obj["API_CLIENT"]
3642
definitions = DestinationConnectorsDefinitions(api_client)
3743
click.echo(definitions)
3844

3945

40-
AVAILABLE_COMMANDS: List[click.Command] = [connectors]
46+
@workspace.command(help="List existing sources in a workspace.")
47+
@click.pass_context
48+
def sources(ctx: click.Context):
49+
api_client = ctx.obj["API_CLIENT"]
50+
workspace_id = ctx.obj["WORKSPACE_ID"]
51+
sources = Sources(api_client, workspace_id)
52+
click.echo(sources)
53+
54+
55+
@workspace.command(help="List existing destinations in a workspace.")
56+
@click.pass_context
57+
def destinations(ctx: click.Context):
58+
api_client = ctx.obj["API_CLIENT"]
59+
workspace_id = ctx.obj["WORKSPACE_ID"]
60+
destinations = Destinations(api_client, workspace_id)
61+
click.echo(destinations)
62+
63+
64+
@workspace.command(help="List existing connections in a workspace.")
65+
@click.pass_context
66+
def connections(ctx: click.Context):
67+
api_client = ctx.obj["API_CLIENT"]
68+
workspace_id = ctx.obj["WORKSPACE_ID"]
69+
connections = Connections(api_client, workspace_id)
70+
click.echo(connections)
71+
72+
73+
AVAILABLE_COMMANDS: List[click.Command] = [connectors, workspace]
4174

4275

4376
def add_commands_to_list():

octavia-cli/octavia_cli/list/connectors_definitions.py

-121
This file was deleted.
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#
2+
# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
3+
#
4+
5+
from typing import List
6+
7+
8+
def compute_columns_width(data: List[List[str]], padding: int = 2) -> List[int]:
9+
"""Compute columns width for display purposes:
10+
Find size for each columns in the data and add padding.
11+
Args:
12+
data (List[List[str]]): Tabular data containing rows and columns.
13+
padding (int): Number of character to adds to create space between columns.
14+
Returns:
15+
columns_width (List[int]): The computed columns widths for each column according to input data.
16+
"""
17+
columns_width = [0 for _ in data[0]]
18+
for row in data:
19+
for i, col in enumerate(row):
20+
current_col_width = len(col) + padding
21+
if current_col_width > columns_width[i]:
22+
columns_width[i] = current_col_width
23+
return columns_width
24+
25+
26+
def camelcased_to_uppercased_spaced(camelcased: str) -> str:
27+
"""Util function to transform a camelCase string to a UPPERCASED SPACED string
28+
e.g: dockerImageName -> DOCKER IMAGE NAME
29+
Args:
30+
camelcased (str): The camel cased string to convert.
31+
32+
Returns:
33+
(str): The converted UPPERCASED SPACED string
34+
"""
35+
return "".join(map(lambda x: x if x.islower() else " " + x, camelcased)).upper()
36+
37+
38+
def display_as_table(data: List[List[str]]) -> str:
39+
"""Formats tabular input data into a displayable table with columns.
40+
Args:
41+
data (List[List[str]]): Tabular data containing rows and columns.
42+
Returns:
43+
table (str): String representation of input tabular data.
44+
"""
45+
columns_width = compute_columns_width(data)
46+
table = "\n".join(["".join(col.ljust(columns_width[i]) for i, col in enumerate(row)) for row in data])
47+
return table
48+
49+
50+
def format_column_names(camelcased_column_names: List[str]) -> List[str]:
51+
"""Format camel cased column names to uppercased spaced column names
52+
53+
Args:
54+
camelcased_column_names (List[str]): Column names in camel case.
55+
56+
Returns:
57+
(List[str]): Column names in uppercase with spaces.
58+
"""
59+
return [camelcased_to_uppercased_spaced(column_name) for column_name in camelcased_column_names]
+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#
2+
# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
3+
#
4+
5+
import abc
6+
from typing import List
7+
8+
import airbyte_api_client
9+
import octavia_cli.list.formatting as formatting
10+
from airbyte_api_client.api import connection_api, destination_api, destination_definition_api, source_api, source_definition_api
11+
from airbyte_api_client.model.workspace_id_request_body import WorkspaceIdRequestBody
12+
13+
14+
class BaseListing(abc.ABC):
15+
COMMON_LIST_FUNCTION_KWARGS = {"_check_return_type": False}
16+
17+
@property
18+
@abc.abstractmethod
19+
def api(
20+
self,
21+
): # pragma: no cover
22+
pass
23+
24+
@property
25+
@abc.abstractmethod
26+
def fields_to_display(
27+
self,
28+
) -> List[str]: # pragma: no cover
29+
pass
30+
31+
@property
32+
@abc.abstractmethod
33+
def list_field_in_response(
34+
self,
35+
) -> str: # pragma: no cover
36+
pass
37+
38+
@property
39+
@abc.abstractmethod
40+
def list_function_name(
41+
self,
42+
) -> str: # pragma: no cover
43+
pass
44+
45+
@property
46+
def _list_fn(self):
47+
return getattr(self.api, self.list_function_name)
48+
49+
@property
50+
def list_function_kwargs(self) -> dict:
51+
return {}
52+
53+
def __init__(self, api_client: airbyte_api_client.ApiClient):
54+
self.api_instance = self.api(api_client)
55+
56+
def _parse_response(self, api_response) -> List[List[str]]:
57+
items = [[item[field] for field in self.fields_to_display] for item in api_response[self.list_field_in_response]]
58+
return items
59+
60+
def get_listing(self) -> List[List[str]]:
61+
api_response = self._list_fn(self.api_instance, **self.list_function_kwargs, **self.COMMON_LIST_FUNCTION_KWARGS)
62+
return self._parse_response(api_response)
63+
64+
def __repr__(self):
65+
items = [formatting.format_column_names(self.fields_to_display)] + self.get_listing()
66+
return formatting.display_as_table(items)
67+
68+
69+
class SourceConnectorsDefinitions(BaseListing):
70+
api = source_definition_api.SourceDefinitionApi
71+
fields_to_display = ["name", "dockerRepository", "dockerImageTag", "sourceDefinitionId"]
72+
list_field_in_response = "source_definitions"
73+
list_function_name = "list_latest_source_definitions"
74+
75+
76+
class DestinationConnectorsDefinitions(BaseListing):
77+
api = destination_definition_api.DestinationDefinitionApi
78+
fields_to_display = ["name", "dockerRepository", "dockerImageTag", "destinationDefinitionId"]
79+
list_field_in_response = "destination_definitions"
80+
list_function_name = "list_latest_destination_definitions"
81+
82+
83+
class WorkspaceListing(BaseListing, abc.ABC):
84+
def __init__(self, api_client: airbyte_api_client.ApiClient, workspace_id: str):
85+
self.workspace_id = workspace_id
86+
super().__init__(api_client)
87+
88+
@property
89+
def list_function_kwargs(self) -> dict:
90+
return {"workspace_id_request_body": WorkspaceIdRequestBody(workspace_id=self.workspace_id)}
91+
92+
93+
class Sources(WorkspaceListing):
94+
api = source_api.SourceApi
95+
fields_to_display = ["name", "sourceName", "sourceId"]
96+
list_field_in_response = "sources"
97+
list_function_name = "list_sources_for_workspace"
98+
99+
100+
class Destinations(WorkspaceListing):
101+
api = destination_api.DestinationApi
102+
fields_to_display = ["name", "destinationName", "destinationId"]
103+
list_field_in_response = "destinations"
104+
list_function_name = "list_destinations_for_workspace"
105+
106+
107+
class Connections(WorkspaceListing):
108+
api = connection_api.ConnectionApi
109+
fields_to_display = ["name", "connectionId", "status", "sourceId", "destinationId"]
110+
list_field_in_response = "connections"
111+
list_function_name = "list_connections_for_workspace"

0 commit comments

Comments
 (0)