Skip to content
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
4 changes: 2 additions & 2 deletions ftf_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from ftf_cli.commands.add_input import add_input
from ftf_cli.commands.delete_module import delete_module
from ftf_cli.commands.get_output_types import get_output_types
from ftf_cli.commands.get_output_details import get_output_lookup_tree
from ftf_cli.commands.get_output_type_details import get_output_type_details
from ftf_cli.commands.validate_facets import validate_facets
from ftf_cli.commands.register_output_type import register_output_type
from ftf_cli.commands.add_import import add_import
Expand All @@ -28,7 +28,7 @@ def cli():
cli.add_command(expose_provider)
cli.add_command(generate_module)
cli.add_command(get_output_types)
cli.add_command(get_output_lookup_tree)
cli.add_command(get_output_type_details)
cli.add_command(login)
cli.add_command(preview_module)
cli.add_command(register_output_type)
Expand Down
4 changes: 2 additions & 2 deletions ftf_cli/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from .login import login
from .preview_module import preview_module
from .get_output_types import get_output_types
from .get_output_details import get_output_lookup_tree
from .get_output_type_details import get_output_type_details
from .register_output_type import register_output_type
from .add_import import add_import
from .get_resources import get_resources
Expand All @@ -23,7 +23,7 @@
"generate_module",
"get_output_types",
"register_output_type",
"get_output_lookup_tree",
"get_output_type_details",
"login",
"preview_module",
"validate_directory",
Expand Down
49 changes: 31 additions & 18 deletions ftf_cli/commands/add_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
transform_properties_to_terraform,
ensure_formatting_for_object,
get_profile_with_priority,
parse_namespace_and_name,
)


Expand Down Expand Up @@ -51,7 +52,7 @@
"--output-type",
prompt="Output Type",
type=str,
help="The type of registered output to be added as input for terraform module.",
help="The type of registered output to be added as input for terraform module. Format: @namespace/name (e.g., @outputs/vpc, @custom/sqs)",
)
def add_input(path, profile, name, display_name, description, output_type):
"""Add an existing registered output as a input in facets.yaml and populate the attributes in variables.tf exposed by selected output."""
Expand All @@ -76,13 +77,13 @@ def add_input(path, profile, name, display_name, description, output_type):
required_inputs = facets_data.get("inputs", {})
required_inputs_map = {}

pattern = r"@outputs/(.*)"
pattern = r"(@[^/]+)/(.*)"
for key, value in required_inputs.items():
required_input = value.get("type", "")
if required_input and required_input != "":
match = re.search(pattern, required_input)
if match:
required_inputs_map[key] = match.group(1)
required_inputs_map[key] = required_input # Store full @namespace/name

