From c40ed7e2ced9de7afadc4388b3dab3cdb092c6a5 Mon Sep 17 00:00:00 2001 From: Guitar Sheng Date: Thu, 16 Jan 2025 14:03:41 +0800 Subject: [PATCH 1/4] add options param --- src/spring/azext_spring/migration/migration_operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spring/azext_spring/migration/migration_operations.py b/src/spring/azext_spring/migration/migration_operations.py index d76081f3130..d884732bf1a 100644 --- a/src/spring/azext_spring/migration/migration_operations.py +++ b/src/spring/azext_spring/migration/migration_operations.py @@ -61,7 +61,7 @@ def export_asa_arm_template(cmd, resource_group, service): subscription, resource_group, service) logger.info("service_resource_id: '%s'", service_resource_id) resources.append(service_resource_id) - options = None + options = "SkipAllParameterization,IncludeParameterDefaultValue" ExportTemplateRequest = cmd.get_models('ExportTemplateRequest') export_template_request = ExportTemplateRequest(resources=resources, options=options) From ed21f2191f7ad92530a4003320c702f84558791f Mon Sep 17 00:00:00 2001 From: Guitar Sheng Date: Sat, 18 Jan 2025 15:11:30 +0800 Subject: [PATCH 2/4] refactor --- .../migration/converter/app_converter.py | 28 ++++ .../migration/converter/base_converter.py | 82 +++++++++++ .../converter/environment_converter.py | 20 +++ .../migration/converter/readme_converter.py | 12 ++ .../migration/converter/revision_converter.py | 19 +++ .../migration/migration_operations.py | 129 +++--------------- 6 files changed, 178 insertions(+), 112 deletions(-) create mode 100644 src/spring/azext_spring/migration/converter/app_converter.py create mode 100644 src/spring/azext_spring/migration/converter/base_converter.py create mode 100644 src/spring/azext_spring/migration/converter/environment_converter.py create mode 100644 src/spring/azext_spring/migration/converter/readme_converter.py create mode 100644 src/spring/azext_spring/migration/converter/revision_converter.py diff --git a/src/spring/azext_spring/migration/converter/app_converter.py b/src/spring/azext_spring/migration/converter/app_converter.py new file mode 100644 index 00000000000..3b54325dc48 --- /dev/null +++ b/src/spring/azext_spring/migration/converter/app_converter.py @@ -0,0 +1,28 @@ +from converter import ConverterTemplate + +# Concrete Converter Subclass for Container App +class AppConverter(ConverterTemplate): + def load_source(self, source): + self.source = source['properties'] + + def calculate_data(self): + self.data = { + "name": self.source.name, + "container_image": "mcr.microsoft.com/azuredocs/containerapps-helloworld:latest", + "target_port": 80, + "cpu_core": "0.5", + "memory_size": "1", + "min_replicas": 1, + "max_replicas": 5, + + "containerAppName": self.source.["name"], + "containerImage": self.source.["container_image"], + "targetPort": self.source.["target_port"], + "cpuCore": self.source.["cpu_core"], + "memorySize": self.source.["memory_size"], + "minReplicas": self.source.["min_replicas"], + "maxReplicas": self.source.["max_replicas"], + } + + def get_template_name(self): + return "app.bicep" \ No newline at end of file diff --git a/src/spring/azext_spring/migration/converter/base_converter.py b/src/spring/azext_spring/migration/converter/base_converter.py new file mode 100644 index 00000000000..f2d9d2f03ea --- /dev/null +++ b/src/spring/azext_spring/migration/converter/base_converter.py @@ -0,0 +1,82 @@ +import os + +from converter import converter, abstractmethod +from jinja2 import Template +from converter import EnvironmentConverter, AppConverter, RevisionConverter, ReadMeConverter + +# Abstract Base Class for Converter +class ConverterTemplate(converter): + def __init__(self): + self.params = {} # custom facing parameters for the converter + self.data = {} # output data of the converter + self.source = {} # input data of the converter + + def set_params(self, params): + self.params = params + + def convert(self, source): + self.load_source(source) + self.calculate_data() + return self.generate_output() + + @abstractmethod + def load_source(self, source): # load the input data + pass + + @abstractmethod + def calculate_data(self): # calculate the output data + pass + + @abstractmethod + def get_template_name(self): + pass + + def generate_output(self): + script_dir = os.path.dirname(os.path.abspath(__file__)) + template_name = self.get_template_name() + with open(f"{script_dir}/templates/{template_name}.j2") as file: + template = Template(file.read()) + return template.render(data=self.data, params=self.params) + +# Context Class +class ConversionContext: + def __init__(self): + self.converters = [] + + def add_converter(self, converter: ConverterTemplate): + self.converters.append(converter) + + def get_converter(self, converter_type: type): + for converter in self.converters: + if isinstance(converter, converter_type): + return converter + raise ValueError(f"Unknown converter type: {converter_type}") + + def set_params_for_converter(self, converter_type, params): + for converter in self.converters: + if isinstance(converter, converter_type): + converter.set_params(params) + + def run_converters(self, source): + converted_contents = [] + for resource in source['resources']: + if resource['type'] == 'Microsoft.AppPlatform/Spring': + converted_contents.append(self.get_converter(EnvironmentConverter).convert(resource)) + if resource['type'] == 'Microsoft.AppPlatform/apps': + converted_contents.append(self.get_converter(AppConverter).convert(resource)) + if resource['type'] == 'Microsoft.AppPlatform/Spring/buildServices': + pass + if resource['type'] == 'Microsoft.AppPlatform/Spring/apps/deployments': + converted_contents.append(self.get_converter(RevisionConverter).convert(resource)) + if resource['type'] == 'Microsoft.AppPlatform/Spring/configServers': + pass + converted_contents.append(self.get_converter(ReadMeConverter).convert(resource)) + return converted_contents + + def save_to_files(self, converted_contents, output_path): + os.makedirs(os.path.dirname(output_path), exist_ok=True) + for i, content in enumerate(converted_contents): + filename = f"{output_path}/export_script_{i+1}.bicep" + with open(filename, 'w', encoding='utf-8') as output_file: + print("Start to generate the {filename} file ...") + output_file.write(content) diff --git a/src/spring/azext_spring/migration/converter/environment_converter.py b/src/spring/azext_spring/migration/converter/environment_converter.py new file mode 100644 index 00000000000..75dc1f3edfd --- /dev/null +++ b/src/spring/azext_spring/migration/converter/environment_converter.py @@ -0,0 +1,20 @@ +from converter import ConverterTemplate + +# Concrete Subclass for Container App Environment +class EnvironmentConverter(ConverterTemplate): + def load_source(self, source): + self.source = source['properties'] + + def calculate_data(self): + self.data = { + "name": self.source.name, + "location": self.source.location, + "log_analytics": f"log-{self.source.name}", + + "containerAppEnvName": self.source["name"], + "location": self.source["location"], + "containerAppLogAnalyticsName": self.source["log_analytics"], + } + + def get_template_name(self): + return "environment.bicep" \ No newline at end of file diff --git a/src/spring/azext_spring/migration/converter/readme_converter.py b/src/spring/azext_spring/migration/converter/readme_converter.py new file mode 100644 index 00000000000..19647ec6652 --- /dev/null +++ b/src/spring/azext_spring/migration/converter/readme_converter.py @@ -0,0 +1,12 @@ +from converter import ConverterTemplate + +# Concrete Converter Subclass for Read Me +class ReadMeConverter(ConverterTemplate): + def load_source(self, source): + pass + + def calculate_data(self): + pass + + def get_template_name(self): + return "readme_template" \ No newline at end of file diff --git a/src/spring/azext_spring/migration/converter/revision_converter.py b/src/spring/azext_spring/migration/converter/revision_converter.py new file mode 100644 index 00000000000..707b1d47c93 --- /dev/null +++ b/src/spring/azext_spring/migration/converter/revision_converter.py @@ -0,0 +1,19 @@ +from converter import ConverterTemplate + +# Concrete Converter Subclass for Revision +class RevisionConverter(ConverterTemplate): + def load_source(self, source): + self.source = source['properties'] + + def calculate_data(self): + self.data = { + "name": self.source.name, + "app": self.source.app_name, + "cpu_core": "0.5", + "memory_size": "1", + "min_replicas": 1, + "max_replicas": 5, + } + + def get_template_name(self): + return "revision.bicep" \ No newline at end of file diff --git a/src/spring/azext_spring/migration/migration_operations.py b/src/spring/azext_spring/migration/migration_operations.py index d884732bf1a..feae8a79346 100644 --- a/src/spring/azext_spring/migration/migration_operations.py +++ b/src/spring/azext_spring/migration/migration_operations.py @@ -5,6 +5,7 @@ from azure.cli.core.commands.client_factory import get_subscription_id from knack.log import get_logger from jinja2 import Environment, FileSystemLoader +from converter import ConversionContext, EnvironmentConverter, AppConverter, RevisionConverter, ReadMeConverter logger = get_logger(__name__) @@ -12,48 +13,31 @@ def migration_aca_start(cmd, client, resource_group, service): # API calls print("Start API calls to get ASA service, apps and deployments...") - asa = get_asa(client, resource_group, service) asa_arm = export_asa_arm_template(cmd, resource_group, service) - # Extract necessary properties from asa_service to aca_env, asa_app to aca_app, asa_deployment to aca_revision - print("Start to convert ASA resources to ACA resources...") - aca = convert_asa_to_aca(asa) + # Create context and add converters + context = ConversionContext() + context.add_converter(EnvironmentConverter()) + context.add_converter(AppConverter()) + context.add_converter(RevisionConverter()) + context.add_converter(ReadMeConverter()) # Define the parameters for the Bicep template and output the Bicep files print("Start to generate ACA bicep files based on parameters...") + # Prepare bicep parameters - main_bicep_params, env_bicep_params, apps_bicep_params = get_aca_bicep_params(aca) + main_bicep_params = get_aca_bicep_params(asa_arm) - output_dir = "output" - template_dir = "templates" - script_dir = os.path.dirname(os.path.abspath(__file__)) + # Set parameters for EnvironmentConverter such as workload profile + context.set_params_for_converter(EnvironmentConverter, main_bicep_params) - env = Environment(loader=FileSystemLoader(os.path.join(script_dir, template_dir))) + # Run all converters + converted_contents = context.run_converters(asa_arm) - # Generate the Bicep files - print(env_bicep_params) - generate_bicep_file(env, "main.bicep.j2", os.path.join(output_dir, "main.bicep"), main_bicep_params) - generate_bicep_file(env, "environment.bicep.j2", os.path.join(output_dir, "environment.bicep"), env_bicep_params) - # [print(app) for app in apps_bicep_params] - [generate_bicep_file(env, "app.bicep.j2", os.path.join(output_dir, f"app-{app['containerAppName']}.bicep"), app) for app in apps_bicep_params] + # Save each line of converted content to a separate file + context.save_to_files(converted_contents, 'output') print("Succeed to generate Bicep files") - # Generate readme file - print("Start to generate the readme file based on parameters...") - readme_params = {} - generate_bicep_file(env, "readme_template.j2", os.path.join(output_dir, "readme"), readme_params) - print("Generated readme file.") - - -def get_asa(client, resource_group, service): - asa_service = client.services.get(resource_group, service) - asa_apps = client.apps.list(resource_group, service) - asa = { - "service": asa_service, - "apps": asa_apps, - } - return asa - def export_asa_arm_template(cmd, resource_group, service): resources = [] subscription = get_subscription_id(cmd.cli_ctx) @@ -77,24 +61,8 @@ def export_asa_arm_template(cmd, resource_group, service): parameters=export_template_request) return result.template -def convert_asa_to_aca(asa): - aca_environment = _convert_asa_service_to_aca_environment(asa["service"]) - aca_apps = [_convert_asa_app_to_aca_app(app) for app in asa["apps"]] - # aca_revisions = [_convert_asa_deployment_to_aca_revision(deployment) for deployment in asa["deployments"]] - aca = { - "environment": aca_environment, - "apps": aca_apps, - # "revisions": aca_revisions - } - return aca - - -def get_aca_bicep_params(aca): - main_bicep_params = {} - env_bicep_params = _get_aca_environment_bicep_params(aca["environment"]) - apps_bicep_params = [_get_aca_app_bicep_params(app) for app in aca["apps"]] # array of container app parameters - return main_bicep_params, env_bicep_params, apps_bicep_params - +def get_aca_bicep_params(aca_arm): + return {"key1": "value1"} def replace_variables_in_template(template_path, output_path, variables): if not os.path.exists(template_path): @@ -110,66 +78,3 @@ def replace_variables_in_template(template_path, output_path, variables): os.makedirs(os.path.dirname(output_path), exist_ok=True) with open(output_path, 'w', encoding='utf-8') as output_file: output_file.write(content) - - -def generate_bicep_file(env, template_name, output_path, variables): - template = env.get_template(template_name) - content = template.render(variables) - - os.makedirs(os.path.dirname(output_path), exist_ok=True) - with open(output_path, 'w', encoding='utf-8') as output_file: - output_file.write(content) - - -def _convert_asa_service_to_aca_environment(asa_service): - aca_environment = { - "name": asa_service.name, - "location": asa_service.location, - "log_analytics": f"log-{asa_service.name}", - } - return aca_environment - - -def _convert_asa_app_to_aca_app(asa_app): - aca_app = { - "name": asa_app.name, - "container_image": "mcr.microsoft.com/azuredocs/containerapps-helloworld:latest", - "target_port": 80, - "cpu_core": "0.5", - "memory_size": "1", - "min_replicas": 1, - "max_replicas": 5, - } - return aca_app - - -def _convert_asa_deployment_to_aca_revision(asa_deployment): - aca_revision = { - "name": asa_deployment.name, - "app": asa_deployment.app_name, - "cpu_core": "0.5", - "memory_size": "1", - "min_replicas": 1, - "max_replicas": 5, - } - return aca_revision - - -def _get_aca_environment_bicep_params(aca_environment): - return { - "containerAppEnvName": aca_environment["name"], - "location": aca_environment["location"], - "containerAppLogAnalyticsName": aca_environment["log_analytics"], - } - - -def _get_aca_app_bicep_params(aca_app): - return { - "containerAppName": aca_app["name"], - "containerImage": aca_app["container_image"], - "targetPort": aca_app["target_port"], - "cpuCore": aca_app["cpu_core"], - "memorySize": aca_app["memory_size"], - "minReplicas": aca_app["min_replicas"], - "maxReplicas": aca_app["max_replicas"], - } From 2e5a05e383a4ab97ed4627ce0d165753ac17946c Mon Sep 17 00:00:00 2001 From: Guitar Sheng Date: Sun, 19 Jan 2025 01:22:25 +0800 Subject: [PATCH 3/4] refactor --- .../migration/converter/__init__.py | 4 ++ .../migration/converter/app_converter.py | 34 +++++----- .../migration/converter/base_converter.py | 48 +------------- .../migration/converter/conversion_context.py | 65 +++++++++++++++++++ .../converter/environment_converter.py | 10 +-- .../migration/converter/gateway_converter.py | 16 +++++ .../migration/converter/readme_converter.py | 2 +- .../migration/converter/revision_converter.py | 24 ++++--- .../migration/migration_operations.py | 8 ++- .../migration/templates/app.bicep.j2 | 34 +++++----- .../migration/templates/environment.bicep.j2 | 11 ++++ .../migration/templates/main.bicep.j2 | 43 ------------ .../migration/templates/revision.bicep.j2 | 28 ++++++++ 13 files changed, 181 insertions(+), 146 deletions(-) create mode 100644 src/spring/azext_spring/migration/converter/__init__.py create mode 100644 src/spring/azext_spring/migration/converter/conversion_context.py create mode 100644 src/spring/azext_spring/migration/converter/gateway_converter.py delete mode 100644 src/spring/azext_spring/migration/templates/main.bicep.j2 create mode 100644 src/spring/azext_spring/migration/templates/revision.bicep.j2 diff --git a/src/spring/azext_spring/migration/converter/__init__.py b/src/spring/azext_spring/migration/converter/__init__.py new file mode 100644 index 00000000000..34913fb394d --- /dev/null +++ b/src/spring/azext_spring/migration/converter/__init__.py @@ -0,0 +1,4 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/src/spring/azext_spring/migration/converter/app_converter.py b/src/spring/azext_spring/migration/converter/app_converter.py index 3b54325dc48..d40576fb759 100644 --- a/src/spring/azext_spring/migration/converter/app_converter.py +++ b/src/spring/azext_spring/migration/converter/app_converter.py @@ -1,28 +1,24 @@ -from converter import ConverterTemplate +from .base_converter import ConverterTemplate # Concrete Converter Subclass for Container App class AppConverter(ConverterTemplate): def load_source(self, source): - self.source = source['properties'] + self.source = [] + for resource in source: + self.source.append(resource) def calculate_data(self): - self.data = { - "name": self.source.name, - "container_image": "mcr.microsoft.com/azuredocs/containerapps-helloworld:latest", - "target_port": 80, - "cpu_core": "0.5", - "memory_size": "1", - "min_replicas": 1, - "max_replicas": 5, - - "containerAppName": self.source.["name"], - "containerImage": self.source.["container_image"], - "targetPort": self.source.["target_port"], - "cpuCore": self.source.["cpu_core"], - "memorySize": self.source.["memory_size"], - "minReplicas": self.source.["min_replicas"], - "maxReplicas": self.source.["max_replicas"], - } + self.data.apps = [] + for app in self.source: + self.data.apps.append({ + "containerAppName": app["properties"]["name"], + "containerImage": app["container_image"], + "targetPort": app["target_port"], + "cpuCore": app["cpu_core"], + "memorySize": app["memory_size"], + "minReplicas": app["min_replicas"], + "maxReplicas": app["max_replicas"], + }) def get_template_name(self): return "app.bicep" \ No newline at end of file diff --git a/src/spring/azext_spring/migration/converter/base_converter.py b/src/spring/azext_spring/migration/converter/base_converter.py index f2d9d2f03ea..a80a78adc31 100644 --- a/src/spring/azext_spring/migration/converter/base_converter.py +++ b/src/spring/azext_spring/migration/converter/base_converter.py @@ -1,11 +1,10 @@ import os -from converter import converter, abstractmethod +from abc import ABC, abstractmethod from jinja2 import Template -from converter import EnvironmentConverter, AppConverter, RevisionConverter, ReadMeConverter # Abstract Base Class for Converter -class ConverterTemplate(converter): +class ConverterTemplate(ABC): def __init__(self): self.params = {} # custom facing parameters for the converter self.data = {} # output data of the converter @@ -37,46 +36,3 @@ def generate_output(self): with open(f"{script_dir}/templates/{template_name}.j2") as file: template = Template(file.read()) return template.render(data=self.data, params=self.params) - -# Context Class -class ConversionContext: - def __init__(self): - self.converters = [] - - def add_converter(self, converter: ConverterTemplate): - self.converters.append(converter) - - def get_converter(self, converter_type: type): - for converter in self.converters: - if isinstance(converter, converter_type): - return converter - raise ValueError(f"Unknown converter type: {converter_type}") - - def set_params_for_converter(self, converter_type, params): - for converter in self.converters: - if isinstance(converter, converter_type): - converter.set_params(params) - - def run_converters(self, source): - converted_contents = [] - for resource in source['resources']: - if resource['type'] == 'Microsoft.AppPlatform/Spring': - converted_contents.append(self.get_converter(EnvironmentConverter).convert(resource)) - if resource['type'] == 'Microsoft.AppPlatform/apps': - converted_contents.append(self.get_converter(AppConverter).convert(resource)) - if resource['type'] == 'Microsoft.AppPlatform/Spring/buildServices': - pass - if resource['type'] == 'Microsoft.AppPlatform/Spring/apps/deployments': - converted_contents.append(self.get_converter(RevisionConverter).convert(resource)) - if resource['type'] == 'Microsoft.AppPlatform/Spring/configServers': - pass - converted_contents.append(self.get_converter(ReadMeConverter).convert(resource)) - return converted_contents - - def save_to_files(self, converted_contents, output_path): - os.makedirs(os.path.dirname(output_path), exist_ok=True) - for i, content in enumerate(converted_contents): - filename = f"{output_path}/export_script_{i+1}.bicep" - with open(filename, 'w', encoding='utf-8') as output_file: - print("Start to generate the {filename} file ...") - output_file.write(content) diff --git a/src/spring/azext_spring/migration/converter/conversion_context.py b/src/spring/azext_spring/migration/converter/conversion_context.py new file mode 100644 index 00000000000..3e68aa76fd2 --- /dev/null +++ b/src/spring/azext_spring/migration/converter/conversion_context.py @@ -0,0 +1,65 @@ +import os + +from abc import ABC, abstractmethod +from jinja2 import Template +from .base_converter import ConverterTemplate +from .environment_converter import EnvironmentConverter +from .app_converter import AppConverter +from .revision_converter import RevisionConverter +from .readme_converter import ReadMeConverter + +# Context Class +class ConversionContext: + def __init__(self): + self.converters = [] + + def add_converter(self, converter: ConverterTemplate): + self.converters.append(converter) + + def get_converter(self, converter_type: type): + for converter in self.converters: + if isinstance(converter, converter_type): + return converter + raise ValueError(f"Unknown converter type: {converter_type}") + + def set_params_for_converter(self, converter_type, params): + for converter in self.converters: + if isinstance(converter, converter_type): + converter.set_params(params) + + def run_converters(self, source): + converted_contents = [] + source_wrapper = SourceDataWrapper(source) + + converted_contents.append( + self.get_converter(EnvironmentConverter).convert( + source_wrapper.get_resources_by_type('Microsoft.AppPlatform/Spring')[0] + ) + ) + converted_contents.append( + self.get_converter(AppConverter).convert( + source_wrapper.get_resources_by_type('Microsoft.AppPlatform/apps') + ) + ) + converted_contents.append( + self.get_converter(RevisionConverter).convert( + source_wrapper.get_resources_by_type('Microsoft.AppPlatform/apps/deployments') + ) + ) + converted_contents.append(self.get_converter(ReadMeConverter).convert(None)) + return converted_contents + + def save_to_files(self, converted_contents, output_path): + os.makedirs(os.path.dirname(output_path), exist_ok=True) + for i, content in enumerate(converted_contents): + filename = f"{output_path}/export_script_{i+1}.bicep" + with open(filename, 'w', encoding='utf-8') as output_file: + print("Start to generate the {filename} file ...") + output_file.write(content) + +class SourceDataWrapper: + def __init__(self, source): + self.source = source + + def get_resources_by_type(self, resource_type): + return [resource for resource in self.source['resources'] if resource['type'] == resource_type] \ No newline at end of file diff --git a/src/spring/azext_spring/migration/converter/environment_converter.py b/src/spring/azext_spring/migration/converter/environment_converter.py index 75dc1f3edfd..fd51576cae9 100644 --- a/src/spring/azext_spring/migration/converter/environment_converter.py +++ b/src/spring/azext_spring/migration/converter/environment_converter.py @@ -1,4 +1,4 @@ -from converter import ConverterTemplate +from .base_converter import ConverterTemplate # Concrete Subclass for Container App Environment class EnvironmentConverter(ConverterTemplate): @@ -7,13 +7,9 @@ def load_source(self, source): def calculate_data(self): self.data = { - "name": self.source.name, + "containerAppEnvName": self.source.name, "location": self.source.location, - "log_analytics": f"log-{self.source.name}", - - "containerAppEnvName": self.source["name"], - "location": self.source["location"], - "containerAppLogAnalyticsName": self.source["log_analytics"], + "containerAppLogAnalyticsName": f"log-{self.source.name}", } def get_template_name(self): diff --git a/src/spring/azext_spring/migration/converter/gateway_converter.py b/src/spring/azext_spring/migration/converter/gateway_converter.py new file mode 100644 index 00000000000..062646b94e2 --- /dev/null +++ b/src/spring/azext_spring/migration/converter/gateway_converter.py @@ -0,0 +1,16 @@ +from .base_converter import ConverterTemplate + +# Concrete Converter Subclass for Gateway +class GatewayConverter(ConverterTemplate): + def __init__(self, client): + self.client = client + + def load_source(self, source): + # Call the client to get additional data + pass + + def calculate_data(self): + pass + + def get_template_name(self): + return "gateway.bicep" \ No newline at end of file diff --git a/src/spring/azext_spring/migration/converter/readme_converter.py b/src/spring/azext_spring/migration/converter/readme_converter.py index 19647ec6652..484cabc2a09 100644 --- a/src/spring/azext_spring/migration/converter/readme_converter.py +++ b/src/spring/azext_spring/migration/converter/readme_converter.py @@ -1,4 +1,4 @@ -from converter import ConverterTemplate +from .base_converter import ConverterTemplate # Concrete Converter Subclass for Read Me class ReadMeConverter(ConverterTemplate): diff --git a/src/spring/azext_spring/migration/converter/revision_converter.py b/src/spring/azext_spring/migration/converter/revision_converter.py index 707b1d47c93..21c692315a2 100644 --- a/src/spring/azext_spring/migration/converter/revision_converter.py +++ b/src/spring/azext_spring/migration/converter/revision_converter.py @@ -1,19 +1,23 @@ -from converter import ConverterTemplate +from .base_converter import ConverterTemplate # Concrete Converter Subclass for Revision class RevisionConverter(ConverterTemplate): def load_source(self, source): - self.source = source['properties'] + self.source = [] + for resource in source: + self.source.append(resource) def calculate_data(self): - self.data = { - "name": self.source.name, - "app": self.source.app_name, - "cpu_core": "0.5", - "memory_size": "1", - "min_replicas": 1, - "max_replicas": 5, - } + self.data.revisions = [] + for revision in self.source: + self.data.revisions.append({ + "name": revision["properties"]["name"], + "app": self.source.app_name, + "cpu_core": "0.5", + "memory_size": "1", + "min_replicas": 1, + "max_replicas": 5, + }) def get_template_name(self): return "revision.bicep" \ No newline at end of file diff --git a/src/spring/azext_spring/migration/migration_operations.py b/src/spring/azext_spring/migration/migration_operations.py index feae8a79346..4a7ca77e107 100644 --- a/src/spring/azext_spring/migration/migration_operations.py +++ b/src/spring/azext_spring/migration/migration_operations.py @@ -5,7 +5,12 @@ from azure.cli.core.commands.client_factory import get_subscription_id from knack.log import get_logger from jinja2 import Environment, FileSystemLoader -from converter import ConversionContext, EnvironmentConverter, AppConverter, RevisionConverter, ReadMeConverter +from .converter.conversion_context import ConversionContext +from .converter.environment_converter import EnvironmentConverter +from .converter.app_converter import AppConverter +from .converter.revision_converter import RevisionConverter +from .converter.gateway_converter import GatewayConverter +from .converter.readme_converter import ReadMeConverter logger = get_logger(__name__) @@ -20,6 +25,7 @@ def migration_aca_start(cmd, client, resource_group, service): context.add_converter(EnvironmentConverter()) context.add_converter(AppConverter()) context.add_converter(RevisionConverter()) + context.add_converter(GatewayConverter(client)) context.add_converter(ReadMeConverter()) # Define the parameters for the Bicep template and output the Bicep files diff --git a/src/spring/azext_spring/migration/templates/app.bicep.j2 b/src/spring/azext_spring/migration/templates/app.bicep.j2 index 1026dd1c4d2..8a5c588500f 100644 --- a/src/spring/azext_spring/migration/templates/app.bicep.j2 +++ b/src/spring/azext_spring/migration/templates/app.bicep.j2 @@ -15,6 +15,21 @@ param maxReplicas int = {{maxReplicas}} param containerAppEnvId string +module appModule 'app.bicep' = [for app in data.apps: { + name: '${app.name}' + dependsOn: [containerAppEnv] + params: { + containerAppName: containerAppName + containerImage: containerImage + targetPort: targetPort + cpuCore: cpuCore + memorySize: memorySize + minReplicas: minReplicas + maxReplicas: maxReplicas + containerAppEnvId: containerAppEnv.outputs.containerAppEnvId + } +}] + resource containerApp 'Microsoft.App/containerApps@2024-03-01' = { name: containerAppName location: resourceGroup().location @@ -52,23 +67,4 @@ resource containerApp 'Microsoft.App/containerApps@2024-03-01' = { } } -resource conainerAppRevision 'Microsoft.App/containerApp/revisions@2024-03-01' = [for revision in range(1, 2): { - name: '${containerAppName}-${revision}' - parent: containerApp - properties: { - template: { - containers: [ - { - name: containerAppName - image: containerImage - resources: { - cpu: json(cpuCore) - memory: '${memorySize}Gi' - } - } - ] - } - } -}] - output containerAppFQDN string = containerApp.properties.configuration.ingress.fqdn diff --git a/src/spring/azext_spring/migration/templates/environment.bicep.j2 b/src/spring/azext_spring/migration/templates/environment.bicep.j2 index e2c55d2db0a..b7fae802e19 100644 --- a/src/spring/azext_spring/migration/templates/environment.bicep.j2 +++ b/src/spring/azext_spring/migration/templates/environment.bicep.j2 @@ -6,6 +6,17 @@ param workloadProfileType string param daprAIInstrumentationKey string? = {{daprAIInstrumentationKey}} param daprAIConnectionString string? = {{daprAIConnectionString}} +module containerAppEnv 'environment.bicep' = { + name: 'containerAppEnvDeployment' + params: { + containerAppEnvName: containerAppEnvName + containerAppLogAnalyticsName: containerAppLogAnalyticsName + location: location + workloadProfileName: workloadProfileName + workloadProfileType: workloadProfileType + } +} + resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { name: containerAppLogAnalyticsName location: location diff --git a/src/spring/azext_spring/migration/templates/main.bicep.j2 b/src/spring/azext_spring/migration/templates/main.bicep.j2 deleted file mode 100644 index 72a133a5b52..00000000000 --- a/src/spring/azext_spring/migration/templates/main.bicep.j2 +++ /dev/null @@ -1,43 +0,0 @@ -// Env -param containerAppEnvName string -param containerAppLogAnalyticsName string -param location string -param apps array -param revisions object -param workloadProfileName string -param workloadProfileType string - -// App -param containerAppName string -param containerImage string -param targetPort int -param cpuCore string -param memorySize string -param minReplicas int -param maxReplicas int - -module containerAppEnv 'environment.bicep' = { - name: 'containerAppEnvDeployment' - params: { - containerAppEnvName: containerAppEnvName - containerAppLogAnalyticsName: containerAppLogAnalyticsName - location: location - workloadProfileName: workloadProfileName - workloadProfileType: workloadProfileType - } -} - -module appModule 'app.bicep' = [for app in apps: { - name: '${app.name}' - dependsOn: [containerAppEnv] - params: { - containerAppName: containerAppName - containerImage: containerImage - targetPort: targetPort - cpuCore: cpuCore - memorySize: memorySize - minReplicas: minReplicas - maxReplicas: maxReplicas - containerAppEnvId: containerAppEnv.outputs.containerAppEnvId - } -}] \ No newline at end of file diff --git a/src/spring/azext_spring/migration/templates/revision.bicep.j2 b/src/spring/azext_spring/migration/templates/revision.bicep.j2 new file mode 100644 index 00000000000..18a8adb943f --- /dev/null +++ b/src/spring/azext_spring/migration/templates/revision.bicep.j2 @@ -0,0 +1,28 @@ +param containerAppName string = {{containerAppName}} +param containerImage string = {{containerImage}} + +param cpuCore string = {{cpuCore}} +param memorySize string = {{memorySize}} + +param containerAppEnvId string + +resource conainerAppRevision 'Microsoft.App/containerApp/revisions@2024-03-01' = [for revision in range(1, 2): { + name: '${containerAppName}-${revision}' + parent: containerApp + properties: { + template: { + containers: [ + { + name: containerAppName + image: containerImage + resources: { + cpu: json(cpuCore) + memory: '${memorySize}Gi' + } + } + ] + } + } +}] + +output containerAppFQDN string = containerApp.properties.configuration.ingress.fqdn From 6cd8e63d8727441035485adc7a8ce6471ee1ae4e Mon Sep 17 00:00:00 2001 From: Guitar Sheng Date: Mon, 20 Jan 2025 16:46:51 +0800 Subject: [PATCH 4/4] refactor --- .../migration/converter/app_converter.py | 8 +++---- .../migration/converter/base_converter.py | 6 +++++ .../migration/converter/conversion_context.py | 24 ++++++++++--------- .../converter/environment_converter.py | 8 +++---- .../migration/converter/main_converter.py | 12 ++++++++++ .../{ => converter}/templates/app.bicep.j2 | 15 ------------ .../templates/environment.bicep.j2 | 11 --------- .../converter/templates/main.bicep.j2 | 24 +++++++++++++++++++ .../templates/param.bicepparam.j2 | 0 .../templates/readme_template.j2 | 0 .../templates/revision.bicep.j2 | 2 -- .../migration/migration_operations.py | 17 +------------ 12 files changed, 64 insertions(+), 63 deletions(-) create mode 100644 src/spring/azext_spring/migration/converter/main_converter.py rename src/spring/azext_spring/migration/{ => converter}/templates/app.bicep.j2 (76%) rename src/spring/azext_spring/migration/{ => converter}/templates/environment.bicep.j2 (83%) create mode 100644 src/spring/azext_spring/migration/converter/templates/main.bicep.j2 rename src/spring/azext_spring/migration/{ => converter}/templates/param.bicepparam.j2 (100%) rename src/spring/azext_spring/migration/{ => converter}/templates/readme_template.j2 (100%) rename src/spring/azext_spring/migration/{ => converter}/templates/revision.bicep.j2 (88%) diff --git a/src/spring/azext_spring/migration/converter/app_converter.py b/src/spring/azext_spring/migration/converter/app_converter.py index d40576fb759..4b9032b9303 100644 --- a/src/spring/azext_spring/migration/converter/app_converter.py +++ b/src/spring/azext_spring/migration/converter/app_converter.py @@ -11,10 +11,10 @@ def calculate_data(self): self.data.apps = [] for app in self.source: self.data.apps.append({ - "containerAppName": app["properties"]["name"], - "containerImage": app["container_image"], - "targetPort": app["target_port"], - "cpuCore": app["cpu_core"], + "containerAppName": app["name"], + "containerImage": self.params["container_image"], + "targetPort": self.params["target_port"], + "cpuCore": app['properties']["cpu_core"], "memorySize": app["memory_size"], "minReplicas": app["min_replicas"], "maxReplicas": app["max_replicas"], diff --git a/src/spring/azext_spring/migration/converter/base_converter.py b/src/spring/azext_spring/migration/converter/base_converter.py index a80a78adc31..ef21b63f827 100644 --- a/src/spring/azext_spring/migration/converter/base_converter.py +++ b/src/spring/azext_spring/migration/converter/base_converter.py @@ -4,6 +4,12 @@ from jinja2 import Template # Abstract Base Class for Converter +# The converter is a template class that defines the structure of the conversion process +# The responsibility of the converter is to convert the input data into the output data +# The conversion process is divided into three steps: +# 1. Load the input data +# 2. Calculate the output data +# 3. Generate the output data class ConverterTemplate(ABC): def __init__(self): self.params = {} # custom facing parameters for the converter diff --git a/src/spring/azext_spring/migration/converter/conversion_context.py b/src/spring/azext_spring/migration/converter/conversion_context.py index 3e68aa76fd2..0013105ab82 100644 --- a/src/spring/azext_spring/migration/converter/conversion_context.py +++ b/src/spring/azext_spring/migration/converter/conversion_context.py @@ -7,6 +7,7 @@ from .app_converter import AppConverter from .revision_converter import RevisionConverter from .readme_converter import ReadMeConverter +from .main_converter import MainConverter # Context Class class ConversionContext: @@ -30,26 +31,27 @@ def set_params_for_converter(self, converter_type, params): def run_converters(self, source): converted_contents = [] source_wrapper = SourceDataWrapper(source) - + # converted_contents.append(self.get_converter(MainConverter).convert(None)) converted_contents.append( self.get_converter(EnvironmentConverter).convert( source_wrapper.get_resources_by_type('Microsoft.AppPlatform/Spring')[0] ) ) - converted_contents.append( - self.get_converter(AppConverter).convert( - source_wrapper.get_resources_by_type('Microsoft.AppPlatform/apps') - ) - ) - converted_contents.append( - self.get_converter(RevisionConverter).convert( - source_wrapper.get_resources_by_type('Microsoft.AppPlatform/apps/deployments') - ) - ) + # converted_contents.append( + # self.get_converter(AppConverter).convert( + # source_wrapper.get_resources_by_type('Microsoft.AppPlatform/apps') + # ) + # ) + # converted_contents.append( + # self.get_converter(RevisionConverter).convert( + # source_wrapper.get_resources_by_type('Microsoft.AppPlatform/apps/deployments') + # ) + # ) converted_contents.append(self.get_converter(ReadMeConverter).convert(None)) return converted_contents def save_to_files(self, converted_contents, output_path): + print("Start to save the converted content to files ...") os.makedirs(os.path.dirname(output_path), exist_ok=True) for i, content in enumerate(converted_contents): filename = f"{output_path}/export_script_{i+1}.bicep" diff --git a/src/spring/azext_spring/migration/converter/environment_converter.py b/src/spring/azext_spring/migration/converter/environment_converter.py index fd51576cae9..f72ec44d219 100644 --- a/src/spring/azext_spring/migration/converter/environment_converter.py +++ b/src/spring/azext_spring/migration/converter/environment_converter.py @@ -3,13 +3,13 @@ # Concrete Subclass for Container App Environment class EnvironmentConverter(ConverterTemplate): def load_source(self, source): - self.source = source['properties'] + self.source = source def calculate_data(self): self.data = { - "containerAppEnvName": self.source.name, - "location": self.source.location, - "containerAppLogAnalyticsName": f"log-{self.source.name}", + "containerAppEnvName": self.source['name'], + "location": self.source['location'], + "containerAppLogAnalyticsName": f"log-{self.source['name']}", } def get_template_name(self): diff --git a/src/spring/azext_spring/migration/converter/main_converter.py b/src/spring/azext_spring/migration/converter/main_converter.py new file mode 100644 index 00000000000..d96edbfcee8 --- /dev/null +++ b/src/spring/azext_spring/migration/converter/main_converter.py @@ -0,0 +1,12 @@ +from .base_converter import ConverterTemplate + +# Concrete Converter Subclass for Read Me +class MainConverter(ConverterTemplate): + def load_source(self, source): + pass + + def calculate_data(self): + pass + + def get_template_name(self): + return "main.bicep" \ No newline at end of file diff --git a/src/spring/azext_spring/migration/templates/app.bicep.j2 b/src/spring/azext_spring/migration/converter/templates/app.bicep.j2 similarity index 76% rename from src/spring/azext_spring/migration/templates/app.bicep.j2 rename to src/spring/azext_spring/migration/converter/templates/app.bicep.j2 index 8a5c588500f..8abcdbbff85 100644 --- a/src/spring/azext_spring/migration/templates/app.bicep.j2 +++ b/src/spring/azext_spring/migration/converter/templates/app.bicep.j2 @@ -15,21 +15,6 @@ param maxReplicas int = {{maxReplicas}} param containerAppEnvId string -module appModule 'app.bicep' = [for app in data.apps: { - name: '${app.name}' - dependsOn: [containerAppEnv] - params: { - containerAppName: containerAppName - containerImage: containerImage - targetPort: targetPort - cpuCore: cpuCore - memorySize: memorySize - minReplicas: minReplicas - maxReplicas: maxReplicas - containerAppEnvId: containerAppEnv.outputs.containerAppEnvId - } -}] - resource containerApp 'Microsoft.App/containerApps@2024-03-01' = { name: containerAppName location: resourceGroup().location diff --git a/src/spring/azext_spring/migration/templates/environment.bicep.j2 b/src/spring/azext_spring/migration/converter/templates/environment.bicep.j2 similarity index 83% rename from src/spring/azext_spring/migration/templates/environment.bicep.j2 rename to src/spring/azext_spring/migration/converter/templates/environment.bicep.j2 index b7fae802e19..e2c55d2db0a 100644 --- a/src/spring/azext_spring/migration/templates/environment.bicep.j2 +++ b/src/spring/azext_spring/migration/converter/templates/environment.bicep.j2 @@ -6,17 +6,6 @@ param workloadProfileType string param daprAIInstrumentationKey string? = {{daprAIInstrumentationKey}} param daprAIConnectionString string? = {{daprAIConnectionString}} -module containerAppEnv 'environment.bicep' = { - name: 'containerAppEnvDeployment' - params: { - containerAppEnvName: containerAppEnvName - containerAppLogAnalyticsName: containerAppLogAnalyticsName - location: location - workloadProfileName: workloadProfileName - workloadProfileType: workloadProfileType - } -} - resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { name: containerAppLogAnalyticsName location: location diff --git a/src/spring/azext_spring/migration/converter/templates/main.bicep.j2 b/src/spring/azext_spring/migration/converter/templates/main.bicep.j2 new file mode 100644 index 00000000000..1cbaca607b1 --- /dev/null +++ b/src/spring/azext_spring/migration/converter/templates/main.bicep.j2 @@ -0,0 +1,24 @@ +// Env +param containerAppEnvName string +param location string +param apps array +param workloadProfileName string +param workloadProfileType string + +module containerAppEnv 'environment.bicep' = { + name: 'containerAppEnvDeployment' + params: { + containerAppEnvName: containerAppEnvName + location: location + workloadProfileName: workloadProfileName + workloadProfileType: workloadProfileType + } +} + +module appModule 'app.bicep' = [for app in apps: { + name: '${app.name}' + dependsOn: [containerAppEnv] + params: { + containerAppEnvId: containerAppEnv.outputs.containerAppEnvId + } +}] \ No newline at end of file diff --git a/src/spring/azext_spring/migration/templates/param.bicepparam.j2 b/src/spring/azext_spring/migration/converter/templates/param.bicepparam.j2 similarity index 100% rename from src/spring/azext_spring/migration/templates/param.bicepparam.j2 rename to src/spring/azext_spring/migration/converter/templates/param.bicepparam.j2 diff --git a/src/spring/azext_spring/migration/templates/readme_template.j2 b/src/spring/azext_spring/migration/converter/templates/readme_template.j2 similarity index 100% rename from src/spring/azext_spring/migration/templates/readme_template.j2 rename to src/spring/azext_spring/migration/converter/templates/readme_template.j2 diff --git a/src/spring/azext_spring/migration/templates/revision.bicep.j2 b/src/spring/azext_spring/migration/converter/templates/revision.bicep.j2 similarity index 88% rename from src/spring/azext_spring/migration/templates/revision.bicep.j2 rename to src/spring/azext_spring/migration/converter/templates/revision.bicep.j2 index 18a8adb943f..aa7e2c9d349 100644 --- a/src/spring/azext_spring/migration/templates/revision.bicep.j2 +++ b/src/spring/azext_spring/migration/converter/templates/revision.bicep.j2 @@ -24,5 +24,3 @@ resource conainerAppRevision 'Microsoft.App/containerApp/revisions@2024-03-01' = } } }] - -output containerAppFQDN string = containerApp.properties.configuration.ingress.fqdn diff --git a/src/spring/azext_spring/migration/migration_operations.py b/src/spring/azext_spring/migration/migration_operations.py index 4a7ca77e107..88b48ad8c39 100644 --- a/src/spring/azext_spring/migration/migration_operations.py +++ b/src/spring/azext_spring/migration/migration_operations.py @@ -41,7 +41,7 @@ def migration_aca_start(cmd, client, resource_group, service): converted_contents = context.run_converters(asa_arm) # Save each line of converted content to a separate file - context.save_to_files(converted_contents, 'output') + context.save_to_files(converted_contents, os.path.join("output","")) print("Succeed to generate Bicep files") def export_asa_arm_template(cmd, resource_group, service): @@ -69,18 +69,3 @@ def export_asa_arm_template(cmd, resource_group, service): def get_aca_bicep_params(aca_arm): return {"key1": "value1"} - -def replace_variables_in_template(template_path, output_path, variables): - if not os.path.exists(template_path): - raise FileNotFoundError(f"Template file {template_path} not found") - - with open(template_path, 'r', encoding='utf-8') as template_file: - content = template_file.read() - - for key, value in variables.items(): - placeholder = f"{{{{{key}}}}}" - content = content.replace(placeholder, f"'{value}'") - - os.makedirs(os.path.dirname(output_path), exist_ok=True) - with open(output_path, 'w', encoding='utf-8') as output_file: - output_file.write(content)