Skip to content

Commit

Permalink
Add spring cloud gateway cli (#8037)
Browse files Browse the repository at this point in the history
  • Loading branch information
Descatles authored Oct 16, 2024
1 parent 2871aca commit 8af0340
Show file tree
Hide file tree
Showing 9 changed files with 5,950 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/containerapp/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ upcoming
* 'az containerapp up': Support `--registry-identity`, `--system-assigned`, `--user-assigned`
* 'az containerapp containerapp create/up': `--registry-server` and `--source` use managed identity for image pull by default
* 'az containerapp containerapp create': `--registry-server` use managed identity for image pull by default. `--no-wait` will not take effect with system registry identity.
* 'az containerapp env java-component gateway-for-spring': Support create/update/show/delete Gateway for spring.

1.0.0b3
++++++
Expand Down
1 change: 1 addition & 0 deletions src/containerapp/azext_containerapp/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
JAVA_COMPONENT_EUREKA = "SpringCloudEureka"
JAVA_COMPONENT_NACOS = "Nacos"
JAVA_COMPONENT_ADMIN = "SpringBootAdmin"
JAVA_COMPONENT_GATEWAY = "SpringCloudGateway"

DOTNET_COMPONENT_RESOURCE_TYPE = "AspireDashboard"

Expand Down
77 changes: 77 additions & 0 deletions src/containerapp/azext_containerapp/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -1619,6 +1619,83 @@
--configuration PropertyName1=Value1 PropertyName2=Value2
"""

helps['containerapp env java-component gateway-for-spring'] = """
type: group
short-summary: Commands to manage the Gateway for Spring for the Container Apps environment.
"""

helps['containerapp env java-component gateway-for-spring create'] = """
type: command
short-summary: Command to create the Gateway for Spring.
examples:
- name: Create a Gateway for Spring with default configuration.
text: |
az containerapp env java-component gateway-for-spring create -g MyResourceGroup \\
-n MyJavaComponentName \\
--environment MyEnvironment \\
--route-yaml MyRouteYamlFilePath
- name: Create a Gateway for Spring with custom configurations.
text: |
az containerapp env java-component gateway-for-spring create -g MyResourceGroup \\
-n MyJavaComponentName \\
--environment MyEnvironment \\
--route-yaml MyRouteYamlFilePath \\
--configuration PropertyName1=Value1 PropertyName2=Value2
- name: Create a Gateway for Spring with multiple replicas.
text: |
az containerapp env java-component gateway-for-spring create -g MyResourceGroup \\
-n MyJavaComponentName \\
--environment MyEnvironment \\
--route-yaml MyRouteYamlFilePath \\
--min-replicas 2 --max-replicas 2
"""

helps['containerapp env java-component gateway-for-spring delete'] = """
type: command
short-summary: Command to delete the Gateway for Spring.
examples:
- name: Delete a Gateway for Spring.
text: |
az containerapp env java-component gateway-for-spring delete -g MyResourceGroup \\
-n MyJavaComponentName \\
--environment MyEnvironment
"""

helps['containerapp env java-component gateway-for-spring show'] = """
type: command
short-summary: Command to show the Gateway for Spring.
examples:
- name: Show Gateway for Spring.
text: |
az containerapp env java-component gateway-for-spring show -g MyResourceGroup \\
-n MyJavaComponentName \\
--environment MyEnvironment
"""

helps['containerapp env java-component gateway-for-spring update'] = """
type: command
short-summary: Command to update the Gateway for Spring.
examples:
- name: Update a Gateway for Spring with new routes.
text: |
az containerapp env java-component gateway-for-spring update -g MyResourceGroup \\
-n MyJavaComponentName \\
--environment MyEnvironment \\
--route-yaml MyRouteYamlFilePath
- name: Delete all configurations of the Gateway for Spring.
text: |
az containerapp env java-component gateway-for-spring update -g MyResourceGroup \\
-n MyJavaComponentName \\
--environment MyEnvironment \\
--configuration
- name: Update a Gateway for Spring with custom configurations.
text: |
az containerapp env java-component gateway-for-spring update -g MyResourceGroup \\
-n MyJavaComponentName \\
--environment MyEnvironment \\
--configuration PropertyName1=Value1 PropertyName2=Value2
"""

# Container Apps Telemetry Commands

helps['containerapp env telemetry'] = """
Expand Down
1 change: 1 addition & 0 deletions src/containerapp/azext_containerapp/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ def load_arguments(self, _):
c.argument('configuration', nargs="*", help="Java component configuration. Configuration must be in format \"<propertyName>=<value>\" \"<propertyName>=<value>\"...")
c.argument('min_replicas', type=int, help="Minimum number of replicas to run for the Java component.")
c.argument('max_replicas', type=int, help="Maximum number of replicas to run for the Java component.")
c.argument('route_yaml', options_list=['--route-yaml', '--yaml'], help="Path to a .yaml file with the configuration of a Spring Cloud Gateway route. For an example, see https://aka.ms/gateway-for-spring-routes-yaml")

with self.argument_context('containerapp job logs show') as c:
c.argument('follow', help="Print logs in real time if present.", arg_type=get_three_state_flag())
Expand Down
8 changes: 7 additions & 1 deletion src/containerapp/azext_containerapp/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def load_command_table(self, args):
with self.command_group('containerapp job replica', is_preview=True) as g:
g.custom_show_command('list', 'list_replica_containerappsjob')

with self.command_group('containerapp env java-component nacos') as g:
with self.command_group('containerapp env java-component nacos', is_preview=True) as g:
g.custom_command('create', 'create_nacos', supports_no_wait=True)
g.custom_command('update', 'update_nacos', supports_no_wait=True)
g.custom_show_command('show', 'show_nacos')
Expand All @@ -225,6 +225,12 @@ def load_command_table(self, args):
g.custom_show_command('show', 'show_admin_for_spring')
g.custom_command('delete', 'delete_admin_for_spring', confirmation=True, supports_no_wait=True)

with self.command_group('containerapp env java-component gateway-for-spring', is_preview=True) as g:
g.custom_command('create', 'create_gateway_for_spring', supports_no_wait=True)
g.custom_command('update', 'update_gateway_for_spring', supports_no_wait=True)
g.custom_show_command('show', 'show_gateway_for_spring')
g.custom_command('delete', 'delete_gateway_for_spring', confirmation=True, supports_no_wait=True)

with self.command_group('containerapp env dotnet-component', is_preview=True) as g:
g.custom_command('list', 'list_dotnet_components')
g.custom_show_command('show', 'show_dotnet_component')
Expand Down
22 changes: 19 additions & 3 deletions src/containerapp/azext_containerapp/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
DEV_QDRANT_CONTAINER_NAME, DEV_QDRANT_SERVICE_TYPE, DEV_WEAVIATE_IMAGE, DEV_WEAVIATE_CONTAINER_NAME, DEV_WEAVIATE_SERVICE_TYPE,
DEV_MILVUS_IMAGE, DEV_MILVUS_CONTAINER_NAME, DEV_MILVUS_SERVICE_TYPE, DEV_SERVICE_LIST, CONTAINER_APPS_SDK_MODELS, BLOB_STORAGE_TOKEN_STORE_SECRET_SETTING_NAME,
DAPR_SUPPORTED_STATESTORE_DEV_SERVICE_LIST, DAPR_SUPPORTED_PUBSUB_DEV_SERVICE_LIST,
JAVA_COMPONENT_CONFIG, JAVA_COMPONENT_EUREKA, JAVA_COMPONENT_ADMIN, JAVA_COMPONENT_NACOS, DOTNET_COMPONENT_RESOURCE_TYPE)
JAVA_COMPONENT_CONFIG, JAVA_COMPONENT_EUREKA, JAVA_COMPONENT_ADMIN, JAVA_COMPONENT_NACOS, JAVA_COMPONENT_GATEWAY, DOTNET_COMPONENT_RESOURCE_TYPE)