if name in required_inputs_map:
click.echo(
Expand All @@ -95,7 +96,7 @@ def add_input(path, profile, name, display_name, description, output_type):
required_inputs.update(
{
name: {
"type": f"@outputs/{output_type}",
"type": output_type,
"displayName": display_name,
"description": description,
}
Expand All @@ -105,6 +106,9 @@ def add_input(path, profile, name, display_name, description, output_type):
# update the facets yaml with the new input
facets_data.update({"inputs": required_inputs})

# Validate output_type format
parse_namespace_and_name(output_type)

# check if profile is set
click.echo(f"Profile selected: {profile}")
credentials = is_logged_in(profile)
Expand All @@ -122,46 +126,55 @@ def add_input(path, profile, name, display_name, description, output_type):
f"{control_plane_url}/cc-ui/v1/tf-outputs", auth=(username, token)
)

registered_outputs = {output["name"]: output for output in response.json()}
registered_output_names = list(registered_outputs.keys())
registered_outputs = {(output["namespace"], output["name"]): output for output in response.json()}
available_output_types = [f'{namespace}/{name}' for namespace, name in registered_outputs.keys()]

# make sure all outputs are registered
for output in required_inputs_map.values():
if output not in registered_output_names:
for output_type_value in required_inputs_map.values():
namespace, name = parse_namespace_and_name(output_type_value)
if (namespace, name) not in registered_outputs:
raise click.UsageError(
f"❌ {output} not found in registered outputs. Please select a valid output type from {registered_output_names}."
f"❌ {output_type_value} not found in registered outputs. Please select a valid output type from {available_output_types}."
)

# get properties for each output and transform them
output_schemas = {}
for output_name, output in required_inputs_map.items():
properties = registered_outputs[output].get("properties")
for output_name, output_type_value in required_inputs_map.items():
namespace, name = parse_namespace_and_name(output_type_value)
output_data = registered_outputs[(namespace, name)]
properties = output_data.get("properties")

if properties:
try:
# Assume properties has the expected structure with attributes and interfaces
if (properties.get("type") == "object" and
# Try direct structure first: properties.{attributes, interfaces}
if "attributes" in properties and "interfaces" in properties:
attributes_schema = properties["attributes"]
interfaces_schema = properties["interfaces"]
output_schemas[output_name] = {
"attributes": attributes_schema,
"interfaces": interfaces_schema
}
# Try nested structure: properties.properties.{attributes, interfaces}
elif (properties.get("type") == "object" and
"properties" in properties and
"attributes" in properties["properties"] and
"interfaces" in properties["properties"]):

attributes_schema = properties["properties"]["attributes"]
interfaces_schema = properties["properties"]["interfaces"]

output_schemas[output_name] = {
"attributes": attributes_schema,
"interfaces": interfaces_schema
}
else:
click.echo(
f"⚠️ Output {output} does not have expected structure (attributes/interfaces). Using default empty structure.")
f"⚠️ Output {output_type_value} does not have expected structure (attributes/interfaces). Using default empty structure.")
output_schemas[output_name] = {"attributes": {}, "interfaces": {}}

except Exception as e:
click.echo(f"⚠️ Error parsing properties for output {output}: {e}. Using default empty structure.")
click.echo(f"⚠️ Error parsing properties for output {output_type_value}: {e}. Using default empty structure.")
output_schemas[output_name] = {"attributes": {}, "interfaces": {}}
else:
click.echo(f"⚠️ Output {output} has no properties defined. Using default empty structure.")
click.echo(f"⚠️ Output {output_type_value} has no properties defined. Using default empty structure.")
output_schemas[output_name] = {"attributes": {}, "interfaces": {}}

inputs_var = generate_inputs_variable(output_schemas)
Expand Down
73 changes: 0 additions & 73 deletions ftf_cli/commands/get_output_details.py

This file was deleted.

99 changes: 99 additions & 0 deletions ftf_cli/commands/get_output_type_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import json
import traceback
import click
import requests

from ftf_cli.utils import is_logged_in, get_profile_with_priority, parse_namespace_and_name


@click.command()
@click.option(
"-p",
"--profile",
default=get_profile_with_priority(),
help="The profile name to use (defaults to the current default profile)",
)
@click.option(
"-o",
"--output-type",
prompt="Output type to get details for",
type=str,
help="The output type to get details for. Format: @namespace/name (e.g., @outputs/vpc, @custom/sqs)",
)
def get_output_type_details(profile, output_type):
"""Get the details of a registered output type from the control plane"""
try:
# Validate output_type format
namespace, name = parse_namespace_and_name(output_type)

# Check if profile is set
click.echo(f"Profile selected: {profile}")
credentials = is_logged_in(profile)
if not credentials:
raise click.UsageError(
f"❌ Not logged in under profile {profile}. Please login first."
)

# Extract credentials
control_plane_url = credentials["control_plane_url"]
username = credentials["username"]
token = credentials["token"]

# Make a request to fetch output types
response = requests.get(
f"{control_plane_url}/cc-ui/v1/tf-outputs", auth=(username, token)
)

if response.status_code == 200:
# Create lookup by (namespace, name) tuple
registered_outputs = {(output["namespace"], output["name"]): output for output in response.json()}

required_output = registered_outputs.get((namespace, name))

if not required_output:
available_outputs = [f'{ns}/{nm}' for ns, nm in registered_outputs.keys()]
raise click.UsageError(
f"❌ Output type {output_type} not found. Available outputs: {available_outputs}"
)

click.echo(f"=== Output Type Details: {output_type} ===\n")

# Display basic information
click.echo(f"Name: {required_output['name']}")
click.echo(f"Namespace: {required_output['namespace']}")
if 'source' in required_output:
click.echo(f"Source: {required_output['source']}")
if 'inferredFromModule' in required_output:
click.echo(f"Inferred from Module: {required_output['inferredFromModule']}")

# Display properties if present
if "properties" in required_output and required_output["properties"]:
click.echo("\n--- Properties ---")
properties = required_output["properties"]
click.echo(json.dumps(properties, indent=2, sort_keys=True))
else:
click.echo("\n--- Properties ---")
click.echo("No properties defined.")

# Display lookup tree if present
if "lookupTree" in required_output and required_output["lookupTree"]:
click.echo("\n--- Lookup Tree ---")
try:
lookup_tree = json.loads(required_output["lookupTree"])
click.echo(json.dumps(lookup_tree, indent=2, sort_keys=True))
except json.JSONDecodeError:
click.echo("Invalid JSON in lookup tree.")
else:
click.echo("\n--- Lookup Tree ---")
lookup_tree = {"out": {"attributes": {}, "interfaces": {}}}
click.echo(json.dumps(lookup_tree, indent=2, sort_keys=True))

else:
raise click.UsageError(
f"❌ Failed to fetch output types. Status code: {response.status_code}"
)
except Exception as e:
traceback.print_exc()
raise click.UsageError(
f"❌ An error occurred while getting output details: {e}"
)
6 changes: 4 additions & 2 deletions ftf_cli/commands/get_output_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from ftf_cli.utils import is_logged_in, get_profile_with_priority


@click.command() # Add this decorator to register the function as a Click command
@click.command()
@click.option(
"-p",
"--profile",
Expand Down Expand Up @@ -36,7 +36,9 @@ def get_output_types(profile):
if response.status_code == 200:
registered_output_types = []
for output_type in response.json():
registered_output_types.append(output_type["name"])
namespace = output_type.get("namespace", "@outputs") # Default fallback
name = output_type["name"]
registered_output_types.append(f"{namespace}/{name}")
registered_output_types.sort()
if len(registered_output_types) == 0:
click.echo("No output types registered.")
Expand Down
Loading