diff --git a/src/spring/azext_spring/migration/converter/app_converter.py b/src/spring/azext_spring/migration/converter/app_converter.py index 485ceab6b8a..084446fd0f1 100644 --- a/src/spring/azext_spring/migration/converter/app_converter.py +++ b/src/spring/azext_spring/migration/converter/app_converter.py @@ -6,6 +6,8 @@ class AppConverter(ConverterTemplate): def load_source(self, source): self.source = source + self.managed_components = source['managedComponents'] + self.is_enterprise = source['isEnterprise'] # print(f"App source: {self.source}") def calculate_data(self): @@ -37,7 +39,7 @@ def get_app_name(input_string): return input_string.split('/')[-1] def _get_service_bind(self, source, envName): - enable_sba = source['enabled_sba'] + enable_sba = self.managed_components['sba'] service_bind = [] addon = source['properties'].get('addonConfigs') @@ -50,11 +52,23 @@ def _get_service_bind(self, source, envName): "name": "bind-config", "serviceId": f"resourceId('Microsoft.App/managedEnvironments/javaComponents', '{envName}', 'config')" }) + if self.is_enterprise != True and self.managed_components['config'] == True: + # standard tier enabled config server and bind all apps automatically + service_bind.append({ + "name": "bind-config", + "serviceId": f"resourceId('Microsoft.App/managedEnvironments/javaComponents', '{envName}', 'config')" + }) if addon.get('serviceRegistry') is not None and addon['serviceRegistry'].get('resourceId') is not None: service_bind.append({ "name": "bind-eureka", "serviceId": f"resourceId('Microsoft.App/managedEnvironments/javaComponents', '{envName}', 'eureka')" }) + if self.is_enterprise != True and self.managed_components['eureka'] == True: + # standard tier enabled eureka server and bind all apps automatically + service_bind.append({ + "name": "bind-eureka", + "serviceId": f"resourceId('Microsoft.App/managedEnvironments/javaComponents', '{envName}', 'eureka')" + }) if enable_sba: service_bind.append({ "name": "bind-sba", @@ -117,7 +131,7 @@ def _get_memory_by_cpu(self, cpu): # create a method _convert_probe to convert the probe from the source to the target format def _convert_probe(self, probe, tier): - print(f"probe: {probe}") + # print(f"probe: {probe}") if probe is None: return None if probe.get("disableProbe") == True: @@ -157,7 +171,7 @@ def _convert_tcp_probe_action(self, probe, tier): } else: probeAction = None - print(f"probeAction: {probeAction}") + # print(f"probeAction: {probeAction}") return probeAction def _convert_http_probe_action(self, probe, tier): @@ -170,6 +184,6 @@ def _convert_http_probe_action(self, probe, tier): } else: probeAction = None - print(f"probeAction: {probeAction}") + # print(f"probeAction: {probeAction}") return probeAction diff --git a/src/spring/azext_spring/migration/converter/conversion_context.py b/src/spring/azext_spring/migration/converter/conversion_context.py index c4813373876..65eccd94260 100644 --- a/src/spring/azext_spring/migration/converter/conversion_context.py +++ b/src/spring/azext_spring/migration/converter/conversion_context.py @@ -42,18 +42,24 @@ def run_converters(self, source): source_wrapper = SourceDataWrapper(source) asa_service = source_wrapper.get_resources_by_type('Microsoft.AppPlatform/Spring')[0] - converted_contents[self.get_converter(EnvironmentConverter).get_template_name()] = self.get_converter(EnvironmentConverter).convert( - asa_service - ) + # Environment Converter + is_vnet = self._is_vnet(asa_service) + is_enterprise = self._is_enterprise_tier(asa_service) + asa_service['isVnet'] = is_vnet + converted_contents[self.get_converter(EnvironmentConverter).get_template_name()] = self.get_converter(EnvironmentConverter).convert(asa_service) + + # Cert Converter asa_certs = source_wrapper.get_resources_by_type('Microsoft.AppPlatform/Spring/certificates') asa_kv_certs = [] - for cert in asa_certs: certName = cert['name'].split('/')[-1] if cert['properties'].get('type') == "KeyVaultCertificate": asa_kv_certs.append(cert) converted_contents[certName+"_"+self.get_converter(CertConverter).get_template_name()] = self.get_converter(CertConverter).convert(cert) + elif cert['properties'].get('type') == "ContentCertificate": + converted_contents[certName+"_"+self.get_converter(CertConverter).get_template_name()] = self.get_converter(CertConverter).convert(cert) + # Managed components Converter managed_components = { 'gateway': False, 'config': False, @@ -65,26 +71,31 @@ def run_converters(self, source): converted_contents = self._convert_live_view(source_wrapper, converted_contents, managed_components) converted_contents = self._convert_eureka_and_service_registry(source_wrapper, converted_contents, asa_service, managed_components) + # Apps Converter asa_apps = source_wrapper.get_resources_by_type('Microsoft.AppPlatform/Spring/apps') asa_deployments = source_wrapper.get_resources_by_type('Microsoft.AppPlatform/Spring/apps/deployments') - # print(asa_deployments) - for app in asa_apps: - appName = app['name'].split('/')[-1] - app['enabled_sba'] = managed_components['sba'] - app['deployments'] = [deployment for deployment in asa_deployments if deployment['name'].startswith(f"{app['name']}/")] - converted_contents[appName+"_"+self.get_converter(AppConverter).get_template_name()] = self.get_converter(AppConverter).convert(app) + for app_source in asa_apps: + appName = app_source['name'].split('/')[-1] + app_source['deployments'] = [deployment for deployment in asa_deployments if deployment['name'].startswith(f"{app_source['name']}/")] + app_source['managedComponents'] = managed_components + app_source['isEnterprise'] = is_enterprise + converted_contents[appName+"_"+self.get_converter(AppConverter).get_template_name()] = self.get_converter(AppConverter).convert(app_source) - main_source = { + # Param, readme and main Converter + full_source = { + "asa": asa_service, "apps": asa_apps, "certs": asa_kv_certs, "managedComponents": managed_components, + "isVnet": is_vnet, + "inEnterprise": is_enterprise, } - converted_contents[self.get_converter(ParamConverter).get_template_name()] = self.get_converter(ParamConverter).convert(asa_apps) - converted_contents[self.get_converter(ReadMeConverter).get_template_name()] = self.get_converter(ReadMeConverter).convert(None) - converted_contents[self.get_converter(MainConverter).get_template_name()] = self.get_converter(MainConverter).convert( - main_source - ) + + converted_contents[self.get_converter(ParamConverter).get_template_name()] = self.get_converter(ParamConverter).convert(full_source) + converted_contents[self.get_converter(ReadMeConverter).get_template_name()] = self.get_converter(ReadMeConverter).convert(full_source) + converted_contents[self.get_converter(MainConverter).get_template_name()] = self.get_converter(MainConverter).convert(full_source) + return converted_contents def save_to_files(self, converted_contents, output_path): @@ -151,12 +162,18 @@ def _convert_eureka_and_service_registry(self, source_wrapper, converted_content if not is_enterprise_tier: managed_components['eureka'] = True eureka_key = self.get_converter(EurekaConverter).get_template_name() - converted_contents[eureka_key] = self.get_converter(EurekaConverter).convert() + converted_contents[eureka_key] = self.get_converter(EurekaConverter).convert(None) return converted_contents def _is_enterprise_tier(self, asa_service): return asa_service['sku']['tier'] == 'Enterprise' + def _is_vnet(self, asa_service): + networkProfile = asa_service['properties'].get('networkProfile') + if networkProfile is None: + return False + return networkProfile.get('appSubnetId') is not None + class SourceDataWrapper: def __init__(self, source): self.source = source diff --git a/src/spring/azext_spring/migration/converter/environment_converter.py b/src/spring/azext_spring/migration/converter/environment_converter.py index 5f922a006e0..05b74040686 100644 --- a/src/spring/azext_spring/migration/converter/environment_converter.py +++ b/src/spring/azext_spring/migration/converter/environment_converter.py @@ -12,6 +12,12 @@ def calculate_data(self): "containerAppLogAnalyticsName": f"log-{name}", } + isVnet = self.source['isVnet'] + if isVnet: + self.data["vnetConfiguration"] = { + "internal": str(True).lower(), + } + asa_zone_redundant = self.source['properties'].get('zoneRedundant') if asa_zone_redundant is not None: self.data["zoneRedundant"] = str(asa_zone_redundant).lower() @@ -25,7 +31,6 @@ def calculate_data(self): }] self.data["scheduledEntries"] = aca_maintenance_window - asa_certs = self.source['properties'].get('certificates') def get_template_name(self): - return "environment.bicep" \ No newline at end of file + return "environment.bicep" diff --git a/src/spring/azext_spring/migration/converter/eureka_converter.py b/src/spring/azext_spring/migration/converter/eureka_converter.py index 6798fb3d3ec..08ce3b6b218 100644 --- a/src/spring/azext_spring/migration/converter/eureka_converter.py +++ b/src/spring/azext_spring/migration/converter/eureka_converter.py @@ -5,8 +5,8 @@ class EurekaConverter(ConverterTemplate): def __init__(self): super().__init__() - def load_source(self): - pass + def load_source(self, source): + self.source = source def calculate_data(self): name = "eureka" diff --git a/src/spring/azext_spring/migration/converter/main_converter.py b/src/spring/azext_spring/migration/converter/main_converter.py index b227836caf4..e52958828b4 100644 --- a/src/spring/azext_spring/migration/converter/main_converter.py +++ b/src/spring/azext_spring/migration/converter/main_converter.py @@ -9,6 +9,7 @@ def load_source(self, source): self.certs = source["certs"] def calculate_data(self): + self.data["isVnet"] = self.source.get("isVnet", False) self.data.setdefault("certs", []) for item in self.certs: certName = item['name'].split('/')[-1] diff --git a/src/spring/azext_spring/migration/converter/param_converter.py b/src/spring/azext_spring/migration/converter/param_converter.py index 01e2e16491c..79c02fef28a 100644 --- a/src/spring/azext_spring/migration/converter/param_converter.py +++ b/src/spring/azext_spring/migration/converter/param_converter.py @@ -3,7 +3,8 @@ # Concrete Converter Subclass for paramter class ParamConverter(ConverterTemplate): def load_source(self, source): - self.apps = source + self.apps = source['apps'] + self.is_vnet = source['isVnet'] def calculate_data(self): self.data.setdefault("apps", []) @@ -13,6 +14,7 @@ def calculate_data(self): "appName": appName, "containerAppImageName": "containerImageFor_"+appName.replace("-", "_"), }) + self.data["isVnet"] = self.is_vnet def get_template_name(self): return "param.bicepparam" \ No newline at end of file diff --git a/src/spring/azext_spring/migration/converter/templates/environment.bicep.j2 b/src/spring/azext_spring/migration/converter/templates/environment.bicep.j2 index e02bc23f73b..ecf5410f1be 100644 --- a/src/spring/azext_spring/migration/converter/templates/environment.bicep.j2 +++ b/src/spring/azext_spring/migration/converter/templates/environment.bicep.j2 @@ -1,13 +1,13 @@ // Params -param workloadProfileName string -param workloadProfileType string +param workloadProfileName = 'Dedicated' +param workloadProfileType = 'D4' +param minNodes = 1 +param maxNodes = 10 -param managedEnvName string = '{{data.containerAppEnvName}}' -param containerAppLogAnalyticsName string = '{{data.containerAppLogAnalyticsName}}' -param zoneRedundant bool = {{data.zoneRedundant}} +param vnetSubnetId string = '' resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { - name: containerAppLogAnalyticsName + name: '{{data.containerAppLogAnalyticsName}}' location: resourceGroup().location properties: { sku: { @@ -31,24 +31,30 @@ resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-pr } resource containerAppEnv 'Microsoft.App/managedEnvironments@2024-10-02-preview' = { - name: managedEnvName + name: '{{data.containerAppEnvName}}' location: resourceGroup().location + {% if data.identity %} identity: { type: 'SystemAssigned' } + {% endif %} properties: { - // vnetConfiguration: {} + {% if data.vnetConfiguration %} + vnetConfiguration: { + internal: {{ data.vnetConfiguration.internal }} + infrastructureSubnetId: vnetSubnetId + } + {% endif %} appLogsConfiguration: { destination: 'azure-monitor' } - zoneRedundant: zoneRedundant - // customDomainConfiguration: {} + zoneRedundant: {{data.zoneRedundant}} workloadProfiles: [ { name: workloadProfileName workloadProfileType: workloadProfileType - // minimumCount: minNodes - // maximumCount: maxNodes + minimumCount: minNodes + maximumCount: maxNodes } ] } diff --git a/src/spring/azext_spring/migration/converter/templates/main.bicep.j2 b/src/spring/azext_spring/migration/converter/templates/main.bicep.j2 index 4dcc4e1130a..cd5f5fd4488 100644 --- a/src/spring/azext_spring/migration/converter/templates/main.bicep.j2 +++ b/src/spring/azext_spring/migration/converter/templates/main.bicep.j2 @@ -1,6 +1,7 @@ // Params -param workloadProfileName string -param workloadProfileType string +{% if data.isVnet == true %} +param vnetSubnetId string +{% endif %} {% for app in data.apps %} param {{app.containerAppImageName}} string {% endfor %} @@ -8,8 +9,9 @@ param {{app.containerAppImageName}} string module containerAppEnv 'environment.bicep' = { name: 'containerAppEnvDeployment' params: { - workloadProfileName: workloadProfileName - workloadProfileType: workloadProfileType + {% if data.isVnet == true %} + vnetSubnetId: vnetSubnetId + {% endif %} } } diff --git a/src/spring/azext_spring/migration/converter/templates/param.bicepparam.j2 b/src/spring/azext_spring/migration/converter/templates/param.bicepparam.j2 index 61dcda140b4..b48d1462e4c 100644 --- a/src/spring/azext_spring/migration/converter/templates/param.bicepparam.j2 +++ b/src/spring/azext_spring/migration/converter/templates/param.bicepparam.j2 @@ -1,8 +1,9 @@ using './main.bicep' -param workloadProfileName = 'Consumption' -param workloadProfileType = 'Consumption' - {% for app in data.apps %} param {{app.containerAppImageName}} = 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' -{% endfor %} \ No newline at end of file +{% endfor %} +{% if data.isVnet == true %} +// Replace this wth the actual vnet subnet id +param vnetSubnetId = '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/vnet/subnets/subnet' +{% endif %} \ No newline at end of file