logger = get_logger(__name__)
Expand Down Expand Up @@ -2328,7 +2328,7 @@ def delete_java_component(cmd, java_component_name, environment_name, resource_g
return java_component_decorator.delete()


def create_java_component(cmd, java_component_name, environment_name, resource_group_name, target_java_component_type, configuration, service_bindings, unbind_service_bindings, min_replicas, max_replicas, no_wait):
def create_java_component(cmd, java_component_name, environment_name, resource_group_name, target_java_component_type, configuration, service_bindings, unbind_service_bindings, min_replicas, max_replicas, no_wait, route_yaml=None):
raw_parameters = locals()
java_component_decorator = JavaComponentDecorator(
cmd=cmd,
Expand All @@ -2340,7 +2340,7 @@ def create_java_component(cmd, java_component_name, environment_name, resource_g
return java_component_decorator.create()


def update_java_component(cmd, java_component_name, environment_name, resource_group_name, target_java_component_type, configuration, service_bindings, unbind_service_bindings, min_replicas, max_replicas, no_wait):
def update_java_component(cmd, java_component_name, environment_name, resource_group_name, target_java_component_type, configuration, service_bindings, unbind_service_bindings, min_replicas, max_replicas, no_wait, route_yaml=None):
raw_parameters = locals()
java_component_decorator = JavaComponentDecorator(
cmd=cmd,
Expand Down Expand Up @@ -2416,6 +2416,22 @@ def delete_admin_for_spring(cmd, java_component_name, environment_name, resource
return delete_java_component(cmd, java_component_name, environment_name, resource_group_name, JAVA_COMPONENT_ADMIN, no_wait)


def create_gateway_for_spring(cmd, java_component_name, environment_name, resource_group_name, configuration=None, min_replicas=1, max_replicas=1, no_wait=False, route_yaml=None):
return create_java_component(cmd, java_component_name, environment_name, resource_group_name, JAVA_COMPONENT_GATEWAY, configuration, None, None, min_replicas, max_replicas, no_wait, route_yaml)


def update_gateway_for_spring(cmd, java_component_name, environment_name, resource_group_name, configuration=None, min_replicas=None, max_replicas=None, no_wait=False, route_yaml=None):
return update_java_component(cmd, java_component_name, environment_name, resource_group_name, JAVA_COMPONENT_GATEWAY, configuration, None, None, min_replicas, max_replicas, no_wait, route_yaml)


def show_gateway_for_spring(cmd, java_component_name, environment_name, resource_group_name):
return show_java_component(cmd, java_component_name, environment_name, resource_group_name, JAVA_COMPONENT_GATEWAY)


def delete_gateway_for_spring(cmd, java_component_name, environment_name, resource_group_name, no_wait=False):
return delete_java_component(cmd, java_component_name, environment_name, resource_group_name, JAVA_COMPONENT_GATEWAY, no_wait)


def set_environment_telemetry_data_dog(cmd,
name,
resource_group_name,
Expand Down
51 changes: 50 additions & 1 deletion src/containerapp/azext_containerapp/java_component_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from azure.cli.core.commands import AzCliCommand
from azure.cli.core.azclierror import ValidationError, CLIInternalError
from azure.cli.command_modules.containerapp.base_resource import BaseResource
from azure.cli.command_modules.containerapp._decorator_utils import load_yaml_file
from azure.cli.core.commands.client_factory import get_subscription_id

from ._constants import CONTAINER_APPS_RP, MANAGED_ENVIRONMENT_RESOURCE_TYPE
Expand Down Expand Up @@ -61,10 +62,14 @@ def get_argument_min_replicas(self):
def get_argument_max_replicas(self):
return self.get_param("max_replicas")

def get_argument_route_yaml(self):
return self.get_param("route_yaml")

def construct_payload(self):
self.java_component_def["properties"]["componentType"] = self.get_argument_target_java_component_type()
self.set_up_service_bindings()
self.set_up_unbind_service_bindings()
self.set_up_gateway_route()
if self.get_argument_min_replicas() is not None and self.get_argument_max_replicas() is not None:
self.java_component_def["properties"]["scale"] = {
"minReplicas": self.get_argument_min_replicas(),
Expand Down Expand Up @@ -149,7 +154,7 @@ def set_up_service_bindings(self):
self.java_component_def["properties"]["serviceBinds"].append(update_item)

def set_up_unbind_service_bindings(self):
if self.get_argument_unbind_service_bindings():
if self.get_argument_unbind_service_bindings() is not None:
new_template = self.java_component_def.setdefault("properties", {})
existing_template = self.java_component_def["properties"]

Expand All @@ -165,3 +170,47 @@ def set_up_unbind_service_bindings(self):
if item in service_bindings_dict:
new_template["serviceBinds"] = [binding for binding in new_template["serviceBinds"] if
binding["name"] != item]

def set_up_gateway_route(self):
if self.get_argument_route_yaml() is not None:
self.java_component_def["properties"]["springCloudGatewayRoutes"] = self.process_loaded_scg_route()

def process_loaded_scg_route(self):
yaml_scg_routes = load_yaml_file(self.get_argument_route_yaml())

# Check if the loaded YAML is a dictionary
if not isinstance(yaml_scg_routes, dict):
raise ValidationError('Invalid YAML provided. Please see https://aka.ms/gateway-for-spring-routes-yaml for a valid Gateway for Spring routes YAML spec.')

# Ensure that 'springCloudGatewayRoutes' is present and is a list (can be empty)
routes = yaml_scg_routes.get('springCloudGatewayRoutes')
if routes is None:
return []

if not isinstance(routes, list):
raise ValidationError('The "springCloudGatewayRoutes" field must be a list. Please see https://aka.ms/gateway-for-spring-routes-yaml for a valid Gateway for Spring routes YAML spec.')

# Loop through each route and validate the required fields
for route in routes:
if not isinstance(route, dict):
raise ValidationError('Each route must be a dictionary. Please see https://aka.ms/gateway-for-spring-routes-yaml for a valid Gateway for Spring routes YAML spec.')

# Ensure each route has 'id' and 'uri' fields
if 'id' not in route or not route['id']:
raise ValidationError(f'Route is missing required "id" field: {route} Please see https://aka.ms/gateway-for-spring-routes-yaml for a valid Gateway for Spring routes YAML spec.')

if 'uri' not in route or not route['uri']:
raise ValidationError(f'Route is missing required "uri" field: {route} Please see https://aka.ms/gateway-for-spring-routes-yaml for a valid Gateway for Spring routes YAML spec.')

# Ensure predicates and filters are lists; set to empty lists if not provided
if 'predicates' not in route:
route['predicates'] = []
elif not isinstance(route['predicates'], list):
raise ValidationError(f'The "predicates" field must be a list in route {route["id"]}. Please see https://aka.ms/gateway-for-spring-routes-yaml for a valid Gateway for Spring routes YAML spec.')

if 'filters' not in route:
route['filters'] = []
elif not isinstance(route['filters'], list):
raise ValidationError(f'The "filters" field must be a list in route {route["id"]}. Please see https://aka.ms/gateway-for-spring-routes-yaml for a valid Gateway for Spring routes YAML spec.')

return yaml_scg_routes.get('springCloudGatewayRoutes')
Loading

0 comments on commit 8af0340

Please sign in to comment.