181 lines
8.1 KiB
Python
181 lines
8.1 KiB
Python
#
|
|
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
#
|
|
|
|
import json
|
|
from typing import List, Type, Union
|
|
|
|
import airbyte_api_client
|
|
import click
|
|
from octavia_cli.apply import resources
|
|
from octavia_cli.base_commands import OctaviaCommand
|
|
from octavia_cli.check_context import requires_init
|
|
from octavia_cli.generate import definitions, renderers
|
|
from octavia_cli.get.commands import get_json_representation
|
|
from octavia_cli.get.resources import Connection as UnmanagedConnection
|
|
from octavia_cli.get.resources import Destination as UnmanagedDestination
|
|
from octavia_cli.get.resources import Source as UnmanagedSource
|
|
from octavia_cli.list.listings import Connections as UnmanagedConnections
|
|
from octavia_cli.list.listings import Destinations as UnmanagedDestinations
|
|
from octavia_cli.list.listings import Sources as UnmanagedSources
|
|
|
|
|
|
class MissingResourceDependencyError(click.UsageError):
|
|
pass
|
|
|
|
|
|
def build_help_message(resource_type: str) -> str:
|
|
"""Helper function to build help message consistently for all the commands in this module.
|
|
Args:
|
|
resource_type (str): source, destination or connection
|
|
Returns:
|
|
str: The generated help message.
|
|
"""
|
|
return f"Import an existing {resource_type} to manage it with octavia-cli."
|
|
|
|
|
|
def import_source_or_destination(
|
|
api_client: airbyte_api_client.ApiClient,
|
|
workspace_id: str,
|
|
ResourceClass: Type[Union[UnmanagedSource, UnmanagedDestination]],
|
|
resource_to_get: str,
|
|
) -> str:
|
|
"""Helper function to import sources & destinations.
|
|
|
|
Args:
|
|
api_client (airbyte_api_client.ApiClient): the Airbyte API client.
|
|
workspace_id (str): current Airbyte workspace id.
|
|
ResourceClass (Union[UnmanagedSource, UnmanagedDestination]): the Airbyte Resource Class.
|
|
resource_to_get (str): the name or ID of the resource in the current Airbyte workspace id.
|
|
|
|
Returns:
|
|
str: The generated import message.
|
|
"""
|
|
remote_configuration = json.loads(get_json_representation(api_client, workspace_id, ResourceClass, resource_to_get))
|
|
|
|
resource_type = ResourceClass.__name__.lower()
|
|
|
|
definition = definitions.factory(resource_type, api_client, workspace_id, remote_configuration[f"{resource_type}_definition_id"])
|
|
|
|
renderer = renderers.ConnectorSpecificationRenderer(remote_configuration["name"], definition)
|
|
|
|
new_configuration_path = renderer.import_configuration(project_path=".", configuration=remote_configuration["connection_configuration"])
|
|
managed_resource, state = resources.factory(api_client, workspace_id, new_configuration_path).manage(
|
|
remote_configuration[f"{resource_type}_id"]
|
|
)
|
|
message = f"✅ - Imported {resource_type} {managed_resource.name} in {new_configuration_path}. State stored in {state.path}"
|
|
click.echo(click.style(message, fg="green"))
|
|
message = f"⚠️ - Please update any secrets stored in {new_configuration_path}"
|
|
click.echo(click.style(message, fg="yellow"))
|
|
|
|
|
|
def import_connection(
|
|
api_client: airbyte_api_client.ApiClient,
|
|
workspace_id: str,
|
|
resource_to_get: str,
|
|
) -> str:
|
|
"""Helper function to import connection.
|
|
|
|
Args:
|
|
api_client (airbyte_api_client.ApiClient): the Airbyte API client.
|
|
workspace_id (str): current Airbyte workspace id.
|
|
resource_to_get (str): the name or ID of the resource in the current Airbyte workspace id.
|
|
|
|
Returns:
|
|
str: The generated import message.
|
|
"""
|
|
remote_configuration = json.loads(get_json_representation(api_client, workspace_id, UnmanagedConnection, resource_to_get))
|
|
# Since #15253 "schedule" is deprecated
|
|
remote_configuration.pop("schedule", None)
|
|
source_name, destination_name = remote_configuration["source"]["name"], remote_configuration["destination"]["name"]
|
|
source_configuration_path = renderers.ConnectorSpecificationRenderer.get_output_path(
|
|
project_path=".", definition_type="source", resource_name=source_name
|
|
)
|
|
|
|
destination_configuration_path = renderers.ConnectorSpecificationRenderer.get_output_path(
|
|
project_path=".", definition_type="destination", resource_name=destination_name
|
|
)
|
|
if not source_configuration_path.is_file():
|
|
raise MissingResourceDependencyError(
|
|
f"The source {source_name} is not managed by octavia-cli, please import and apply it before importing your connection."
|
|
)
|
|
elif not destination_configuration_path.is_file():
|
|
raise MissingResourceDependencyError(
|
|
f"The destination {destination_name} is not managed by octavia-cli, please import and apply it before importing your connection."
|
|
)
|
|
else:
|
|
source = resources.factory(api_client, workspace_id, source_configuration_path)
|
|
destination = resources.factory(api_client, workspace_id, destination_configuration_path)
|
|
if not source.was_created:
|
|
raise resources.NonExistingResourceError(
|
|
f"The source defined at {source_configuration_path} does not exists. Please run octavia apply before creating this connection."
|
|
)
|
|
if not destination.was_created:
|
|
raise resources.NonExistingResourceError(
|
|
f"The destination defined at {destination_configuration_path} does not exists. Please run octavia apply before creating this connection."
|
|
)
|
|
|
|
connection_name, connection_id = remote_configuration["name"], remote_configuration["connection_id"]
|
|
connection_renderer = renderers.ConnectionRenderer(connection_name, source, destination)
|
|
new_configuration_path = connection_renderer.import_configuration(".", remote_configuration)
|
|
managed_resource, state = resources.factory(api_client, workspace_id, new_configuration_path).manage(connection_id)
|
|
message = f"✅ - Imported connection {managed_resource.name} in {new_configuration_path}. State stored in {state.path}"
|
|
click.echo(click.style(message, fg="green"))
|
|
|
|
|
|
@click.group(
|
|
"import",
|
|
help=f'{build_help_message("source, destination or connection")}. ID or name can be used as argument. Example: \'octavia import source "My Pokemon source"\' or \'octavia import source cb5413b2-4159-46a2-910a-dc282a439d2d\'',
|
|
)
|
|
@click.pass_context
|
|
def _import(ctx: click.Context): # pragma: no cover
|
|
pass
|
|
|
|
|
|
@_import.command(cls=OctaviaCommand, name="source", help=build_help_message("source"))
|
|
@click.argument("resource", type=click.STRING)
|
|
@click.pass_context
|
|
@requires_init
|
|
def source(ctx: click.Context, resource: str):
|
|
click.echo(import_source_or_destination(ctx.obj["API_CLIENT"], ctx.obj["WORKSPACE_ID"], UnmanagedSource, resource))
|
|
|
|
|
|
@_import.command(cls=OctaviaCommand, name="destination", help=build_help_message("destination"))
|
|
@click.argument("resource", type=click.STRING)
|
|
@click.pass_context
|
|
@requires_init
|
|
def destination(ctx: click.Context, resource: str):
|
|
click.echo(import_source_or_destination(ctx.obj["API_CLIENT"], ctx.obj["WORKSPACE_ID"], UnmanagedDestination, resource))
|
|
|
|
|
|
@_import.command(cls=OctaviaCommand, name="connection", help=build_help_message("connection"))
|
|
@click.argument("resource", type=click.STRING)
|
|
@click.pass_context
|
|
@requires_init
|
|
def connection(ctx: click.Context, resource: str):
|
|
click.echo(import_connection(ctx.obj["API_CLIENT"], ctx.obj["WORKSPACE_ID"], resource))
|
|
|
|
|
|
@_import.command(cls=OctaviaCommand, name="all", help=build_help_message("all"))
|
|
@click.pass_context
|
|
@requires_init
|
|
def all(ctx: click.Context):
|
|
api_client, workspace_id = ctx.obj["API_CLIENT"], ctx.obj["WORKSPACE_ID"]
|
|
for _, _, resource_id in UnmanagedSources(api_client, workspace_id).get_listing():
|
|
import_source_or_destination(api_client, workspace_id, UnmanagedSource, resource_id)
|
|
for _, _, resource_id in UnmanagedDestinations(api_client, workspace_id).get_listing():
|
|
import_source_or_destination(api_client, workspace_id, UnmanagedDestination, resource_id)
|
|
for _, resource_id, _, _, _ in UnmanagedConnections(api_client, workspace_id).get_listing():
|
|
import_connection(api_client, workspace_id, resource_id)
|
|
|
|
|
|
AVAILABLE_COMMANDS: List[click.Command] = [source, destination, connection]
|
|
|
|
|
|
def add_commands_to_list():
|
|
for command in AVAILABLE_COMMANDS:
|
|
_import.add_command(command)
|
|
|
|
|
|
add_commands_to_list()
|