diff --git a/azure-functions-maven-plugin/CHANGELOG.md b/azure-functions-maven-plugin/CHANGELOG.md index 04be11bbd9..61ac4bf198 100644 --- a/azure-functions-maven-plugin/CHANGELOG.md +++ b/azure-functions-maven-plugin/CHANGELOG.md @@ -22,6 +22,11 @@ All notable changes to the "Maven Plugin for Azure Function" will be documented - [1.2.1](#121) - [1.2.0](#120) +## 1.14.0 +- Support default value for region/pricing tier/javaVersion [#1755](https://github.com/microsoft/azure-maven-plugins/pull/1761) +- Support username and password in proxy [#1677](https://github.com/microsoft/azure-maven-plugins/pull/1677) +- Fix warning message of `illegal reflective access from groovy` [#1763](https://github.com/microsoft/azure-maven-plugins/pull/1763) + ## 1.13.0 - Support skip function extensions installation [#1616](https://github.com/microsoft/azure-maven-plugins/issues/1616) (Thanks @sschmeck) diff --git a/azure-functions-maven-plugin/pom.xml b/azure-functions-maven-plugin/pom.xml index c28f476245..293a4b1c12 100644 --- a/azure-functions-maven-plugin/pom.xml +++ b/azure-functions-maven-plugin/pom.xml @@ -6,11 +6,11 @@ com.microsoft.azure azure-maven-plugins - 1.14.0-SNAPSHOT + 1.14.0 azure-functions-maven-plugin - 1.14.0-SNAPSHOT + 1.14.0 maven-plugin Maven Plugin for Azure Functions Maven Plugin for Azure Functions @@ -52,22 +52,17 @@ provided - com.squareup.okhttp3 - okhttp - - - org.jetbrains.kotlin - kotlin-stdlib - - + io.projectreactor.netty + reactor-netty - com.squareup.okhttp3 - logging-interceptor + com.azure + azure-core-http-netty - com.squareup.okhttp3 - okhttp-urlconnection + org.projectlombok + lombok + provided com.microsoft.azure @@ -173,6 +168,12 @@ azure-eventhubs-eph test + + com.microsoft.azure + azure + 1.41.1 + test + org.jacoco org.jacoco.agent @@ -275,6 +276,56 @@ + + + + com.nickwongdev + aspectj-maven-plugin + + false + 1.8 + 1.8 + ignore + 1.8 + UTF-8 + false + true + true + + + + com.microsoft.azure + azure-toolkit-common-lib + + + + + + compile-with-aspectj + process-classes + + + ${project.build.directory}/classes + + + + compile + + + + test-compile-with-aspectj + process-test-classes + + + ${project.build.directory}/test-classes + + + + test-compile + + + + org.apache.maven.plugins maven-plugin-plugin @@ -304,7 +355,6 @@ - org.jacoco jacoco-maven-plugin diff --git a/azure-functions-maven-plugin/src/it/3-eventhub-trigger/cleanup.groovy b/azure-functions-maven-plugin/src/it/3-eventhub-trigger/cleanup.groovy deleted file mode 100644 index 2e5b53b5e4..0000000000 --- a/azure-functions-maven-plugin/src/it/3-eventhub-trigger/cleanup.groovy +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - */ - -import com.microsoft.azure.maven.function.invoker.storage.EventHubProcesser -import com.microsoft.azure.maven.function.invoker.CommonUtils - -String storageName = "cihub${timestamp}" -String namespaceName = "FunctionCIEventHubNamespace-${timestamp}" -String resourceGroupName = "maven-functions-it-${timestamp}-rg-3" - -EventHubProcesser eventHubProcesser = null -try { - eventHubProcesser = new EventHubProcesser(resourceGroupName, namespaceName, storageName); - // verify - CommonUtils.runVerification(new Runnable() { - @Override - void run() { - eventHubProcesser.sendMessageToEventHub("trigger", "CIInput") - sleep(10 * 1000 /* ms */) - assert eventHubProcesser.getMessageFromEventHub("output").get(0) == "CITest" - } - }) -} finally { - if (eventHubProcesser != null) { - eventHubProcesser.close() - } -} -CommonUtils.deleteAzureResourceGroup(resourceGroupName, false) -return true - diff --git a/azure-functions-maven-plugin/src/it/3-eventhub-trigger/pom.xml b/azure-functions-maven-plugin/src/it/3-eventhub-trigger/pom.xml deleted file mode 100644 index 630fe4ee77..0000000000 --- a/azure-functions-maven-plugin/src/it/3-eventhub-trigger/pom.xml +++ /dev/null @@ -1,115 +0,0 @@ - - - 4.0.0 - - com.microsoft.azure - azure-java-functions - 1.0-SNAPSHOT - jar - - Azure Java Functions - - - UTF-8 - 1.8 - 1.8 - maven-functions-it-${timestamp}-3 - westus - ${project.build.directory}/azure-functions/${functionAppName} - maven-functions-it-${timestamp}-rg-3 - - - - - com.microsoft.azure.functions - azure-functions-java-library - 1.2.0 - - - com.microsoft.azure - azure-eventhubs - 1.2.0 - - - com.microsoft.azure - azure-eventhubs-eph - 2.0.1 - - - com.microsoft.azure - azure - 1.18.0 - - - org.codehaus.plexus - plexus-utils - 3.0.20 - - - - - - - - maven-resources-plugin - 3.0.2 - - - @project.groupId@ - @project.artifactId@ - @project.version@ - - - - - - - @project.groupId@ - @project.artifactId@ - - - azure-auth - - ${functionResourceGroup} - ${functionAppName} - ${functionAppRegion} - - - - package-functions - - package - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 3.0.2 - - - copy-dependencies - prepare-package - - copy-dependencies - - - ${stagingDirectory}/lib - false - false - true - runtime - azure-functions-java-library - - - - - - - - - - diff --git a/azure-functions-maven-plugin/src/it/3-eventhub-trigger/setup.groovy b/azure-functions-maven-plugin/src/it/3-eventhub-trigger/setup.groovy deleted file mode 100644 index a7ad21f1df..0000000000 --- a/azure-functions-maven-plugin/src/it/3-eventhub-trigger/setup.groovy +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - */ - -import com.microsoft.azure.maven.function.invoker.CommonUtils -import com.microsoft.azure.maven.function.invoker.storage.EventHubProcesser - -String functionName = "maven-functions-it-${timestamp}-3" -String storageName = "cihub${timestamp}" -String namespaceName = "FunctionCIEventHubNamespace-${timestamp}" -String resourceGroupName = "maven-functions-it-${timestamp}-rg-3" - -CommonUtils.azureLogin() -CommonUtils.deleteAzureResourceGroup(resourceGroupName, true) - -// Create EventHub -EventHubProcesser eventHubProcesser = null -eventHubProcesser = new EventHubProcesser(resourceGroupName, namespaceName, storageName); -eventHubProcesser.createOrGetEventHubByName("trigger") -eventHubProcesser.createOrGetEventHubByName("output") - -// Get connnection string of EventHub and save it to pom -def connectionString = eventHubProcesser.getEventHubConnectionString() - -// Create FunctionApp and set eventhub connection string -CommonUtils.executeCommand("az functionapp create --resource-group ${resourceGroupName} --consumption-plan-location westus " + - "--name ${functionName} --storage-account ${storageName}") - -CommonUtils.executeCommand("az webapp config appsettings set --name ${functionName} --resource-group ${resourceGroupName} --settings CIEventHubConnection=\"${connectionString}\"") - -return true - diff --git a/azure-functions-maven-plugin/src/it/3-eventhub-trigger/src/main/java/com/microsoft/azure/EventHubTriggerJava.java b/azure-functions-maven-plugin/src/it/3-eventhub-trigger/src/main/java/com/microsoft/azure/EventHubTriggerJava.java deleted file mode 100644 index 53b39057ae..0000000000 --- a/azure-functions-maven-plugin/src/it/3-eventhub-trigger/src/main/java/com/microsoft/azure/EventHubTriggerJava.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.microsoft.azure; - -import com.microsoft.azure.functions.annotation.*; -import com.microsoft.azure.functions.*; - -/** - * Azure Functions with Event Hub trigger. - */ -public class EventHubTriggerJava { - /** - * This function will be invoked when an event is received from Event Hub. - */ - @FunctionName("EventHubTriggerJava") - public void run( - @EventHubTrigger(name = "message", eventHubName = "trigger", connection = "CIEventHubConnection", consumerGroup = "$Default") String message, - @EventHubOutput(name = "result", eventHubName = "output", connection = "CIEventHubConnection") OutputBinding result, - final ExecutionContext context - ) { - if(message.contains("CIInput")) { - result.setValue("CITest"); - } - } -} diff --git a/azure-functions-maven-plugin/src/main/java/com/microsoft/azure/maven/function/AbstractFunctionMojo.java b/azure-functions-maven-plugin/src/main/java/com/microsoft/azure/maven/function/AbstractFunctionMojo.java index ad7842d829..f85356cd0c 100644 --- a/azure-functions-maven-plugin/src/main/java/com/microsoft/azure/maven/function/AbstractFunctionMojo.java +++ b/azure-functions-maven-plugin/src/main/java/com/microsoft/azure/maven/function/AbstractFunctionMojo.java @@ -6,9 +6,9 @@ package com.microsoft.azure.maven.function; import com.microsoft.azure.maven.AbstractAppServiceMojo; -import com.microsoft.azure.toolkit.lib.appservice.service.IFunctionApp; import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException; import com.microsoft.azure.toolkit.lib.legacy.function.configurations.RuntimeConfiguration; +import lombok.Getter; import org.apache.commons.lang3.StringUtils; import org.apache.maven.plugins.annotations.Parameter; @@ -44,6 +44,7 @@ public abstract class AbstractFunctionMojo extends AbstractAppServiceMojo { *
  • P3V2
  • * */ + @Getter @Parameter(property = "functions.pricingTier") protected String pricingTier; @@ -64,7 +65,7 @@ public abstract class AbstractFunctionMojo extends AbstractAppServiceMojo { /** * App Service region, which will only be used to create App Service at the first time. */ - @Parameter(property = "functions.region", defaultValue = "westeurope") + @Parameter(property = "functions.region") protected String region; @Parameter(property = "functions.runtime") @@ -79,6 +80,9 @@ public abstract class AbstractFunctionMojo extends AbstractAppServiceMojo { @Parameter(property = "functions.disableAppInsights", defaultValue = "false") protected boolean disableAppInsights; + @Getter + protected ConfigParser parser = new ConfigParser(this); + //endregion //region Getter @@ -108,10 +112,6 @@ public boolean isDisableAppInsights() { return disableAppInsights; } - public IFunctionApp getFunctionApp() { - return getOrCreateAzureAppServiceClient().functionApp(getResourceGroup(), getAppName()); - } - public RuntimeConfiguration getRuntimeConfiguration() { return runtime; } diff --git a/azure-functions-maven-plugin/src/main/java/com/microsoft/azure/maven/function/ConfigParser.java b/azure-functions-maven-plugin/src/main/java/com/microsoft/azure/maven/function/ConfigParser.java new file mode 100644 index 0000000000..a00c05b60b --- /dev/null +++ b/azure-functions-maven-plugin/src/main/java/com/microsoft/azure/maven/function/ConfigParser.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ +package com.microsoft.azure.maven.function; + +import com.microsoft.azure.maven.MavenDockerCredentialProvider; +import com.microsoft.azure.toolkit.lib.appservice.config.FunctionAppConfig; +import com.microsoft.azure.toolkit.lib.appservice.config.RuntimeConfig; +import com.microsoft.azure.toolkit.lib.appservice.model.JavaVersion; +import com.microsoft.azure.toolkit.lib.appservice.model.OperatingSystem; +import com.microsoft.azure.toolkit.lib.appservice.model.PricingTier; +import com.microsoft.azure.toolkit.lib.appservice.model.WebContainer; +import com.microsoft.azure.toolkit.lib.common.exception.AzureExecutionException; +import com.microsoft.azure.toolkit.lib.common.model.Region; +import com.microsoft.azure.toolkit.lib.legacy.appservice.DeploymentSlotSetting; +import com.microsoft.azure.toolkit.lib.legacy.function.configurations.RuntimeConfiguration; +import org.apache.commons.lang3.StringUtils; + +import java.util.Optional; + +public class ConfigParser { + private final AbstractFunctionMojo mojo; + + public ConfigParser(AbstractFunctionMojo mojo) { + this.mojo = mojo; + } + + public FunctionAppConfig parseConfig() throws AzureExecutionException { + return (FunctionAppConfig) new FunctionAppConfig() + .disableAppInsights(mojo.isDisableAppInsights()) + .appInsightsKey(mojo.getAppInsightsKey()) + .appInsightsInstance(mojo.getAppInsightsInstance()) + .subscriptionId(mojo.getSubscriptionId()) + .resourceGroup(mojo.getResourceGroup()) + .appName(mojo.getAppName()) + .servicePlanName(mojo.getAppServicePlanName()) + .servicePlanResourceGroup(mojo.getAppServicePlanResourceGroup()) + .deploymentSlotName(getDeploymentSlotName()) + .deploymentSlotConfigurationSource(getDeploymentSlotConfigurationSource()) + .pricingTier(getParsedPricingTier()) + .region(getParsedRegion()) + .runtime(getRuntimeConfig()) + .appSettings(mojo.getAppSettings()); + } + + public RuntimeConfig getRuntimeConfig() throws AzureExecutionException { + final RuntimeConfiguration runtime = mojo.getRuntimeConfiguration(); + if (runtime == null) { + return null; + } + final OperatingSystem os = Optional.ofNullable(runtime.getOs()).map(OperatingSystem::fromString).orElse(null); + final JavaVersion javaVersion = Optional.ofNullable(runtime.getJavaVersion()).map(JavaVersion::fromString).orElse(null); + final RuntimeConfig result = new RuntimeConfig().os(os).javaVersion(javaVersion).webContainer(WebContainer.JAVA_OFF) + .image(runtime.getImage()).registryUrl(runtime.getRegistryUrl()); + if (StringUtils.isNotEmpty(runtime.getServerId())) { + final MavenDockerCredentialProvider credentialProvider = MavenDockerCredentialProvider.fromMavenSettings(mojo.getSettings(), runtime.getServerId()); + result.username(credentialProvider.getUsername()).password(credentialProvider.getPassword()); + } + return result; + } + + private String getDeploymentSlotName() { + return Optional.ofNullable(mojo.getDeploymentSlotSetting()).map(DeploymentSlotSetting::getName).orElse(null); + } + + private String getDeploymentSlotConfigurationSource() { + return Optional.ofNullable(mojo.getDeploymentSlotSetting()).map(DeploymentSlotSetting::getConfigurationSource).orElse(null); + } + + private Region getParsedRegion() { + return Optional.ofNullable(mojo.getRegion()).map(Region::fromName).orElse(null); + } + + private PricingTier getParsedPricingTier() { + return Optional.ofNullable(mojo.getPricingTier()).map(PricingTier::fromString).orElse(null); + } +} diff --git a/azure-functions-maven-plugin/src/main/java/com/microsoft/azure/maven/function/DeployMojo.java b/azure-functions-maven-plugin/src/main/java/com/microsoft/azure/maven/function/DeployMojo.java index fb5d8a2948..cfc66c6b8b 100644 --- a/azure-functions-maven-plugin/src/main/java/com/microsoft/azure/maven/function/DeployMojo.java +++ b/azure-functions-maven-plugin/src/main/java/com/microsoft/azure/maven/function/DeployMojo.java @@ -5,128 +5,54 @@ package com.microsoft.azure.maven.function; -import com.azure.core.management.AzureEnvironment; -import com.azure.core.management.exception.ManagementException; -import com.microsoft.azure.functions.annotation.AuthorizationLevel; -import com.microsoft.azure.maven.MavenDockerCredentialProvider; import com.microsoft.azure.toolkit.lib.Azure; -import com.microsoft.azure.toolkit.lib.applicationinsights.ApplicationInsights; -import com.microsoft.azure.toolkit.lib.applicationinsights.ApplicationInsightsEntity; import com.microsoft.azure.toolkit.lib.appservice.AzureAppService; -import com.microsoft.azure.toolkit.lib.appservice.entity.AppServiceBaseEntity; -import com.microsoft.azure.toolkit.lib.appservice.entity.FunctionEntity; -import com.microsoft.azure.toolkit.lib.appservice.model.DockerConfiguration; +import com.microsoft.azure.toolkit.lib.appservice.config.AppServiceConfig; +import com.microsoft.azure.toolkit.lib.appservice.config.FunctionAppConfig; +import com.microsoft.azure.toolkit.lib.appservice.config.RuntimeConfig; import com.microsoft.azure.toolkit.lib.appservice.model.FunctionDeployType; import com.microsoft.azure.toolkit.lib.appservice.model.JavaVersion; import com.microsoft.azure.toolkit.lib.appservice.model.OperatingSystem; import com.microsoft.azure.toolkit.lib.appservice.model.PricingTier; -import com.microsoft.azure.toolkit.lib.appservice.model.Runtime; -import com.microsoft.azure.toolkit.lib.appservice.model.WebContainer; -import com.microsoft.azure.toolkit.lib.appservice.service.IAppServicePlan; -import com.microsoft.azure.toolkit.lib.appservice.service.IAppServiceUpdater; import com.microsoft.azure.toolkit.lib.appservice.service.IFunctionApp; import com.microsoft.azure.toolkit.lib.appservice.service.IFunctionAppBase; -import com.microsoft.azure.toolkit.lib.appservice.service.IFunctionAppDeploymentSlot; -import com.microsoft.azure.toolkit.lib.auth.AzureAccount; +import com.microsoft.azure.toolkit.lib.appservice.task.CreateOrUpdateFunctionAppTask; +import com.microsoft.azure.toolkit.lib.appservice.task.DeployFunctionAppTask; +import com.microsoft.azure.toolkit.lib.appservice.utils.AppServiceConfigUtils; import com.microsoft.azure.toolkit.lib.common.bundle.AzureString; import com.microsoft.azure.toolkit.lib.common.exception.AzureExecutionException; import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException; import com.microsoft.azure.toolkit.lib.common.messager.AzureMessager; import com.microsoft.azure.toolkit.lib.common.model.Region; -import com.microsoft.azure.toolkit.lib.common.model.ResourceGroup; +import com.microsoft.azure.toolkit.lib.common.operation.AzureOperation; import com.microsoft.azure.toolkit.lib.common.utils.Utils; -import com.microsoft.azure.toolkit.lib.legacy.appservice.DeploymentSlotSetting; -import com.microsoft.azure.toolkit.lib.resource.AzureGroup; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.maven.artifact.versioning.ComparableVersion; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; -import org.zeroturnaround.zip.ZipUtil; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; -import reactor.util.retry.Retry; import java.io.File; -import java.time.Duration; import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Objects; import java.util.Optional; -import java.util.Properties; -import java.util.stream.Collectors; + +import static com.microsoft.azure.toolkit.lib.appservice.utils.AppServiceConfigUtils.fromAppService; +import static com.microsoft.azure.toolkit.lib.appservice.utils.AppServiceConfigUtils.mergeAppServiceConfig; /** * Deploy artifacts to target Azure Functions in Azure. If target Azure Functions doesn't exist, it will be created. */ @Mojo(name = "deploy", defaultPhase = LifecyclePhase.DEPLOY) public class DeployMojo extends AbstractFunctionMojo { - - private static final String DEPLOY_START = "Starting deployment..."; - private static final String DEPLOY_FINISH = - "Deployment done, you may access your resource through %s"; - private static final String FUNCTION_SLOT_CREATE_START = "The specified function slot does not exist. " + - "Creating a new slot..."; - private static final String FUNCTION_SLOT_CREATED = "Successfully created the function slot: %s."; - private static final String FUNCTION_SLOT_UPDATE = "Updating the specified function slot..."; - private static final String FUNCTION_SLOT_UPDATE_DONE = "Successfully updated the function slot: %s."; - private static final String APPINSIGHTS_INSTRUMENTATION_KEY = "APPINSIGHTS_INSTRUMENTATIONKEY"; private static final String APPLICATION_INSIGHTS_CONFIGURATION_CONFLICT = "Contradictory configurations for application insights," + " specify 'appInsightsKey' or 'appInsightsInstance' if you want to enable it, and specify " + "'disableAppInsights=true' if you want to disable it."; - private static final String FAILED_TO_GET_APPLICATION_INSIGHTS = "The application insights %s cannot be found, " + - "will create it in resource group %s."; - private static final String SKIP_CREATING_APPLICATION_INSIGHTS = "Skip creating application insights"; - private static final String APPLICATION_INSIGHTS_CREATE_START = "Creating application insights..."; - private static final String APPLICATION_INSIGHTS_CREATED = "Successfully created the application insights %s " + - "for this Function App. You can visit %s/#@/resource%s/overview to view your " + - "Application Insights component."; - private static final String APPLICATION_INSIGHTS_CREATE_FAILED = "Unable to create the Application Insights " + - "for the Function App due to error %s. Please use the Azure Portal to manually create and configure the " + - "Application Insights if needed."; - private static final String INSTRUMENTATION_KEY_IS_NOT_VALID = "Instrumentation key is not valid, " + - "please update the application insights configuration"; - private static final String UNABLE_TO_LIST_NONE_ANONYMOUS_HTTP_TRIGGERS = "Some http trigger urls cannot be displayed " + - "because they are non-anonymous. To access the non-anonymous triggers, please refer to https://aka.ms/azure-functions-key."; - private static final String HTTP_TRIGGER_URLS = "HTTP Trigger Urls:"; - private static final String NO_ANONYMOUS_HTTP_TRIGGER = "No anonymous HTTP Triggers found in deployed function app, skip list triggers."; - private static final String AUTH_LEVEL = "authLevel"; - private static final String HTTP_TRIGGER = "httpTrigger"; private static final String ARTIFACT_INCOMPATIBLE_WARNING = "Your function app artifact compile version {0} may not compatible with java version {1} in " + "configuration."; private static final String ARTIFACT_INCOMPATIBLE_ERROR = "Your function app artifact compile version {0} is not compatible with java version {1} in " + "configuration, please downgrade the project compile version and try again."; - private static final String FUNCTIONS_WORKER_RUNTIME_NAME = "FUNCTIONS_WORKER_RUNTIME"; - private static final String FUNCTIONS_WORKER_RUNTIME_VALUE = "java"; - private static final String SET_FUNCTIONS_WORKER_RUNTIME = "Set function worker runtime to java."; - private static final String CUSTOMIZED_FUNCTIONS_WORKER_RUNTIME_WARNING = "App setting `FUNCTIONS_WORKER_RUNTIME` doesn't " + - "meet the requirement of Azure Java Functions, the value should be `java`."; - private static final String FUNCTIONS_EXTENSION_VERSION_NAME = "FUNCTIONS_EXTENSION_VERSION"; - private static final String FUNCTIONS_EXTENSION_VERSION_VALUE = "~3"; - private static final String SET_FUNCTIONS_EXTENSION_VERSION = "Functions extension version " + - "isn't configured, setting up the default value."; - private static final String RUNNING = "Running"; - private static final String CREATE_FUNCTION_APP = "Creating function app %s..."; - private static final String CREATE_FUNCTION_APP_DONE = "Successfully created function app %s."; - private static final String CREATE_APP_SERVICE_PLAN = "Creating app service plan..."; - private static final String CREATE_APP_SERVICE_DONE = "Successfully created app service plan %s."; - private static final String CREATE_RESOURCE_GROUP = "Creating resource group %s in region %s..."; - private static final String CREATE_RESOURCE_GROUP_DONE = "Successfully created resource group %s."; - private static final String CREATE_NEW_FUNCTION_APP = "isCreateNewFunctionApp"; - private static final String CREATE_NEW_APP_SERVICE_PLAN = "createNewAppServicePlan"; - private static final String CREATE_NEW_RESOURCE_GROUP = "createNewResourceGroup"; - private static final String UPDATE_FUNCTION_APP = "Updating target Function App %s..."; - private static final String UPDATE_FUNCTION_DONE = "Successfully updated Function App %s."; private static final String NO_ARTIFACT_FOUNDED = "Failed to find function artifact '%s.jar' in folder '%s', please re-package the project and try again."; - private static final String LOCAL_SETTINGS_FILE = "local.settings.json"; - private static final int LIST_TRIGGERS_MAX_RETRY = 5; - private static final int LIST_TRIGGERS_RETRY_PERIOD_IN_SECONDS = 10; - private static final String SYNCING_TRIGGERS = "Syncing triggers and fetching function information"; - private static final String SYNCING_TRIGGERS_WITH_RETRY = "Syncing triggers and fetching function information (Attempt {0}/{1})..."; - private static final String NO_TRIGGERS_FOUNDED = "No triggers found in deployed function app, " + - "please try recompile the project by `mvn clean package` and deploy again."; private static final String APP_NAME_PATTERN = "[a-zA-Z0-9\\-]{2,60}"; private static final String RESOURCE_GROUP_PATTERN = "[a-zA-Z0-9._\\-()]{1,90}"; private static final String SLOT_NAME_PATTERN = "[A-Za-z0-9-]{1,60}"; @@ -143,29 +69,21 @@ public class DeployMojo extends AbstractFunctionMojo { private static final String INVALID_SLOT_NAME = "Invalid value of inside in pom.xml, it needs to match the pattern '%s'"; private static final String EMPTY_IMAGE_NAME = "Please config the of in pom.xml."; private static final String INVALID_OS = "The value of is not correct, supported values are: windows, linux and docker."; - private static final String FAILED_TO_LIST_TRIGGERS = "Deployment succeeded, but failed to list http trigger urls."; - private static final String SKIP_DEPLOYMENT_FOR_DOCKER_APP_SERVICE = "Skip deployment for docker app service"; private static final String EXPANDABLE_PRICING_TIER_WARNING = "'%s' may not be a valid pricing tier, " + "please refer to https://aka.ms/maven_function_configuration#supported-pricing-tiers for valid values"; private static final String EXPANDABLE_REGION_WARNING = "'%s' may not be a valid region, " + "please refer to https://aka.ms/maven_function_configuration#supported-regions for valid values"; private static final String EXPANDABLE_JAVA_VERSION_WARNING = "'%s' may not be a valid java version, recommended values are `Java 8` and `Java 11`"; - private AzureAppService az; - @Override + @AzureOperation(name = "functionapp|mojo.deploy", type = AzureOperation.Type.ACTION) protected void doExecute() throws AzureExecutionException { doValidate(); - processAppSettingsWithDefaultValue(); - - az = getOrCreateAzureAppServiceClient(); - final IFunctionAppBase target = createOrUpdateResource(); + getOrCreateAzureAppServiceClient(); + final IFunctionAppBase target = createOrUpdateResource(getParser().parseConfig()); deployArtifact(target); - - if (target instanceof IFunctionApp) { - listHTTPTriggerUrls((IFunctionApp) target); - } + updateTelemetryProperties(); } protected void doValidate() throws AzureExecutionException { @@ -228,256 +146,55 @@ protected void validateParameters() { } } - protected IFunctionAppBase createOrUpdateResource() throws AzureExecutionException { - final String deploymentSlotName = Optional.ofNullable(deploymentSlotSetting) - .map(DeploymentSlotSetting::getName).orElse(null); - final IFunctionApp functionApp = az.functionApp(getResourceGroup(), getAppName()); - if (StringUtils.isEmpty(deploymentSlotName)) { - return functionApp.exists() ? updateFunctionApp(functionApp) : createFunctionApp(functionApp); - } else { - final IFunctionAppDeploymentSlot slot = functionApp.deploymentSlot(deploymentSlotName); - return slot.exists() ? updateDeploymentSlot(slot) : createDeploymentSlot(slot); - } - } - - protected IFunctionApp createFunctionApp(final IFunctionApp functionApp) throws AzureExecutionException { - getTelemetryProxy().addDefaultProperty(CREATE_NEW_FUNCTION_APP, String.valueOf(true)); - final ResourceGroup resourceGroup = getOrCreateResourceGroup(); - final IAppServicePlan appServicePlan = getOrCreateAppServicePlan(); - AzureMessager.getMessager().info(String.format(CREATE_FUNCTION_APP, getAppName())); - final Runtime runtime = getRuntimeOrDefault(); - final Map appSettings = getAppSettings(); - // get/create ai instances only if user didn't specify ai connection string in app settings - bindApplicationInsights(appSettings, true); - final IFunctionApp result = functionApp.create().withName(getAppName()) - .withResourceGroup(resourceGroup.getName()) - .withPlan(appServicePlan.id()) - .withRuntime(runtime) - .withDockerConfiguration(getDockerConfiguration()) - .withAppSettings(appSettings) - .commit(); - AzureMessager.getMessager().info(String.format(CREATE_FUNCTION_APP_DONE, result.name())); - return result; - } - - private IAppServicePlan getOrCreateAppServicePlan() { - final String servicePlanName = StringUtils.isEmpty(getAppServicePlanName()) ? - String.format("asp-%s", getAppName()) : getAppServicePlanName(); - final String servicePlanGroup = getServicePlanResourceGroup(); - final IAppServicePlan appServicePlan = az.appServicePlan(servicePlanGroup, servicePlanName); - if (!appServicePlan.exists()) { - AzureMessager.getMessager().info(CREATE_APP_SERVICE_PLAN); - getTelemetryProxy().addDefaultProperty(CREATE_NEW_APP_SERVICE_PLAN, String.valueOf(true)); - appServicePlan.create() - .withName(servicePlanName) - .withResourceGroup(servicePlanGroup) - .withRegion(getParsedRegion()) - .withPricingTier(getParsedPricingTier()) - .withOperatingSystem(getRuntimeOrDefault().getOperatingSystem()) - .commit(); - AzureMessager.getMessager().info(String.format(CREATE_APP_SERVICE_DONE, appServicePlan.name())); - } - return appServicePlan; - } - - private Region getParsedRegion() { - return Optional.ofNullable(region).map(Region::fromName).orElse(Region.US_WEST); - } - - private PricingTier getParsedPricingTier() { - if (StringUtils.isEmpty(pricingTier)) { - return PricingTier.CONSUMPTION; - } - return Optional.ofNullable(PricingTier.fromString(pricingTier)) - .orElseThrow(() -> new AzureToolkitRuntimeException(String.format("Invalid pricing tier %s", pricingTier))); - } - - private ResourceGroup getOrCreateResourceGroup() { - try { - return Azure.az(AzureGroup.class).getByName(getResourceGroup()); - } catch (ManagementException e) { - AzureMessager.getMessager().info(String.format(CREATE_RESOURCE_GROUP, getResourceGroup(), getRegion())); - getTelemetryProxy().addDefaultProperty(CREATE_NEW_RESOURCE_GROUP, String.valueOf(true)); - final ResourceGroup result = Azure.az(AzureGroup.class).create(getResourceGroup(), getRegion()); - AzureMessager.getMessager().info(String.format(CREATE_RESOURCE_GROUP_DONE, result.getName())); - return result; + protected IFunctionAppBase createOrUpdateResource(final FunctionAppConfig config) { + IFunctionApp app = Azure.az(AzureAppService.class).functionApp(config.resourceGroup(), config.appName()); + final boolean newFunctionApp = !app.exists(); + AppServiceConfig defaultConfig = !newFunctionApp ? fromAppService(app, app.plan()) : buildDefaultConfig(config.subscriptionId(), + config.resourceGroup(), config.appName()); + mergeAppServiceConfig(config, defaultConfig); + if (!newFunctionApp && !config.disableAppInsights() && StringUtils.isEmpty(config.appInsightsKey())) { + // fill ai key from existing app settings + config.appInsightsKey(app.entity().getAppSettings().get(CreateOrUpdateFunctionAppTask.APPINSIGHTS_INSTRUMENTATION_KEY)); } + return new CreateOrUpdateFunctionAppTask(config).execute(); } - private Runtime getRuntimeOrDefault() { - final OperatingSystem os = Optional.ofNullable(runtime.getOs()).map(OperatingSystem::fromString).orElse(OperatingSystem.WINDOWS); - final JavaVersion javaVersion = Optional.ofNullable(runtime.getJavaVersion()).map(JavaVersion::fromString).orElse(JavaVersion.JAVA_8); - return Runtime.getRuntime(os, WebContainer.JAVA_OFF, javaVersion); - } - - private Runtime getRuntime() { - if (StringUtils.isEmpty(runtime.getOs()) && StringUtils.isEmpty(runtime.getJavaVersion())) { - return null; - } - final OperatingSystem os = OperatingSystem.fromString(runtime.getOs()); - final JavaVersion javaVersion = JavaVersion.fromString(runtime.getJavaVersion()); - return Runtime.getRuntime(os, WebContainer.JAVA_OFF, javaVersion); - } - - private DockerConfiguration getDockerConfiguration() throws AzureExecutionException { - final OperatingSystem os = Optional.ofNullable(runtime.getOs()).map(OperatingSystem::fromString).orElse(null); - if (os != OperatingSystem.DOCKER) { - return null; - } - final MavenDockerCredentialProvider credentialProvider = MavenDockerCredentialProvider.fromMavenSettings(getSettings(), runtime.getServerId()); - return DockerConfiguration.builder() - .registryUrl(runtime.getRegistryUrl()) - .image(runtime.getImage()) - .userName(credentialProvider.getUsername()) - .password(credentialProvider.getPassword()).build(); - } - - protected IFunctionApp updateFunctionApp(final IFunctionApp functionApp) throws AzureExecutionException { - // update app service plan - AzureMessager.getMessager().info(String.format(UPDATE_FUNCTION_APP, functionApp.name())); - final IAppServicePlan currentPlan = functionApp.plan(); - IAppServicePlan targetServicePlan = StringUtils.isEmpty(appServicePlanName) ? currentPlan : - az.appServicePlan(getServicePlanResourceGroup(), appServicePlanName); - if (!targetServicePlan.exists()) { - targetServicePlan = getOrCreateAppServicePlan(); - } else { - if (region != null && !Objects.equals(Region.fromName(region), Region.fromName(targetServicePlan.entity().getRegion()))) { - AzureMessager.getMessager().warning(String.format("Skip region update for existing service plan '%s' since it is not allowed.", - targetServicePlan.name())); - } - if (StringUtils.isNotEmpty(pricingTier)) { - targetServicePlan.update().withPricingTier(getParsedPricingTier()).commit(); + private AppServiceConfig buildDefaultConfig(String subscriptionId, String resourceGroup, String appName) { + ComparableVersion javaVersionForProject = null; + final String outputFileName = project.getBuild().getFinalName() + "." + project.getPackaging(); + File outputFile = new File(project.getBuild().getDirectory(), outputFileName); + if (outputFile.exists() && StringUtils.equalsIgnoreCase("jar", FilenameUtils.getExtension(outputFile.getName()))) { + try { + javaVersionForProject = new ComparableVersion(Utils.getArtifactCompileVersion(outputFile)); + } catch (Exception e) { + // it is acceptable that java version from jar file cannot be retrieved } } - // update app settings - final Map appSettings = getAppSettings(); - final IAppServiceUpdater update = functionApp.update(); - if (isDisableAppInsights()) { - update.withoutAppSettings(APPINSIGHTS_INSTRUMENTATION_KEY); - } else { - bindApplicationInsights(appSettings, false); - } - final IFunctionApp result = update.withPlan(targetServicePlan.id()) - .withRuntime(getRuntime()) - .withDockerConfiguration(getDockerConfiguration()) - .withAppSettings(appSettings) - .commit(); - AzureMessager.getMessager().info(String.format(UPDATE_FUNCTION_DONE, functionApp.name())); - return result; - } - - private String getServicePlanResourceGroup() { - return StringUtils.isEmpty(getAppServicePlanResourceGroup()) ? getResourceGroup() : getAppServicePlanResourceGroup(); - } - - protected IFunctionAppDeploymentSlot createDeploymentSlot(final IFunctionAppDeploymentSlot deploymentSlot) - throws AzureExecutionException { - AzureMessager.getMessager().info(FUNCTION_SLOT_CREATE_START); - final DeploymentSlotSetting slotSetting = getDeploymentSlotSetting(); - final Map appSettings = getAppSettings(); - bindApplicationInsights(appSettings, false); - final IFunctionAppDeploymentSlot result = deploymentSlot.create().withAppSettings(appSettings) - .withConfigurationSource(slotSetting.getConfigurationSource()) - .withName(slotSetting.getName()).commit(); - AzureMessager.getMessager().info(String.format(FUNCTION_SLOT_CREATED, result.name())); - return result; - } - - protected IFunctionAppDeploymentSlot updateDeploymentSlot(final IFunctionAppDeploymentSlot deploymentSlot) throws AzureExecutionException { - AzureMessager.getMessager().info(FUNCTION_SLOT_UPDATE); - final Map appSettings = getAppSettings(); - final IFunctionAppDeploymentSlot.Updater update = deploymentSlot.update(); - // todo: remove duplicate codes with update function - if (isDisableAppInsights()) { - update.withoutAppSettings(APPINSIGHTS_INSTRUMENTATION_KEY); - } else { - bindApplicationInsights(appSettings, false); - } - final IFunctionAppDeploymentSlot result = update.withAppSettings(appSettings).commit(); - AzureMessager.getMessager().info(String.format(FUNCTION_SLOT_UPDATE_DONE, result.name())); - return deploymentSlot; - } - - private void deployArtifact(IFunctionAppBase target) throws AzureExecutionException { - if (target.getRuntime().isDocker()) { - AzureMessager.getMessager().info(SKIP_DEPLOYMENT_FOR_DOCKER_APP_SERVICE); - return; - } - AzureMessager.getMessager().info(DEPLOY_START); - final FunctionDeployType deployType = StringUtils.isEmpty(deploymentType) ? null : FunctionDeployType.fromString(deploymentType); - // For ftp deploy, we need to upload entire staging directory not the zipped package - final File file = deployType == FunctionDeployType.FTP ? new File(getDeploymentStagingDirectoryPath()) : packageStagingDirectory(); - final RunnableWithException deployRunnable = deployType == null ? () -> target.deploy(file) : () -> target.deploy(file, deployType); - executeWithTimeRecorder(deployRunnable, DEPLOY); - // todo: check function status after deployment - if (!StringUtils.equalsIgnoreCase(target.state(), RUNNING)) { - target.start(); - } - AzureMessager.getMessager().info(String.format(DEPLOY_FINISH, getResourcePortalUrl(target.id()))); - } - - private File packageStagingDirectory() { - final File zipFile = new File(getDeploymentStagingDirectoryPath() + ".zip"); - final File stagingDirectory = new File(getDeploymentStagingDirectoryPath()); - - ZipUtil.pack(stagingDirectory, zipFile); - ZipUtil.removeEntry(zipFile, LOCAL_SETTINGS_FILE); - return zipFile; - } - /** - * List anonymous HTTP Triggers url after deployment - */ - protected void listHTTPTriggerUrls(IFunctionApp target) { - try { - final List triggers = listFunctions(target); - final List httpFunction = triggers.stream() - .filter(function -> function.getTrigger() != null && - StringUtils.equalsIgnoreCase(function.getTrigger().getType(), HTTP_TRIGGER)) - .collect(Collectors.toList()); - final List anonymousTriggers = httpFunction.stream() - .filter(bindingResource -> bindingResource.getTrigger() != null && - StringUtils.equalsIgnoreCase(bindingResource.getTrigger().getProperty(AUTH_LEVEL), AuthorizationLevel.ANONYMOUS.toString())) - .collect(Collectors.toList()); - if (CollectionUtils.isEmpty(httpFunction) || CollectionUtils.isEmpty(anonymousTriggers)) { - AzureMessager.getMessager().info(NO_ANONYMOUS_HTTP_TRIGGER); - return; - } - AzureMessager.getMessager().info(HTTP_TRIGGER_URLS); - anonymousTriggers.forEach(trigger -> AzureMessager.getMessager().info(String.format("\t %s : %s", trigger.getName(), trigger.getTriggerUrl()))); - if (anonymousTriggers.size() < httpFunction.size()) { - AzureMessager.getMessager().info(UNABLE_TO_LIST_NONE_ANONYMOUS_HTTP_TRIGGERS); - } - } catch (RuntimeException e) { - // show warning instead of exception for list triggers - AzureMessager.getMessager().warning(FAILED_TO_LIST_TRIGGERS); - } + javaVersionForProject = ObjectUtils.firstNonNull(javaVersionForProject, new ComparableVersion(System.getProperty("java.version"))); + // get java version according to project java version + JavaVersion javaVersion = javaVersionForProject.compareTo(new ComparableVersion("9")) < 0 ? JavaVersion.JAVA_8 : JavaVersion.JAVA_11; + return AppServiceConfigUtils.buildDefaultFunctionConfig(subscriptionId, resourceGroup, appName, javaVersion); } - private List listFunctions(final IFunctionApp functionApp) { - final int[] count = {0}; - return Mono.fromCallable(() -> { - final AzureString message = count[0]++ == 0 ? - AzureString.fromString(SYNCING_TRIGGERS) : AzureString.format(SYNCING_TRIGGERS_WITH_RETRY, count[0], LIST_TRIGGERS_MAX_RETRY); - AzureMessager.getMessager().info(message); - return Optional.ofNullable(functionApp.listFunctions(true)) - .filter(CollectionUtils::isNotEmpty) - .orElseThrow(() -> new AzureToolkitRuntimeException(NO_TRIGGERS_FOUNDED)); - }).subscribeOn(Schedulers.boundedElastic()) - .retryWhen(Retry.fixedDelay(LIST_TRIGGERS_MAX_RETRY - 1, Duration.ofSeconds(LIST_TRIGGERS_RETRY_PERIOD_IN_SECONDS))).block(); + private void deployArtifact(final IFunctionAppBase target) { + final File file = new File(getDeploymentStagingDirectoryPath()); + final FunctionDeployType type = StringUtils.isEmpty(deploymentType) ? null : FunctionDeployType.fromString(deploymentType); + new DeployFunctionAppTask(target, file, type).execute(); } protected void validateArtifactCompileVersion() throws AzureExecutionException { - final Runtime runtime = getRuntimeOrDefault(); - if (runtime.isDocker()) { + final RuntimeConfig runtimeConfig = getParser().getRuntimeConfig(); + if (runtimeConfig.os() == OperatingSystem.DOCKER) { return; } - final ComparableVersion runtimeVersion = new ComparableVersion(runtime.getJavaVersion().getValue()); + final JavaVersion javaVersion = Optional.ofNullable(runtimeConfig.javaVersion()).orElse(CreateOrUpdateFunctionAppTask.DEFAULT_FUNCTION_JAVA_VERSION); + final ComparableVersion runtimeVersion = new ComparableVersion(javaVersion.getValue()); final ComparableVersion artifactVersion = new ComparableVersion(Utils.getArtifactCompileVersion(getArtifactToDeploy())); if (runtimeVersion.compareTo(artifactVersion) >= 0) { return; } - if (runtime.getJavaVersion().isExpandedValue()) { + if (javaVersion.isExpandedValue()) { AzureMessager.getMessager().warning(AzureString.format(ARTIFACT_INCOMPATIBLE_WARNING, artifactVersion.toString(), runtimeVersion.toString())); } else { final String errorMessage = AzureString.format(ARTIFACT_INCOMPATIBLE_ERROR, artifactVersion.toString(), runtimeVersion.toString()).toString(); @@ -485,30 +202,6 @@ protected void validateArtifactCompileVersion() throws AzureExecutionException { } } - public void processAppSettingsWithDefaultValue() { - if (appSettings == null) { - appSettings = new Properties(); - } - setDefaultAppSetting(appSettings, FUNCTIONS_WORKER_RUNTIME_NAME, SET_FUNCTIONS_WORKER_RUNTIME, - FUNCTIONS_WORKER_RUNTIME_VALUE, CUSTOMIZED_FUNCTIONS_WORKER_RUNTIME_WARNING); - setDefaultAppSetting(appSettings, FUNCTIONS_EXTENSION_VERSION_NAME, SET_FUNCTIONS_EXTENSION_VERSION, - FUNCTIONS_EXTENSION_VERSION_VALUE, null); - } - - private void setDefaultAppSetting(Map result, String settingName, String settingIsEmptyMessage, - String defaultValue, String warningMessage) { - final String setting = (String) result.get(settingName); - if (StringUtils.isEmpty(setting)) { - AzureMessager.getMessager().info(settingIsEmptyMessage); - result.put(settingName, defaultValue); - return; - } - // Show warning message when user set a different value - if (!StringUtils.equalsIgnoreCase(setting, defaultValue) && StringUtils.isNotEmpty(warningMessage)) { - AzureMessager.getMessager().warning(warningMessage); - } - } - private File getArtifactToDeploy() throws AzureExecutionException { final File stagingFolder = new File(getDeploymentStagingDirectoryPath()); return Arrays.stream(Optional.ofNullable(stagingFolder.listFiles()).orElse(new File[0])) @@ -517,73 +210,9 @@ private File getArtifactToDeploy() throws AzureExecutionException { .orElseThrow(() -> new AzureExecutionException(String.format(NO_ARTIFACT_FOUNDED, this.getFinalName(), stagingFolder))); } - /** - * Binding Function App with Application Insights - * Will follow the below sequence appInsightsKey -> appInsightsInstance -> Create New AI Instance (Function creation only) - * - * @param appSettings App settings map - * @param isCreation Define the stage of function app, as we only create ai instance by default when create new function apps - * @throws AzureExecutionException When there are conflicts in configuration or meet errors while finding/creating application insights instance - */ - private void bindApplicationInsights(Map appSettings, boolean isCreation) throws AzureExecutionException { - // Skip app insights creation when user specify ai connection string in app settings - if (appSettings.containsKey(APPINSIGHTS_INSTRUMENTATION_KEY)) { - return; - } - final String instrumentationKey; - if (StringUtils.isNotEmpty(getAppInsightsKey())) { - instrumentationKey = getAppInsightsKey(); - if (!Utils.isGUID(instrumentationKey)) { - throw new AzureExecutionException(INSTRUMENTATION_KEY_IS_NOT_VALID); - } - } else { - final ApplicationInsightsEntity applicationInsightsComponent = getOrCreateApplicationInsights(isCreation); - instrumentationKey = applicationInsightsComponent == null ? null : applicationInsightsComponent.getInstrumentationKey(); - } - if (StringUtils.isNotEmpty(instrumentationKey)) { - appSettings.put(APPINSIGHTS_INSTRUMENTATION_KEY, instrumentationKey); - } - } - private void validateApplicationInsightsConfiguration() throws AzureExecutionException { if (isDisableAppInsights() && (StringUtils.isNotEmpty(getAppInsightsKey()) || StringUtils.isNotEmpty(getAppInsightsInstance()))) { throw new AzureExecutionException(APPLICATION_INSIGHTS_CONFIGURATION_CONFLICT); } } - - private ApplicationInsightsEntity getOrCreateApplicationInsights(boolean enableCreation) { - return StringUtils.isNotEmpty(getAppInsightsInstance()) ? getApplicationInsights(getAppInsightsInstance()) : - enableCreation ? createApplicationInsights(getAppName()) : null; - } - - private ApplicationInsightsEntity getApplicationInsights(String appInsightsInstance) { - ApplicationInsightsEntity resource; - try { - resource = Azure.az(ApplicationInsights.class).get(getResourceGroup(), appInsightsInstance); - } catch (ManagementException e) { - resource = null; - } - if (resource == null) { - AzureMessager.getMessager().warning(String.format(FAILED_TO_GET_APPLICATION_INSIGHTS, appInsightsInstance, getResourceGroup())); - return createApplicationInsights(appInsightsInstance); - } - return resource; - } - - private ApplicationInsightsEntity createApplicationInsights(String name) { - if (isDisableAppInsights()) { - AzureMessager.getMessager().info(SKIP_CREATING_APPLICATION_INSIGHTS); - return null; - } - try { - AzureMessager.getMessager().info(APPLICATION_INSIGHTS_CREATE_START); - final AzureEnvironment environment = Azure.az(AzureAccount.class).account().getEnvironment(); - final ApplicationInsightsEntity resource = Azure.az(ApplicationInsights.class).create(getResourceGroup(), Region.fromName(getRegion()), name); - AzureMessager.getMessager().info(String.format(APPLICATION_INSIGHTS_CREATED, resource.getName(), getPortalUrl(environment), resource.getId())); - return resource; - } catch (Exception e) { - AzureMessager.getMessager().warning(String.format(APPLICATION_INSIGHTS_CREATE_FAILED, e.getMessage())); - return null; - } - } } diff --git a/azure-functions-maven-plugin/src/test/java/com/microsoft/azure/maven/function/DeployMojoTest.java b/azure-functions-maven-plugin/src/test/java/com/microsoft/azure/maven/function/DeployMojoTest.java index 82fb86a4e9..d27035cd66 100644 --- a/azure-functions-maven-plugin/src/test/java/com/microsoft/azure/maven/function/DeployMojoTest.java +++ b/azure-functions-maven-plugin/src/test/java/com/microsoft/azure/maven/function/DeployMojoTest.java @@ -5,17 +5,13 @@ package com.microsoft.azure.maven.function; -import com.microsoft.azure.toolkit.lib.common.exception.AzureExecutionException; -import com.microsoft.azure.toolkit.lib.legacy.appservice.DeploymentSlotSetting; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; @RunWith(MockitoJUnitRunner.class) @@ -38,16 +34,6 @@ public void getConfiguration() { assertEquals("westeurope", mojo.getRegion()); } - @Ignore - @Test(expected = AzureExecutionException.class) - public void testDeploymentSlotThrowExceptionIfFunctionNotExists() throws AzureExecutionException { - final DeploymentSlotSetting slotSetting = new DeploymentSlotSetting(); - slotSetting.setName("Exception"); - doReturn(slotSetting).when(mojoSpy).getDeploymentSlotSetting(); - doReturn(null).when(mojoSpy).getFunctionApp(); - mojoSpy.doExecute(); - } - private DeployMojo getMojoFromPom() throws Exception { final DeployMojo mojoFromPom = (DeployMojo) getMojoFromPom("/pom.xml", "deploy"); assertNotNull(mojoFromPom); diff --git a/azure-functions-maven-plugin/src/test/java/com/microsoft/azure/maven/function/invoker/storage/EventHubProcesser.java b/azure-functions-maven-plugin/src/test/java/com/microsoft/azure/maven/function/invoker/storage/EventHubProcesser.java deleted file mode 100644 index 85abe8f100..0000000000 --- a/azure-functions-maven-plugin/src/test/java/com/microsoft/azure/maven/function/invoker/storage/EventHubProcesser.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - */ - -package com.microsoft.azure.maven.function.invoker.storage; - -import com.google.gson.Gson; -import com.microsoft.azure.eventhubs.ConnectionStringBuilder; -import com.microsoft.azure.eventhubs.EventData; -import com.microsoft.azure.eventhubs.EventHubClient; -import com.microsoft.azure.eventhubs.EventPosition; -import com.microsoft.azure.eventhubs.PartitionReceiver; -import com.microsoft.azure.management.Azure; -import com.microsoft.azure.management.eventhub.EventHub; -import com.microsoft.azure.management.eventhub.EventHubNamespace; -import com.microsoft.azure.management.resources.ResourceGroup; -import com.microsoft.azure.management.resources.fluentcore.arm.Region; -import com.microsoft.azure.management.storage.StorageAccount; -import com.microsoft.azure.management.storage.StorageAccountSkuType; -import com.microsoft.azure.maven.function.invoker.CommonUtils; - -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; - -public class EventHubProcesser { - - private static final String SAS_KAY_NAME = "RootManageSharedAccessKey"; - - private EventHubNamespace eventHubNamespace; - private StorageAccount storageAccount; - private ResourceGroup resourceGroup; - - private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(8); - private Map eventHubClientMap = new HashMap<>(); - - public EventHubProcesser(String resourceGroupName, String namespaceName, String storageAccountName) - throws Exception { - final Azure azureClint = CommonUtils.getAzureClient(); - - if (azureClint.resourceGroups().contain(resourceGroupName)) { - resourceGroup = azureClint.resourceGroups().getByName(resourceGroupName); - } else { - resourceGroup = azureClint.resourceGroups().define(resourceGroupName).withRegion(Region.US_EAST).create(); - } - - final boolean isEventHubNamespaceExist = azureClint.eventHubNamespaces().list().stream() - .anyMatch(namespace -> namespace.name().equals(namespaceName) && - namespace.resourceGroupName().equals(resourceGroupName)); - if (isEventHubNamespaceExist) { - eventHubNamespace = azureClint.eventHubNamespaces().getByResourceGroup(resourceGroupName, namespaceName); - } else { - eventHubNamespace = azureClint.eventHubNamespaces() - .define(namespaceName).withRegion(resourceGroup.region()) - .withExistingResourceGroup(resourceGroupName).create(); - } - - storageAccount = azureClint.storageAccounts().getByResourceGroup(resourceGroupName, storageAccountName); - if (storageAccount == null) { - storageAccount = azureClint.storageAccounts() - .define(storageAccountName).withRegion(resourceGroup.region()) - .withExistingResourceGroup(resourceGroup) - .withSku(StorageAccountSkuType.STANDARD_LRS) - .withGeneralPurposeAccountKindV2().create(); - } - } - - public EventHub createOrGetEventHubByName(final String eventHubName) throws Exception { - final Azure azureClient = CommonUtils.getAzureClient(); - final Optional eventHub = eventHubNamespace.listEventHubs().stream() - .filter(eventHubEntry -> eventHubEntry.name().equals(eventHubName)).findFirst(); - return eventHub.isPresent() ? eventHub.get() : azureClient.eventHubs().define(eventHubName) - .withExistingNamespace(eventHubNamespace) - .withExistingStorageAccountForCapturedData(storageAccount, eventHubName) - .withDataCaptureEnabled().create(); - } - - public void sendMessageToEventHub(final String eventHubName, final String message) throws Exception { - final EventHubClient eventHubClient = getEventHubClientByName(eventHubName); - final Gson gson = new Gson(); - final byte[] payloadBytes = gson.toJson(message).getBytes(Charset.defaultCharset()); - final EventData sendEvent = EventData.create(payloadBytes); - eventHubClient.send(sendEvent).get(); - } - - public List getMessageFromEventHub(final String eventHubName) throws Exception { - final List result = new CopyOnWriteArrayList<>(); - final EventHubClient eventHubClient = getEventHubClientByName(eventHubName); - final List partitionIds = Arrays.asList(eventHubClient.getRuntimeInformation().get().getPartitionIds()); - partitionIds.parallelStream() - .forEach(partitionId -> result.addAll(getMessageFromPartition(eventHubClient, partitionId))); - return result; - } - - public List getMessageFromPartition(final EventHubClient eventHubClient, final String partitionId) { - final List result = new ArrayList<>(); - try { - final PartitionReceiver partitionReceiver = eventHubClient - .createReceiver(EventHubClient.DEFAULT_CONSUMER_GROUP_NAME, - partitionId, EventPosition.fromStartOfStream()).get(); - final Iterable data = partitionReceiver.receive(10).get(); - if (data != null) { - data.forEach(eventData -> result.add(new String(eventData.getBytes()))); - } - partitionReceiver.closeSync(); - } catch (Exception e) { - // When exception, just return empty List - e.printStackTrace(); - } - return result; - } - - public void close() throws Exception { - for (final EventHubClient eventHubClient : eventHubClientMap.values()) { - eventHubClient.closeSync(); - } - executorService.shutdown(); - } - - private EventHubClient getEventHubClientByName(final String eventHubName) throws Exception { - if (eventHubClientMap.containsKey(eventHubName)) { - return eventHubClientMap.get(eventHubName); - } else { - final ConnectionStringBuilder connStr = new ConnectionStringBuilder() - .setNamespaceName(eventHubNamespace.name()) - .setEventHubName(eventHubName) - .setSasKeyName(SAS_KAY_NAME) - .setSasKey(getEventHubKey()); - final EventHubClient eventHubClient = EventHubClient.createFromConnectionStringSync(connStr.toString(), executorService); - eventHubClientMap.put(eventHubName, eventHubClient); - return eventHubClient; - } - } - - private String getEventHubKey() { - return eventHubNamespace.listAuthorizationRules().get(0).getKeys().primaryKey(); - } - - public String getEventHubConnectionString() { - return eventHubNamespace.listAuthorizationRules().get(0).getKeys().primaryConnectionString(); - } -} diff --git a/azure-maven-plugin-lib/pom.xml b/azure-maven-plugin-lib/pom.xml index e3f98bb180..7b02fa4829 100644 --- a/azure-maven-plugin-lib/pom.xml +++ b/azure-maven-plugin-lib/pom.xml @@ -7,7 +7,7 @@ com.microsoft.azure azure-maven-plugins - 1.14.0-SNAPSHOT + 1.14.0 azure-maven-plugin-lib @@ -159,11 +159,6 @@ lombok provided
    - - com.microsoft.azure - azure - compile - com.github.java-json-tools json-schema-validator @@ -177,6 +172,15 @@ + + + src/main/resources + true + + schema/**/*.json + + + org.apache.maven.plugins diff --git a/azure-maven-plugin-lib/src/main/java/com/microsoft/azure/maven/AbstractAppServiceMojo.java b/azure-maven-plugin-lib/src/main/java/com/microsoft/azure/maven/AbstractAppServiceMojo.java index f89b1244af..01e8f1cccf 100644 --- a/azure-maven-plugin-lib/src/main/java/com/microsoft/azure/maven/AbstractAppServiceMojo.java +++ b/azure-maven-plugin-lib/src/main/java/com/microsoft/azure/maven/AbstractAppServiceMojo.java @@ -192,6 +192,7 @@ protected AzureAppService getOrCreateAzureAppServiceClient() { com.microsoft.azure.toolkit.lib.Azure.az(AzureAccount.class).account().selectSubscription(Collections.singletonList(targetSubscriptionId)); appServiceClient = Azure.az(AzureAppService.class).subscription(targetSubscriptionId); printCurrentSubscription(appServiceClient); + this.subscriptionId = targetSubscriptionId; } catch (AzureLoginException | AzureExecutionException | IOException e) { throw new AzureToolkitRuntimeException(String.format("Cannot authenticate due to error %s", e.getMessage()), e); } diff --git a/azure-maven-plugin-lib/src/main/java/com/microsoft/azure/maven/AbstractAzureMojo.java b/azure-maven-plugin-lib/src/main/java/com/microsoft/azure/maven/AbstractAzureMojo.java index 658f6041da..ea75e1fde2 100755 --- a/azure-maven-plugin-lib/src/main/java/com/microsoft/azure/maven/AbstractAzureMojo.java +++ b/azure-maven-plugin-lib/src/main/java/com/microsoft/azure/maven/AbstractAzureMojo.java @@ -34,6 +34,7 @@ import com.microsoft.azure.toolkit.lib.common.proxy.ProxyInfo; import com.microsoft.azure.toolkit.lib.common.proxy.ProxyManager; import com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemeter; +import com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemetry; import com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemetryClient; import com.microsoft.azure.toolkit.lib.common.utils.InstallationIdUtils; import com.microsoft.azure.toolkit.lib.common.utils.TextUtils; @@ -221,9 +222,6 @@ public abstract class AbstractAzureMojo extends AbstractMojo { @JsonIgnore private Account azureAccount; - @JsonIgnore - private com.microsoft.azure.management.Azure azure; - @Getter @JsonIgnore protected AzureTelemetryClient telemetryProxy; @@ -440,16 +438,6 @@ protected void initTelemetryProxy() { } //endregion - protected static void printCurrentSubscription(com.microsoft.azure.management.Azure azure) { - if (azure == null) { - return; - } - final com.microsoft.azure.management.resources.Subscription subscription = azure.getCurrentSubscription(); - if (subscription != null) { - Log.info(String.format(SUBSCRIPTION_TEMPLATE, TextUtils.cyan(subscription.displayName()), TextUtils.cyan(subscription.subscriptionId()))); - } - } - public Map getTelemetryProperties() { final Map map = new HashMap<>(); map.put(INSTALLATION_ID_KEY, getInstallationId()); @@ -691,4 +679,8 @@ protected static void checkSubscription(List subscriptions, String } } + protected void updateTelemetryProperties() { + Optional.ofNullable(AzureTelemetry.getActionContext().getProperties()).ifPresent(properties -> + properties.forEach((key, value) -> telemetryProxy.addDefaultProperty(key, value))); + } } diff --git a/azure-maven-plugin-lib/src/main/java/com/microsoft/azure/maven/auth/AzureClientFactory.java b/azure-maven-plugin-lib/src/main/java/com/microsoft/azure/maven/auth/AzureClientFactory.java deleted file mode 100644 index 887ea469c5..0000000000 --- a/azure-maven-plugin-lib/src/main/java/com/microsoft/azure/maven/auth/AzureClientFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - */ - -package com.microsoft.azure.maven.auth; - -import com.microsoft.azure.management.Azure; -import com.microsoft.azure.management.Azure.Authenticated; -import com.microsoft.azure.toolkit.lib.auth.Account; -import com.microsoft.azure.toolkit.lib.auth.AzureAccount; -import com.microsoft.azure.toolkit.lib.auth.exception.AzureLoginException; -import org.apache.commons.lang3.StringUtils; - -import java.io.IOException; - -public class AzureClientFactory { - public static Azure getAzureClient(String userAgent, String defaultSubscriptionId) throws IOException, AzureLoginException { - final Account account = com.microsoft.azure.toolkit.lib.Azure.az(AzureAccount.class).account(); - final Authenticated authenticated = Azure.configure().withUserAgent(userAgent) - .authenticate(account.getTokenCredentialV1(defaultSubscriptionId)); - - return StringUtils.isEmpty(defaultSubscriptionId) ? authenticated.withDefaultSubscription() : - authenticated.withSubscription(defaultSubscriptionId); - } -} diff --git a/azure-maven-plugin-lib/src/main/resources/schema/maven/AzureAppServiceMavenPlugin.json b/azure-maven-plugin-lib/src/main/resources/schema/maven/AzureAppServiceMavenPlugin.json new file mode 100644 index 0000000000..e9040f0886 --- /dev/null +++ b/azure-maven-plugin-lib/src/main/resources/schema/maven/AzureAppServiceMavenPlugin.json @@ -0,0 +1,148 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Configuration", + "description": "Configuration for Maven plugin for Azure Web App", + "properties": { + "subscriptionId": { + "$ref": "classpath:///schema/common/UUID.json" + }, + "resourceGroup": { + "$ref": "classpath:///schema/common/ResourceGroupName.json" + }, + "appName": { + "$ref": "classpath:///schema/appservice/AppServiceName.json" + }, + "appServicePlanName": { + "$ref": "classpath:///schema/appservice/AppServicePlanName.json" + }, + "appServicePlanResourceGroup": { + "$ref": "classpath:///schema/common/ResourceGroupName.json" + }, + "auth": { + "$ref": "#/definitions/auth" + }, + "deploymentSlot": { + "$ref": "#/definitions/deployment-slot" + }, + "appSettings": { + "type": "object" + }, + "allowTelemetry": { + "type": "boolean", + "default": true + }, + "failsOnError": { + "type": "boolean", + "default": true + }, + "authType": { + "$ref": "classpath:///schema/common/AuthConfiguration.json#/definitions/auth-type", + "deprecationMessage": "Please set auth related properties like type in " + } + }, + "required": [ + "appName", + "resourceGroup" + ], + "definitions": { + "deployment-slot": { + "title": "DeploymentSlotConfiguration", + "description": "Deployment slot configuration for Maven plugin for Azure Web App", + "type": "object", + "properties": { + "name": { + "$ref": "classpath:///schema/appservice/DeploymentSlotName.json" + }, + "configurationSource": { + "$ref": "classpath:///schema/common/NonEmptyString.json" + } + }, + "required": [ + "name" + ] + }, + "auth": { + "title": "AuthConfiguration", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "The auth config for accessing azure resources", + "type": "object", + "properties": { + "type": { + "$ref": "classpath:///schema/common/AuthConfiguration.json#/definitions/auth-type" + }, + "client": { + "$ref": "classpath:///schema/common/UUID.json" + }, + "tenant": { + "$ref": "classpath:///schema/common/UUID.json" + }, + "serverId": { + "$ref": "classpath:///schema/common/NonEmptyString.json" + }, + "key": { + "description": "Password", + "type": "string" + }, + "certificate": { + "description": "The absolute path of your certificate", + "type": "string" + }, + "certificatePassword": { + "description": "The password for your certificate, if there is any", + "type": "string" + }, + "environment": { + "$ref": "classpath:///schema/common/AzureEnvironment.json" + } + }, + "allOf": [ + { + "if": { + "properties": { + "type": { + "pattern": "(?i)^service_principal$" + } + }, + "required": [ + "type" + ] + }, + "then": { + "anyOf": [ + { + "required": [ + "client", + "tenant", + "key" + ] + }, + { + "required": [ + "client", + "tenant", + "certificate" + ] + }, + { + "required": [ + "serverId" + ] + } + ] + } + } + ], + "not": { + "required": [ + "key", + "certificate" + ] + }, + "dependencies": { + "certificatePassword": [ + "certificate" + ] + } + } + } +} diff --git a/azure-sfmesh-maven-plugin/pom.xml b/azure-sfmesh-maven-plugin/pom.xml index dc3bbf31dc..2c0aab652d 100644 --- a/azure-sfmesh-maven-plugin/pom.xml +++ b/azure-sfmesh-maven-plugin/pom.xml @@ -8,7 +8,7 @@ com.microsoft.azure azure-maven-plugins - 1.14.0-SNAPSHOT + 1.14.0 azure-sfmesh-maven-plugin diff --git a/azure-spring-cloud-maven-plugin/pom.xml b/azure-spring-cloud-maven-plugin/pom.xml index 7ee6ebe140..c46a2221e0 100644 --- a/azure-spring-cloud-maven-plugin/pom.xml +++ b/azure-spring-cloud-maven-plugin/pom.xml @@ -12,7 +12,7 @@ com.microsoft.azure azure-maven-plugins - 1.14.0-SNAPSHOT + 1.14.0 Azure Spring Cloud Maven Plugin @@ -275,6 +275,56 @@ + + + + com.nickwongdev + aspectj-maven-plugin + + false + 1.8 + 1.8 + ignore + 1.8 + UTF-8 + false + true + true + + + + com.microsoft.azure + azure-toolkit-common-lib + + + + + + compile-with-aspectj + process-classes + + + ${project.build.directory}/classes + + + + compile + + + + test-compile-with-aspectj + process-test-classes + + + ${project.build.directory}/test-classes + + + + test-compile + + + + diff --git a/azure-spring-cloud-maven-plugin/src/test/resources/maven/projects/parent-project/core/pom.xml b/azure-spring-cloud-maven-plugin/src/test/resources/maven/projects/parent-project/core/pom.xml index bd07da9cb2..ca70e228b1 100644 --- a/azure-spring-cloud-maven-plugin/src/test/resources/maven/projects/parent-project/core/pom.xml +++ b/azure-spring-cloud-maven-plugin/src/test/resources/maven/projects/parent-project/core/pom.xml @@ -25,7 +25,7 @@ junit junit - 4.11 + 4.13.1 test diff --git a/azure-spring-cloud-maven-plugin/src/test/resources/maven/projects/parent-project/service/pom.xml b/azure-spring-cloud-maven-plugin/src/test/resources/maven/projects/parent-project/service/pom.xml index b56e0f8b18..629561377c 100644 --- a/azure-spring-cloud-maven-plugin/src/test/resources/maven/projects/parent-project/service/pom.xml +++ b/azure-spring-cloud-maven-plugin/src/test/resources/maven/projects/parent-project/service/pom.xml @@ -25,7 +25,7 @@ junit junit - 4.11 + 4.13.1 test diff --git a/azure-toolkit-libs/azure-toolkit-applicationinsights-lib/pom.xml b/azure-toolkit-libs/azure-toolkit-applicationinsights-lib/pom.xml index 69339e6f74..36df7025a3 100644 --- a/azure-toolkit-libs/azure-toolkit-applicationinsights-lib/pom.xml +++ b/azure-toolkit-libs/azure-toolkit-applicationinsights-lib/pom.xml @@ -5,7 +5,7 @@ azure-toolkit-libs com.microsoft.azure - 0.11.0 + 0.12.1 4.0.0 diff --git a/azure-toolkit-libs/azure-toolkit-applicationinsights-lib/src/main/java/com/microsoft/azure/toolkit/lib/applicationinsights/ApplicationInsights.java b/azure-toolkit-libs/azure-toolkit-applicationinsights-lib/src/main/java/com/microsoft/azure/toolkit/lib/applicationinsights/ApplicationInsights.java index b70ae743cb..ffd66b7c81 100644 --- a/azure-toolkit-libs/azure-toolkit-applicationinsights-lib/src/main/java/com/microsoft/azure/toolkit/lib/applicationinsights/ApplicationInsights.java +++ b/azure-toolkit-libs/azure-toolkit-applicationinsights-lib/src/main/java/com/microsoft/azure/toolkit/lib/applicationinsights/ApplicationInsights.java @@ -1,3 +1,7 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package com.microsoft.azure.toolkit.lib.applicationinsights; import com.azure.core.http.policy.HttpLogDetailLevel; @@ -5,10 +9,10 @@ import com.azure.core.http.policy.HttpPipelinePolicy; import com.azure.core.management.exception.ManagementException; import com.azure.core.management.profile.AzureProfile; -import com.azure.resourcemanager.AzureResourceManager; import com.azure.resourcemanager.applicationinsights.ApplicationInsightsManager; import com.azure.resourcemanager.applicationinsights.models.ApplicationInsightsComponent; import com.azure.resourcemanager.applicationinsights.models.ApplicationType; +import com.azure.resourcemanager.resources.ResourceManager; import com.azure.resourcemanager.resources.fluentcore.arm.ResourceId; import com.azure.resourcemanager.resources.fluentcore.policy.ProviderRegistrationPolicy; import com.azure.resourcemanager.resources.models.Providers; @@ -94,7 +98,7 @@ public void delete(@Nonnull String subscriptionId, @Nonnull String resourceGroup getApplicationInsightsManager(subscriptionId).components().deleteByResourceGroup(resourceGroup, name); } - @Cacheable(cacheName = "ApplicationInsightsManager", key = "$subscriptionId") + @Cacheable(cacheName = "applicationinsights/{}/manager", key = "$subscriptionId") private ApplicationInsightsManager getApplicationInsightsManager(String subscriptionId) { final Account account = Azure.az(AzureAccount.class).account(); final String tenantId = account.getSubscription(subscriptionId).getTenantId(); @@ -104,12 +108,14 @@ private ApplicationInsightsManager getApplicationInsightsManager(String subscrip logOptions.setLogLevel(Optional.ofNullable(config.getLogLevel()).map(HttpLogDetailLevel::valueOf).orElse(HttpLogDetailLevel.NONE)); final AzureProfile azureProfile = new AzureProfile(tenantId, subscriptionId, account.getEnvironment()); // todo: migrate resource provider related codes to common library - final Providers providers = AzureResourceManager.configure() + final Providers providers = ResourceManager.configure() + .withHttpClient(AzureService.getDefaultHttpClient()) .withPolicy(getUserAgentPolicy(userAgent)) .authenticate(account.getTokenCredential(subscriptionId), azureProfile) .withSubscription(subscriptionId).providers(); return ApplicationInsightsManager .configure() + .withHttpClient(AzureService.getDefaultHttpClient()) .withLogOptions(logOptions) .withPolicy(getUserAgentPolicy(userAgent)) .withPolicy(new ProviderRegistrationPolicy(providers)) // add policy to auto register resource providers diff --git a/azure-toolkit-libs/azure-toolkit-applicationinsights-lib/src/main/java/com/microsoft/azure/toolkit/lib/applicationinsights/task/GetOrCreateApplicationInsightsTask.java b/azure-toolkit-libs/azure-toolkit-applicationinsights-lib/src/main/java/com/microsoft/azure/toolkit/lib/applicationinsights/task/GetOrCreateApplicationInsightsTask.java new file mode 100644 index 0000000000..022f3a1a0d --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-applicationinsights-lib/src/main/java/com/microsoft/azure/toolkit/lib/applicationinsights/task/GetOrCreateApplicationInsightsTask.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.applicationinsights.task; + +import com.azure.core.management.AzureEnvironment; +import com.azure.core.management.exception.ManagementException; +import com.microsoft.azure.toolkit.lib.Azure; +import com.microsoft.azure.toolkit.lib.applicationinsights.ApplicationInsights; +import com.microsoft.azure.toolkit.lib.applicationinsights.ApplicationInsightsEntity; +import com.microsoft.azure.toolkit.lib.auth.AzureAccount; +import com.microsoft.azure.toolkit.lib.common.messager.AzureMessager; +import com.microsoft.azure.toolkit.lib.common.model.Region; +import com.microsoft.azure.toolkit.lib.common.task.AzureTask; + +import javax.annotation.Nonnull; + +import static com.microsoft.azure.toolkit.lib.auth.util.AzureEnvironmentUtils.getPortalUrl; + +public class GetOrCreateApplicationInsightsTask extends AzureTask { + private static final String APPLICATION_INSIGHTS_CREATE_START = "Creating application insights..."; + private static final String APPLICATION_INSIGHTS_CREATED = "Successfully created the application insights %s " + + "for this Function App. You can visit %s/#@/resource%s/overview to view your " + + "Application Insights component."; + + private final String subscriptionId; + private final String resourceGroup; + private final String name; + private final Region region; + + public GetOrCreateApplicationInsightsTask(@Nonnull String subscriptionId, @Nonnull String resourceGroup, @Nonnull Region region, @Nonnull String name) { + this.subscriptionId = subscriptionId; + this.resourceGroup = resourceGroup; + this.name = name; + this.region = region; + } + + @Override + public ApplicationInsightsEntity execute() { + final ApplicationInsights az = Azure.az(ApplicationInsights.class).subscription(subscriptionId); + try { + return az.get(resourceGroup, name); + } catch (ManagementException e) { + if (e.getResponse().getStatusCode() != 404) { + throw e; + } + } + AzureMessager.getMessager().info(APPLICATION_INSIGHTS_CREATE_START); + final AzureEnvironment environment = Azure.az(AzureAccount.class).account().getEnvironment(); + final ApplicationInsightsEntity resource = Azure.az(ApplicationInsights.class).create(resourceGroup, region, name); + AzureMessager.getMessager().info(String.format(APPLICATION_INSIGHTS_CREATED, resource.getName(), getPortalUrl(environment), resource.getId())); + return resource; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/pom.xml b/azure-toolkit-libs/azure-toolkit-appservice-lib/pom.xml index 858b57c9f0..e493eba1f0 100644 --- a/azure-toolkit-libs/azure-toolkit-appservice-lib/pom.xml +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/pom.xml @@ -5,7 +5,7 @@ azure-toolkit-libs com.microsoft.azure - 0.11.0 + 0.12.1 4.0.0 @@ -35,7 +35,12 @@ com.azure.resourcemanager - azure-resourcemanager + azure-resourcemanager-appservice + + + + com.microsoft.azure + azure-storage @@ -78,17 +83,6 @@ commons-codec commons-codec - - com.google.errorprone - error_prone_core - ${error.prone.core.version} - - - com.google.guava - guava - - - org.zeroturnaround zt-zip @@ -154,12 +148,11 @@ com.microsoft.azure - azure - compile + azure-toolkit-common-lib com.microsoft.azure - azure-toolkit-common-lib + azure-toolkit-applicationinsights-lib diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/AzureAppService.java b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/AzureAppService.java index 05082f2587..6135169a54 100644 --- a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/AzureAppService.java +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/AzureAppService.java @@ -7,7 +7,9 @@ import com.azure.core.http.policy.HttpLogDetailLevel; import com.azure.core.http.policy.HttpPipelinePolicy; import com.azure.core.management.profile.AzureProfile; -import com.azure.resourcemanager.AzureResourceManager; +import com.azure.resourcemanager.appservice.AppServiceManager; +import com.azure.resourcemanager.appservice.fluent.models.ResourceNameAvailabilityInner; +import com.azure.resourcemanager.appservice.models.CheckNameResourceTypes; import com.microsoft.azure.toolkit.lib.Azure; import com.microsoft.azure.toolkit.lib.AzureConfiguration; import com.microsoft.azure.toolkit.lib.AzureService; @@ -31,6 +33,7 @@ import com.microsoft.azure.toolkit.lib.auth.AzureAccount; import com.microsoft.azure.toolkit.lib.common.cache.Cacheable; import com.microsoft.azure.toolkit.lib.common.cache.Preload; +import com.microsoft.azure.toolkit.lib.common.entity.CheckNameAvailabilityResultEntity; import com.microsoft.azure.toolkit.lib.common.model.Subscription; import com.microsoft.azure.toolkit.lib.common.operation.AzureOperation; import org.apache.commons.lang3.StringUtils; @@ -51,20 +54,20 @@ private AzureAppService(@Nonnull final List subscriptions) { super(AzureAppService::new, subscriptions); } - @Cacheable(cacheName = "appservcie/functionapp/{}", key = "$id") + @Cacheable(cacheName = "appservice/functionapp/{}", key = "$id") @AzureOperation(name = "functionapp.get.id", params = {"id"}, type = AzureOperation.Type.SERVICE) public IFunctionApp functionApp(String id) { - return new FunctionApp(id, getAzureResourceManager(Utils.getSubscriptionId(id))); + return new FunctionApp(id, getAppServiceManager(Utils.getSubscriptionId(id))); } public IFunctionApp functionApp(String resourceGroup, String name) { return functionApp(getDefaultSubscription().getId(), resourceGroup, name); } - @Cacheable(cacheName = "appservcie/{}/rg/{}/functionapp/{}", key = "$sid/$rg/$name") + @Cacheable(cacheName = "appservice/{}/rg/{}/functionapp/{}", key = "$sid/$rg/$name") @AzureOperation(name = "functionapp.get.name|rg|sid", params = {"name", "rg", "sid"}, type = AzureOperation.Type.SERVICE) public IFunctionApp functionApp(String sid, String rg, String name) { - return new FunctionApp(sid, rg, name, getAzureResourceManager(sid)); + return new FunctionApp(sid, rg, name, getAppServiceManager(sid)); } public IFunctionApp functionApp(FunctionAppEntity entity) { @@ -80,10 +83,10 @@ public List functionApps(boolean... force) { .collect(Collectors.toList()); } - @Cacheable(cacheName = "appservcie/{}/functionapps", key = "$sid", condition = "!(force&&force[0])") + @Cacheable(cacheName = "appservice/{}/functionapps", key = "$sid", condition = "!(force&&force[0])") @AzureOperation(name = "functionapp.list.subscription", params = "sid", type = AzureOperation.Type.SERVICE) private List functionApps(String sid, boolean... force) { - final AzureResourceManager azureResourceManager = getAzureResourceManager(sid); + final AppServiceManager azureResourceManager = getAppServiceManager(sid); return azureResourceManager .functionApps().list().stream().parallel() .filter(functionAppBasic -> StringUtils.containsIgnoreCase(functionAppBasic.innerModel().kind(), "functionapp")) // Filter out function apps @@ -91,20 +94,20 @@ private List functionApps(String sid, boolean... force) { .collect(Collectors.toList()); } - @Cacheable(cacheName = "appservcie/webapp/{}", key = "$id") + @Cacheable(cacheName = "appservice/webapp/{}", key = "$id") @AzureOperation(name = "webapp.get.id", params = "id", type = AzureOperation.Type.SERVICE) public IWebApp webapp(String id) { - return new WebApp(id, getAzureResourceManager(Utils.getSubscriptionId(id))); + return new WebApp(id, getAppServiceManager(Utils.getSubscriptionId(id))); } public IWebApp webapp(String resourceGroup, String name) { return webapp(getDefaultSubscription().getId(), resourceGroup, name); } - @Cacheable(cacheName = "appservcie/{}/rg/{}/webapp/{}", key = "$sid/$rg/$name") + @Cacheable(cacheName = "appservice/{}/rg/{}/webapp/{}", key = "$sid/$rg/$name") @AzureOperation(name = "webapp.get.name|rg|sid", params = {"name", "rg", "sid"}, type = AzureOperation.Type.SERVICE) public IWebApp webapp(String sid, String rg, String name) { - return new WebApp(sid, rg, name, getAzureResourceManager(sid)); + return new WebApp(sid, rg, name, getAppServiceManager(sid)); } public IWebApp webapp(WebAppEntity webAppEntity) { @@ -120,38 +123,46 @@ public List webapps(boolean... force) { .collect(Collectors.toList()); } - @Cacheable(cacheName = "appservcie/{}/webapps", key = "$sid", condition = "!(force&&force[0])") + @AzureOperation(name = "appservice.check_name", params = "name", type = AzureOperation.Type.SERVICE) + public CheckNameAvailabilityResultEntity checkNameAvailability(String subscriptionId, String name) { + final AppServiceManager azureResourceManager = getAppServiceManager(subscriptionId); + final ResourceNameAvailabilityInner result = azureResourceManager.webApps().manager() + .serviceClient().getResourceProviders().checkNameAvailability(name, CheckNameResourceTypes.MICROSOFT_WEB_SITES); + return new CheckNameAvailabilityResultEntity(result.nameAvailable(), result.reason().toString(), result.message()); + } + + @Cacheable(cacheName = "appservice/{}/webapps", key = "$sid", condition = "!(force&&force[0])") @AzureOperation(name = "webapp.list.subscription", params = "sid", type = AzureOperation.Type.SERVICE) private List webapps(String sid, boolean... force) { - final AzureResourceManager azureResourceManager = getAzureResourceManager(sid); + final AppServiceManager azureResourceManager = getAppServiceManager(sid); return azureResourceManager.webApps().list().stream().parallel() .filter(webAppBasic -> !StringUtils.containsIgnoreCase(webAppBasic.innerModel().kind(), "functionapp")) // Filter out function apps .map(webAppBasic -> new WebApp(webAppBasic, azureResourceManager)) .collect(Collectors.toList()); } - public @Nonnull + @Nonnull @AzureOperation(name = "webapp|runtime.list.os|version", params = {"os.getValue()", "version.getValue()"}, type = AzureOperation.Type.SERVICE) - List listWebAppRuntimes(@Nonnull OperatingSystem os, @Nonnull JavaVersion version) { + public List listWebAppRuntimes(@Nonnull OperatingSystem os, @Nonnull JavaVersion version) { return Runtime.WEBAPP_RUNTIME.stream() .filter(runtime -> Objects.equals(os, runtime.getOperatingSystem()) && Objects.equals(version, runtime.getJavaVersion())) .collect(Collectors.toList()); } - @Cacheable(cacheName = "appservcie/plan/{}", key = "$id") + @Cacheable(cacheName = "appservice/plan/{}", key = "$id") @AzureOperation(name = "appservice|plan.get.id", params = "id", type = AzureOperation.Type.SERVICE) public IAppServicePlan appServicePlan(String id) { - return new AppServicePlan(id, getAzureResourceManager(Utils.getSubscriptionId(id))); + return new AppServicePlan(id, getAppServiceManager(Utils.getSubscriptionId(id))); } public IAppServicePlan appServicePlan(String resourceGroup, String name) { return appServicePlan(getDefaultSubscription().getId(), resourceGroup, name); } - @Cacheable(cacheName = "appservcie/{}/rg/{}/plan/{}", key = "$sid/$rg/$name") + @Cacheable(cacheName = "appservice/{}/rg/{}/plan/{}", key = "$sid/$rg/$name") @AzureOperation(name = "appservice|plan.get.name|rg|sid", params = {"name", "rg", "sid"}, type = AzureOperation.Type.SERVICE) public IAppServicePlan appServicePlan(String sid, String rg, String name) { - return new AppServicePlan(sid, rg, name, getAzureResourceManager(sid)); + return new AppServicePlan(sid, rg, name, getAppServiceManager(sid)); } public IAppServicePlan appServicePlan(AppServicePlanEntity entity) { @@ -167,46 +178,46 @@ public List appServicePlans(boolean... force) { .collect(Collectors.toList()); } - @Cacheable(cacheName = "appservcie/{}/plans", key = "$sid", condition = "!(force&&force[0])") + @Cacheable(cacheName = "appservice/{}/plans", key = "$sid", condition = "!(force&&force[0])") @AzureOperation(name = "appservice|plan.list.subscription", params = "sid", type = AzureOperation.Type.SERVICE) public List appServicePlans(String sid, boolean... force) { - final AzureResourceManager azureResourceManager = getAzureResourceManager(sid); + final AppServiceManager azureResourceManager = getAppServiceManager(sid); return azureResourceManager.appServicePlans().list().stream().parallel() .map(appServicePlan -> new AppServicePlan(appServicePlan, azureResourceManager)) .collect(Collectors.toList()); } - @Cacheable(cacheName = "appservcie/rg/{}/plans", key = "$rg", condition = "!(force&&force[0])") + @Cacheable(cacheName = "appservice/rg/{}/plans", key = "$rg", condition = "!(force&&force[0])") @AzureOperation(name = "appservice|plan.list.rg", params = "rg", type = AzureOperation.Type.SERVICE) public List appServicePlansByResourceGroup(String rg, boolean... force) { return getSubscriptions().stream().parallel() - .map(subscription -> getAzureResourceManager(subscription.getId())) + .map(subscription -> getAppServiceManager(subscription.getId())) .flatMap(azureResourceManager -> azureResourceManager.appServicePlans().listByResourceGroup(rg).stream() .map(appServicePlan -> new AppServicePlan(appServicePlan, azureResourceManager))) .collect(Collectors.toList()); } @Deprecated - @Cacheable(cacheName = "appservcie/slot/{}", key = "$id") + @Cacheable(cacheName = "appservice/slot/{}", key = "$id") @AzureOperation(name = "appservice|deployment.get.id", params = "id", type = AzureOperation.Type.SERVICE) public IWebAppDeploymentSlot deploymentSlot(String id) { - return new WebAppDeploymentSlot(id, getAzureResourceManager(Utils.getSubscriptionId(id))); + return new WebAppDeploymentSlot(id, getAppServiceManager(Utils.getSubscriptionId(id))); } // todo: share codes with other library which leverage track2 mgmt sdk @Cacheable(cacheName = "appservice/{}/manager", key = "$sid") @AzureOperation(name = "appservice.get_client.subscription", params = "sid", type = AzureOperation.Type.SERVICE) - public AzureResourceManager getAzureResourceManager(String sid) { + public AppServiceManager getAppServiceManager(String sid) { final Account account = Azure.az(AzureAccount.class).account(); final AzureConfiguration config = Azure.az().config(); final String userAgent = config.getUserAgent(); final HttpLogDetailLevel logLevel = Optional.ofNullable(config.getLogLevel()).map(HttpLogDetailLevel::valueOf).orElse(HttpLogDetailLevel.NONE); - final AzureProfile azureProfile = new AzureProfile(account.getEnvironment()); - return AzureResourceManager.configure() + final AzureProfile azureProfile = new AzureProfile(null, sid, account.getEnvironment()); + return AppServiceManager.configure() + .withHttpClient(AzureService.getDefaultHttpClient()) .withLogLevel(logLevel) .withPolicy(getUserAgentPolicy(userAgent)) // set user agent with policy - .authenticate(account.getTokenCredential(sid), azureProfile) - .withSubscription(sid); + .authenticate(account.getTokenCredential(sid), azureProfile); } private HttpPipelinePolicy getUserAgentPolicy(String userAgent) { @@ -216,4 +227,8 @@ private HttpPipelinePolicy getUserAgentPolicy(String userAgent) { return httpPipelineNextPolicy.process(); }; } + + public String name() { + return "Microsoft.Web/sites"; + } } diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/config/AppServiceConfig.java b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/config/AppServiceConfig.java index 84b02d7244..6a677558b4 100644 --- a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/config/AppServiceConfig.java +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/config/AppServiceConfig.java @@ -5,12 +5,17 @@ package com.microsoft.azure.toolkit.lib.appservice.config; +import com.microsoft.azure.toolkit.lib.appservice.model.JavaVersion; +import com.microsoft.azure.toolkit.lib.appservice.model.OperatingSystem; import com.microsoft.azure.toolkit.lib.appservice.model.PricingTier; +import com.microsoft.azure.toolkit.lib.appservice.model.WebContainer; import com.microsoft.azure.toolkit.lib.common.model.Region; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; +import org.apache.commons.lang3.StringUtils; +import javax.annotation.Nonnull; import java.util.Map; @Getter @@ -46,7 +51,37 @@ public AppServicePlanConfig getServicePlanConfig() { .servicePlanResourceGroup(servicePlanResourceGroup()) .servicePlanName(servicePlanName()) .region(region()) - .os(runtime() == null ? null : runtime().os()) + .os(runtime().os()) .pricingTier(pricingTier()); } + + public static AppServiceConfig buildDefaultWebAppConfig(String resourceGroup, String appName, String packaging, JavaVersion javaVersion) { + RuntimeConfig runtimeConfig = new RuntimeConfig().os(OperatingSystem.LINUX).webContainer(StringUtils.equalsIgnoreCase(packaging, "war") ? + WebContainer.TOMCAT_85 : (StringUtils.equalsIgnoreCase(packaging, "ear") ? WebContainer.JBOSS_7 : WebContainer.JAVA_SE)) + .javaVersion(javaVersion); + AppServiceConfig appServiceConfig = buildDefaultAppServiceConfig(resourceGroup, appName); + appServiceConfig.runtime(runtimeConfig); + return appServiceConfig; + } + + public static AppServiceConfig buildDefaultFunctionConfig(String resourceGroup, String appName, JavaVersion javaVersion) { + RuntimeConfig runtimeConfig = new RuntimeConfig().os(OperatingSystem.WINDOWS).webContainer(WebContainer.JAVA_OFF) + .javaVersion(javaVersion); + AppServiceConfig appServiceConfig = buildDefaultAppServiceConfig(resourceGroup, appName); + appServiceConfig.runtime(runtimeConfig); + appServiceConfig.pricingTier(PricingTier.CONSUMPTION); + return appServiceConfig; + } + + @Nonnull + private static AppServiceConfig buildDefaultAppServiceConfig(String resourceGroup, String appName) { + AppServiceConfig appServiceConfig = new AppServiceConfig(); + appServiceConfig.region(Region.US_CENTRAL); + + appServiceConfig.resourceGroup(resourceGroup); + appServiceConfig.appName(appName); + appServiceConfig.servicePlanResourceGroup(resourceGroup); + appServiceConfig.servicePlanName(String.format("asp-%s", appName)); + return appServiceConfig; + } } diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/config/AppServicePlanConfig.java b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/config/AppServicePlanConfig.java index d3e46fb807..6e22a9c013 100644 --- a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/config/AppServicePlanConfig.java +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/config/AppServicePlanConfig.java @@ -5,6 +5,7 @@ package com.microsoft.azure.toolkit.lib.appservice.config; +import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.microsoft.azure.toolkit.lib.appservice.model.OperatingSystem; import com.microsoft.azure.toolkit.lib.appservice.model.PricingTier; import com.microsoft.azure.toolkit.lib.common.model.Region; @@ -15,6 +16,7 @@ @Getter @Setter @Accessors(fluent = true) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) public class AppServicePlanConfig { private String subscriptionId; diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/config/FunctionAppConfig.java b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/config/FunctionAppConfig.java new file mode 100644 index 0000000000..7490414180 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/config/FunctionAppConfig.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ +package com.microsoft.azure.toolkit.lib.appservice.config; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +@Getter +@Setter +@Accessors(fluent = true) +public class FunctionAppConfig extends AppServiceConfig { + private String appInsightsInstance; + private String appInsightsKey; + private boolean disableAppInsights; +} diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/model/OperatingSystem.java b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/model/OperatingSystem.java index c905e4a2d2..675b3bdcad 100644 --- a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/model/OperatingSystem.java +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/model/OperatingSystem.java @@ -5,11 +5,10 @@ package com.microsoft.azure.toolkit.lib.appservice.model; +import com.fasterxml.jackson.annotation.JsonAutoDetect; import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; @@ -17,6 +16,7 @@ @Getter @NoArgsConstructor @AllArgsConstructor +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) public enum OperatingSystem { WINDOWS("windows"), LINUX("linux"), diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/model/PricingTier.java b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/model/PricingTier.java index 5fcff7c3f3..085ee2b10d 100644 --- a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/model/PricingTier.java +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/model/PricingTier.java @@ -5,6 +5,7 @@ package com.microsoft.azure.toolkit.lib.appservice.model; +import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.google.common.collect.Sets; import com.microsoft.azure.toolkit.lib.common.model.ExpandableParameter; import lombok.AllArgsConstructor; @@ -24,6 +25,7 @@ @Setter @NoArgsConstructor @AllArgsConstructor +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) public class PricingTier implements ExpandableParameter { public static final PricingTier BASIC_B1 = new PricingTier("Basic", "B1"); public static final PricingTier BASIC_B2 = new PricingTier("Basic", "B2"); diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/model/Runtime.java b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/model/Runtime.java index 029a253e27..4e6086003f 100644 --- a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/model/Runtime.java +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/model/Runtime.java @@ -45,7 +45,6 @@ public class Runtime { public static final Runtime LINUX_JAVA8_TOMCAT85 = new Runtime(OperatingSystem.LINUX, WebContainer.TOMCAT_85, JavaVersion.JAVA_8); public static final Runtime LINUX_JAVA8_JBOSS7 = new Runtime(OperatingSystem.LINUX, WebContainer.JBOSS_7, JavaVersion.JAVA_8); public static final Runtime LINUX_JAVA11_JBOSS7 = new Runtime(OperatingSystem.LINUX, WebContainer.JBOSS_7, JavaVersion.JAVA_11); - public static final Runtime LINUX_JAVA8_JBOSS72 = new Runtime(OperatingSystem.LINUX, WebContainer.JBOSS_72, JavaVersion.JAVA_8); public static final Runtime LINUX_JAVA11_TOMCAT9 = new Runtime(OperatingSystem.LINUX, WebContainer.TOMCAT_9, JavaVersion.JAVA_11); public static final Runtime LINUX_JAVA11_TOMCAT85 = new Runtime(OperatingSystem.LINUX, WebContainer.TOMCAT_85, JavaVersion.JAVA_11); // Function @@ -58,7 +57,7 @@ public class Runtime { public static final List WEBAPP_RUNTIME = Collections.unmodifiableList(Arrays.asList(WINDOWS_JAVA8, WINDOWS_JAVA11, WINDOWS_JAVA8_TOMCAT9, WINDOWS_JAVA8_TOMCAT85, WINDOWS_JAVA11_TOMCAT9, WINDOWS_JAVA11_TOMCAT85, LINUX_JAVA8, LINUX_JAVA11, LINUX_JAVA8_TOMCAT9, LINUX_JAVA8_TOMCAT85, - LINUX_JAVA8_JBOSS72, LINUX_JAVA11_JBOSS7, LINUX_JAVA8_JBOSS7, LINUX_JAVA11_TOMCAT9, LINUX_JAVA11_TOMCAT85)); + LINUX_JAVA11_JBOSS7, LINUX_JAVA8_JBOSS7, LINUX_JAVA11_TOMCAT9, LINUX_JAVA11_TOMCAT85)); public static final List FUNCTION_APP_RUNTIME = Collections.unmodifiableList(Arrays.asList(FUNCTION_LINUX_JAVA8, FUNCTION_LINUX_JAVA11, FUNCTION_WINDOWS_JAVA8, FUNCTION_WINDOWS_JAVA11)); private static final List values = diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/model/WebContainer.java b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/model/WebContainer.java index fa64371ad1..b4352ea250 100644 --- a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/model/WebContainer.java +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/model/WebContainer.java @@ -35,7 +35,6 @@ public class WebContainer implements ExpandableParameter { public static final WebContainer TOMCAT_85 = new WebContainer("tomcat 8.5"); public static final WebContainer TOMCAT_9 = new WebContainer("tomcat 9.0"); public static final WebContainer JBOSS_7 = new WebContainer("JBOSSEAP 7"); - public static final WebContainer JBOSS_72 = new WebContainer("JBOSSEAP 7.2"); public static final WebContainer TOMCAT_7_0_50 = new WebContainer("tomcat 7.0.50"); public static final WebContainer TOMCAT_7_0_62 = new WebContainer("tomcat 7.0.62"); @@ -56,7 +55,7 @@ public class WebContainer implements ExpandableParameter { private static final Set values = Collections.unmodifiableSet(Sets.newHashSet(TOMCAT_7, TOMCAT_7_0_50, TOMCAT_7_0_62, TOMCAT_8, TOMCAT_8_0_23, TOMCAT_85, TOMCAT_8_5_6, TOMCAT_8_5_20, TOMCAT_8_5_31, TOMCAT_8_5_34, TOMCAT_8_5_37, TOMCAT_9, TOMCAT_9_0_0, TOMCAT_9_0_8, - TOMCAT_9_0_12, TOMCAT_9_0_14, JETTY_9_1_NEWEST, JETTY_9_1_V20131115, JETTY_9_3_NEWEST, JETTY_9_3_V20161014, JAVA_SE, JBOSS_72, JBOSS_7)); + TOMCAT_9_0_12, TOMCAT_9_0_14, JETTY_9_1_NEWEST, JETTY_9_1_V20131115, JETTY_9_3_NEWEST, JETTY_9_3_V20161014, JAVA_SE, JBOSS_7)); private String value; diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/service/impl/AppServicePlan.java b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/service/impl/AppServicePlan.java index e4c857cbab..8cd0b2b861 100644 --- a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/service/impl/AppServicePlan.java +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/service/impl/AppServicePlan.java @@ -4,7 +4,7 @@ */ package com.microsoft.azure.toolkit.lib.appservice.service.impl; -import com.azure.resourcemanager.AzureResourceManager; +import com.azure.resourcemanager.appservice.AppServiceManager; import com.microsoft.azure.toolkit.lib.appservice.entity.AppServicePlanEntity; import com.microsoft.azure.toolkit.lib.appservice.model.OperatingSystem; import com.microsoft.azure.toolkit.lib.appservice.model.PricingTier; @@ -23,21 +23,21 @@ public class AppServicePlan extends AbstractAzureManager implements IFunctionApp { public static final JavaVersion DEFAULT_JAVA_VERSION = JavaVersion.JAVA_8; private static final String UNSUPPORTED_OPERATING_SYSTEM = "Unsupported operating system %s"; - private final AzureResourceManager azureClient; + private final AppServiceManager azureClient; - public FunctionApp(@Nonnull final String id, @Nonnull final AzureResourceManager azureClient) { + public FunctionApp(@Nonnull final String id, @Nonnull final AppServiceManager azureClient) { super(id); this.azureClient = azureClient; } public FunctionApp(@Nonnull final String subscriptionId, @Nonnull final String resourceGroup, @Nonnull final String name, - @Nonnull final AzureResourceManager azureClient) { + @Nonnull final AppServiceManager azureClient) { super(subscriptionId, resourceGroup, name); this.azureClient = azureClient; } - public FunctionApp(@Nonnull WebSiteBase webSiteBase, @Nonnull final AzureResourceManager azureClient) { + public FunctionApp(@Nonnull WebSiteBase webSiteBase, @Nonnull final AppServiceManager azureClient) { super(webSiteBase); this.azureClient = azureClient; } @@ -137,7 +136,7 @@ public AbstractAppService @AzureOperation(name = "appservice|plan.create_update", params = {"this.config.servicePlanName()"}, type = AzureOperation.Type.SERVICE) public IAppServicePlan execute() { + SchemaValidator.getInstance().validateAndThrow("appservice/AppServicePlan", config); final AzureAppService az = Azure.az(AzureAppService.class).subscription(config.subscriptionId()); final IAppServicePlan appServicePlan = az.appServicePlan(config.servicePlanResourceGroup(), config.servicePlanName()); final String servicePlanName = config.servicePlanName(); if (!appServicePlan.exists()) { + SchemaValidator.getInstance().validateAndThrow("appservice/CreateAppServicePlan", config); AzureMessager.getMessager().info(String.format(CREATE_APP_SERVICE_PLAN, servicePlanName)); AzureTelemetry.getActionContext().setProperty(CREATE_NEW_APP_SERVICE_PLAN, String.valueOf(true)); - if (config.os() == null) { - throw new AzureToolkitRuntimeException("Missing required configuration for 'runtime.os'."); - } - - if (config.region() == null) { - throw new AzureToolkitRuntimeException("Missing required configuration for 'region'."); - } - - if (config.pricingTier() == null) { - throw new AzureToolkitRuntimeException("Missing required configuration for 'pricingTier'."); - } - + new CreateResourceGroupTask(this.config.subscriptionId(), config.servicePlanResourceGroup(), config.region()).execute(); appServicePlan.create() .withName(servicePlanName) .withResourceGroup(config.servicePlanResourceGroup()) @@ -59,7 +51,7 @@ public IAppServicePlan execute() { AzureMessager.getMessager().warning(String.format("Skip region update for existing service plan '%s' since it is not allowed.", appServicePlan.name())); } - if (config.pricingTier() != null) { + if (config.pricingTier() != null && !Objects.equals(config.pricingTier(), appServicePlan.entity().getPricingTier())) { // apply pricing tier to service plan appServicePlan.update().withPricingTier(config.pricingTier()).commit(); } diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/task/CreateOrUpdateFunctionAppTask.java b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/task/CreateOrUpdateFunctionAppTask.java new file mode 100644 index 0000000000..f630f52cce --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/task/CreateOrUpdateFunctionAppTask.java @@ -0,0 +1,269 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.appservice.task; + +import com.microsoft.azure.toolkit.lib.Azure; +import com.microsoft.azure.toolkit.lib.applicationinsights.ApplicationInsightsEntity; +import com.microsoft.azure.toolkit.lib.applicationinsights.task.GetOrCreateApplicationInsightsTask; +import com.microsoft.azure.toolkit.lib.appservice.AzureAppService; +import com.microsoft.azure.toolkit.lib.appservice.config.FunctionAppConfig; +import com.microsoft.azure.toolkit.lib.appservice.config.RuntimeConfig; +import com.microsoft.azure.toolkit.lib.appservice.model.DockerConfiguration; +import com.microsoft.azure.toolkit.lib.appservice.model.JavaVersion; +import com.microsoft.azure.toolkit.lib.appservice.model.OperatingSystem; +import com.microsoft.azure.toolkit.lib.appservice.model.Runtime; +import com.microsoft.azure.toolkit.lib.appservice.model.WebContainer; +import com.microsoft.azure.toolkit.lib.appservice.service.IAppServicePlan; +import com.microsoft.azure.toolkit.lib.appservice.service.IAppServiceUpdater; +import com.microsoft.azure.toolkit.lib.appservice.service.IFunctionApp; +import com.microsoft.azure.toolkit.lib.appservice.service.IFunctionAppBase; +import com.microsoft.azure.toolkit.lib.appservice.service.IFunctionAppDeploymentSlot; +import com.microsoft.azure.toolkit.lib.common.bundle.AzureString; +import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException; +import com.microsoft.azure.toolkit.lib.common.messager.AzureMessager; +import com.microsoft.azure.toolkit.lib.common.model.ResourceGroup; +import com.microsoft.azure.toolkit.lib.common.task.AzureTask; +import com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemetry; +import com.microsoft.azure.toolkit.lib.resource.task.CreateResourceGroupTask; +import org.apache.commons.lang3.StringUtils; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; + +public class CreateOrUpdateFunctionAppTask extends AzureTask> { + public static final String APPINSIGHTS_INSTRUMENTATION_KEY = "APPINSIGHTS_INSTRUMENTATIONKEY"; + private static final String APPLICATION_INSIGHTS_CREATE_FAILED = "Unable to create the Application Insights " + + "for the Function App due to error %s. Please use the Azure Portal to manually create and configure the " + + "Application Insights if needed."; + private static final String CREATE_FUNCTION_APP = "Creating function app %s..."; + private static final String CREATE_FUNCTION_APP_DONE = "Successfully created function app %s."; + private static final String CREATE_NEW_FUNCTION_APP = "isCreateNewFunctionApp"; + private static final String UPDATE_FUNCTION_APP = "Updating target Function App %s..."; + private static final String UPDATE_FUNCTION_DONE = "Successfully updated Function App %s."; + private static final String FUNCTIONS_WORKER_RUNTIME_NAME = "FUNCTIONS_WORKER_RUNTIME"; + private static final String FUNCTIONS_WORKER_RUNTIME_VALUE = "java"; + private static final String SET_FUNCTIONS_WORKER_RUNTIME = "Set function worker runtime to java."; + private static final String CUSTOMIZED_FUNCTIONS_WORKER_RUNTIME_WARNING = "App setting `FUNCTIONS_WORKER_RUNTIME` doesn't " + + "meet the requirement of Azure Java Functions, the value should be `java`."; + private static final String FUNCTIONS_EXTENSION_VERSION_NAME = "FUNCTIONS_EXTENSION_VERSION"; + private static final String FUNCTIONS_EXTENSION_VERSION_VALUE = "~3"; + private static final String SET_FUNCTIONS_EXTENSION_VERSION = "Functions extension version " + + "isn't configured, setting up the default value."; + private static final String FUNCTION_APP_NOT_EXIST_FOR_SLOT = "The Function App specified in pom.xml does not exist. " + + "Please make sure the Web App name is correct."; + private static final String FUNCTION_SLOT_CREATE_START = "The specified function slot does not exist. " + + "Creating a new slot..."; + private static final String FUNCTION_SLOT_CREATED = "Successfully created the function slot: %s."; + private static final String FUNCTION_SLOT_UPDATE = "Updating the specified function slot..."; + private static final String FUNCTION_SLOT_UPDATE_DONE = "Successfully updated the function slot: %s."; + + public static final JavaVersion DEFAULT_FUNCTION_JAVA_VERSION = JavaVersion.JAVA_8; + + private final FunctionAppConfig functionAppConfig; + private final List> tasks = new ArrayList<>(); + + private ResourceGroup resourceGroup; + private IAppServicePlan appServicePlan; + private ApplicationInsightsEntity applicationInsights; + private IFunctionAppBase functionApp; + + + public CreateOrUpdateFunctionAppTask(@Nonnull final FunctionAppConfig config) { + this.functionAppConfig = config; + initTasks(); + } + + private void initTasks() { + final IFunctionApp functionApp = Azure.az(AzureAppService.class).subscription(functionAppConfig.subscriptionId()) + .functionApp(functionAppConfig.resourceGroup(), functionAppConfig.appName()); + registerSubTask(getResourceGroupTask(), result -> this.resourceGroup = result); + registerSubTask(getServicePlanTask(), result -> this.appServicePlan = result); + // get/create ai instances only if user didn't specify ai connection string in app settings + if (!functionAppConfig.disableAppInsights() && !functionAppConfig.appSettings().containsKey(APPINSIGHTS_INSTRUMENTATION_KEY)) { + if (StringUtils.isNotEmpty(functionAppConfig.appInsightsKey())) { + this.applicationInsights = ApplicationInsightsEntity.builder().instrumentationKey(functionAppConfig.appInsightsKey()).build(); + } else if (StringUtils.isNotEmpty(functionAppConfig.appInsightsInstance()) || !functionApp.exists()) { + // create ai instance by default when create new function + registerSubTask(getApplicationInsightsTask(), result -> this.applicationInsights = result); + } + } + if (StringUtils.isEmpty(functionAppConfig.deploymentSlotName())) { + final AzureTask functionTask = functionApp.exists() ? + getUpdateFunctionAppTask(functionApp) : getCreateFunctionAppTask(functionApp); + registerSubTask(functionTask, result -> this.functionApp = result); + } else { + final IFunctionAppDeploymentSlot deploymentSlot = getFunctionDeploymentSlot(functionApp); + final AzureTask slotTask = deploymentSlot.exists() ? + getUpdateFunctionSlotTask(deploymentSlot) : getCreateFunctionSlotTask(deploymentSlot); + registerSubTask(slotTask, result -> this.functionApp = result); + } + } + + private void registerSubTask(AzureTask task, Consumer consumer) { + tasks.add(new AzureTask<>(() -> { + T result = task.getSupplier().get(); + consumer.accept(result); + return result; + })); + } + + private AzureTask getCreateFunctionAppTask(final IFunctionApp functionApp) { + final AzureString title = AzureString.format("Create new app({0}) on subscription({1})", + functionAppConfig.appName(), functionAppConfig.subscriptionId()); + return new AzureTask<>(title, () -> { + AzureTelemetry.getActionContext().setProperty(CREATE_NEW_FUNCTION_APP, String.valueOf(true)); + AzureMessager.getMessager().info(String.format(CREATE_FUNCTION_APP, functionAppConfig.appName())); + final Map appSettings = processAppSettingsWithDefaultValue(); + Optional.ofNullable(applicationInsights).ifPresent(insights -> + appSettings.put(APPINSIGHTS_INSTRUMENTATION_KEY, applicationInsights.getInstrumentationKey())); + final IFunctionApp result = functionApp.create().withName(functionAppConfig.appName()) + .withResourceGroup(resourceGroup.getName()) + .withPlan(appServicePlan.id()) + .withRuntime(getRuntime(functionAppConfig.runtime())) + .withDockerConfiguration(getDockerConfiguration(functionAppConfig.runtime())) + .withAppSettings(appSettings) + .commit(); + AzureMessager.getMessager().info(String.format(CREATE_FUNCTION_APP_DONE, result.name())); + return result; + }); + } + + private Map processAppSettingsWithDefaultValue() { + final Map appSettings = functionAppConfig.appSettings(); + setDefaultAppSetting(appSettings, FUNCTIONS_WORKER_RUNTIME_NAME, SET_FUNCTIONS_WORKER_RUNTIME, + FUNCTIONS_WORKER_RUNTIME_VALUE, CUSTOMIZED_FUNCTIONS_WORKER_RUNTIME_WARNING); + setDefaultAppSetting(appSettings, FUNCTIONS_EXTENSION_VERSION_NAME, SET_FUNCTIONS_EXTENSION_VERSION, + FUNCTIONS_EXTENSION_VERSION_VALUE, null); + return appSettings; + } + + private void setDefaultAppSetting(Map result, String settingName, String settingIsEmptyMessage, + String defaultValue, String warningMessage) { + final String setting = result.get(settingName); + if (StringUtils.isEmpty(setting)) { + AzureMessager.getMessager().info(settingIsEmptyMessage); + result.put(settingName, defaultValue); + return; + } + // Show warning message when user set a different value + if (!StringUtils.equalsIgnoreCase(setting, defaultValue) && StringUtils.isNotEmpty(warningMessage)) { + AzureMessager.getMessager().warning(warningMessage); + } + } + + private AzureTask getUpdateFunctionAppTask(final IFunctionApp functionApp) { + final AzureString title = AzureString.format("Update function app({0})", functionAppConfig.appName()); + return new AzureTask<>(title, () -> { + AzureMessager.getMessager().info(String.format(UPDATE_FUNCTION_APP, functionApp.name())); + // update app settings + final IAppServiceUpdater update = functionApp.update(); + final Map appSettings = processAppSettingsWithDefaultValue(); + if (functionAppConfig.disableAppInsights()) { + update.withoutAppSettings(APPINSIGHTS_INSTRUMENTATION_KEY); + } else if (applicationInsights != null) { + appSettings.put(APPINSIGHTS_INSTRUMENTATION_KEY, applicationInsights.getInstrumentationKey()); + } + final IFunctionApp result = update.withPlan(appServicePlan.id()) + .withRuntime(getRuntime(functionAppConfig.runtime())) + .withDockerConfiguration(getDockerConfiguration(functionAppConfig.runtime())) + .withAppSettings(appSettings) + .commit(); + AzureMessager.getMessager().info(String.format(UPDATE_FUNCTION_DONE, functionApp.name())); + return result; + }); + } + + private AzureTask getCreateFunctionSlotTask(IFunctionAppDeploymentSlot deploymentSlot) { + final AzureString title = AzureString.format("Create new slot({0}) on function app ({1})", + functionAppConfig.deploymentSlotName(), functionAppConfig.appName()); + return new AzureTask<>(title, () -> { + AzureMessager.getMessager().info(FUNCTION_SLOT_CREATE_START); + final Map appSettings = processAppSettingsWithDefaultValue(); + Optional.ofNullable(applicationInsights).ifPresent(insights -> + appSettings.put(APPINSIGHTS_INSTRUMENTATION_KEY, applicationInsights.getInstrumentationKey())); + final IFunctionAppDeploymentSlot result = deploymentSlot.create().withAppSettings(appSettings) + .withConfigurationSource(functionAppConfig.deploymentSlotConfigurationSource()) + .withName(functionAppConfig.deploymentSlotName()).commit(); + AzureMessager.getMessager().info(String.format(FUNCTION_SLOT_CREATED, result.name())); + return result; + }); + } + + private AzureTask getUpdateFunctionSlotTask(IFunctionAppDeploymentSlot deploymentSlot) { + final AzureString title = AzureString.format("Update function deployment slot({0})", functionAppConfig.deploymentSlotName()); + return new AzureTask<>(title, () -> { + AzureMessager.getMessager().info(FUNCTION_SLOT_UPDATE); + final Map appSettings = processAppSettingsWithDefaultValue(); + final IFunctionAppDeploymentSlot.Updater update = deploymentSlot.update(); + if (functionAppConfig.disableAppInsights()) { + update.withoutAppSettings(APPINSIGHTS_INSTRUMENTATION_KEY); + } else if (applicationInsights != null) { + appSettings.put(APPINSIGHTS_INSTRUMENTATION_KEY, applicationInsights.getInstrumentationKey()); + } + final IFunctionAppDeploymentSlot result = update.withAppSettings(appSettings).commit(); + AzureMessager.getMessager().info(String.format(FUNCTION_SLOT_UPDATE_DONE, result.name())); + return deploymentSlot; + }); + } + + private IFunctionAppDeploymentSlot getFunctionDeploymentSlot(final IFunctionApp functionApp) { + if (!functionApp.exists()) { + throw new AzureToolkitRuntimeException(FUNCTION_APP_NOT_EXIST_FOR_SLOT); + } + return functionApp.deploymentSlot(functionAppConfig.deploymentSlotName()); + } + + private AzureTask getApplicationInsightsTask() { + return new AzureTask<>(() -> { + try { + final String name = StringUtils.firstNonEmpty(functionAppConfig.appInsightsInstance(), functionAppConfig.appName()); + return new GetOrCreateApplicationInsightsTask(functionAppConfig.subscriptionId(), + functionAppConfig.resourceGroup(), functionAppConfig.region(), name).getSupplier().get(); + } catch (final Exception e) { + AzureMessager.getMessager().warning(String.format(APPLICATION_INSIGHTS_CREATE_FAILED, e.getMessage())); + return null; + } + }); + } + + private CreateResourceGroupTask getResourceGroupTask() { + return new CreateResourceGroupTask(functionAppConfig.subscriptionId(), functionAppConfig.resourceGroup(), functionAppConfig.region()); + } + + private CreateOrUpdateAppServicePlanTask getServicePlanTask() { + return new CreateOrUpdateAppServicePlanTask(functionAppConfig.getServicePlanConfig()); + } + + private Runtime getRuntime(RuntimeConfig runtime) { + return Runtime.getRuntime(runtime.os(), + WebContainer.JAVA_OFF, + OperatingSystem.DOCKER != runtime.os() ? runtime.javaVersion() : JavaVersion.OFF); + } + + // todo: remove duplicated with Create Web App Task + private DockerConfiguration getDockerConfiguration(RuntimeConfig runtime) { + if (OperatingSystem.DOCKER == runtime.os()) { + return DockerConfiguration.builder() + .userName(runtime.username()) + .password(runtime.password()) + .registryUrl(runtime.registryUrl()) + .image(runtime.image()) + .startUpCommand(runtime.startUpCommand()) + .build(); + } + return null; + } + + @Override + public IFunctionAppBase execute() { + this.tasks.forEach(t -> t.getSupplier().get()); + return functionApp; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/task/CreateOrUpdateWebAppTask.java b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/task/CreateOrUpdateWebAppTask.java index dcbbe97d3f..5d986a8537 100644 --- a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/task/CreateOrUpdateWebAppTask.java +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/task/CreateOrUpdateWebAppTask.java @@ -16,14 +16,19 @@ import com.microsoft.azure.toolkit.lib.appservice.model.Runtime; import com.microsoft.azure.toolkit.lib.appservice.model.WebContainer; import com.microsoft.azure.toolkit.lib.appservice.service.IAppServicePlan; +import com.microsoft.azure.toolkit.lib.appservice.service.IAppServiceUpdater; import com.microsoft.azure.toolkit.lib.appservice.service.IWebApp; import com.microsoft.azure.toolkit.lib.common.bundle.AzureString; +import com.microsoft.azure.toolkit.lib.common.entity.CheckNameAvailabilityResultEntity; +import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException; import com.microsoft.azure.toolkit.lib.common.messager.AzureMessager; +import com.microsoft.azure.toolkit.lib.common.model.Region; import com.microsoft.azure.toolkit.lib.common.operation.AzureOperation; import com.microsoft.azure.toolkit.lib.common.operation.AzureOperation.Type; import com.microsoft.azure.toolkit.lib.common.task.AzureTask; import com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemetry; import com.microsoft.azure.toolkit.lib.resource.task.CreateResourceGroupTask; +import lombok.Setter; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import reactor.core.publisher.Flux; @@ -32,6 +37,8 @@ import java.util.HashMap; import java.util.List; +import static com.microsoft.azure.toolkit.lib.appservice.utils.Utils.throwForbidCreateResourceWarning; + public class CreateOrUpdateWebAppTask extends AzureTask { private static final String CREATE_NEW_WEB_APP = "createNewWebApp"; @@ -43,6 +50,9 @@ public class CreateOrUpdateWebAppTask extends AzureTask { private final AppServiceConfig config; private final List> subTasks; + @Setter + private boolean skipCreateAzureResource; + public CreateOrUpdateWebAppTask(AppServiceConfig config) { this.config = config; this.subTasks = this.initTasks(); @@ -50,17 +60,21 @@ public CreateOrUpdateWebAppTask(AppServiceConfig config) { private List> initTasks() { final List> tasks = new ArrayList<>(); - tasks.add(new CreateResourceGroupTask(this.config.subscriptionId(), this.config.resourceGroup(), this.config.region())); - if (StringUtils.isNotBlank(this.config.servicePlanResourceGroup()) - && !StringUtils.equalsIgnoreCase(this.config.servicePlanResourceGroup(), this.config.resourceGroup())) { - tasks.add(new CreateResourceGroupTask(this.config.subscriptionId(), this.config.servicePlanResourceGroup(), this.config.region())); - } final AzureString title = AzureString.format("Create new web app({0})", this.config.appName()); - + AzureAppService az = Azure.az(AzureAppService.class); tasks.add(new AzureTask<>(title, () -> { - final IWebApp target = Azure.az(AzureAppService.class).subscription(config.subscriptionId()) + final IWebApp target = az.subscription(config.subscriptionId()) .webapp(config.resourceGroup(), config.appName()); if (!target.exists()) { + if (skipCreateAzureResource) { + throwForbidCreateResourceWarning("Web app", config.appName()); + } + CheckNameAvailabilityResultEntity result = az.checkNameAvailability(config.subscriptionId(), config.appName()); + if (!result.isAvailable()) { + throw new AzureToolkitRuntimeException(AzureString.format("Cannot create webapp {0} due to error: {1}", + config.appName(), + result.getUnavailabilityReason()).getString()); + } return create(); } return update(target); @@ -72,17 +86,14 @@ private List> initTasks() { private IWebApp create() { AzureTelemetry.getActionContext().setProperty(CREATE_NEW_WEB_APP, String.valueOf(true)); AzureMessager.getMessager().info(String.format(CREATE_WEBAPP, config.appName())); + + final Region region = this.config.region(); + new CreateResourceGroupTask(this.config.subscriptionId(), this.config.resourceGroup(), region).execute(); final AzureAppService az = Azure.az(AzureAppService.class).subscription(config.subscriptionId()); final IWebApp webapp = az.webapp(config.resourceGroup(), config.appName()); final AppServicePlanConfig servicePlanConfig = config.getServicePlanConfig(); - // initialize default value for service plan creation - if (StringUtils.isBlank(servicePlanConfig.servicePlanResourceGroup())) { - servicePlanConfig.servicePlanResourceGroup(config.resourceGroup()); - } - if (StringUtils.isBlank(servicePlanConfig.servicePlanName())) { - servicePlanConfig.servicePlanName(String.format("asp-%s", config.appName())); - } final IAppServicePlan appServicePlan = new CreateOrUpdateAppServicePlanTask(servicePlanConfig).execute(); + final IWebApp result = webapp.create().withName(config.appName()) .withResourceGroup(config.resourceGroup()) .withPlan(appServicePlan.id()) @@ -93,34 +104,28 @@ private IWebApp create() { AzureMessager.getMessager().info(String.format(CREATE_WEB_APP_DONE, result.name())); return result; } + @AzureOperation(name = "webapp.update", params = {"this.config.appName()"}, type = Type.SERVICE) private IWebApp update(final IWebApp webApp) { AzureMessager.getMessager().info(String.format(UPDATE_WEBAPP, webApp.name())); final IAppServicePlan currentPlan = webApp.plan(); final AppServicePlanConfig servicePlanConfig = config.getServicePlanConfig(); - if (StringUtils.isAllBlank(servicePlanConfig.servicePlanResourceGroup(), servicePlanConfig.servicePlanName())) { - // initialize service plan creation from exising webapp if user doesn't specify it in config - servicePlanConfig.servicePlanResourceGroup(currentPlan.resourceGroup()); - servicePlanConfig.servicePlanName(currentPlan.name()); - } else if (StringUtils.isAnyBlank(servicePlanConfig.servicePlanResourceGroup(), servicePlanConfig.servicePlanName())) { - if (StringUtils.isBlank(servicePlanConfig.servicePlanResourceGroup())) { - if (StringUtils.equalsIgnoreCase(servicePlanConfig.servicePlanName(), currentPlan.name())) { - servicePlanConfig.servicePlanResourceGroup(currentPlan.resourceGroup()); - } else { - servicePlanConfig.servicePlanResourceGroup(config.resourceGroup()); - } - } else if (StringUtils.isBlank(servicePlanConfig.servicePlanName())) { - if (StringUtils.equalsIgnoreCase(servicePlanConfig.servicePlanResourceGroup(), currentPlan.resourceGroup())) { - servicePlanConfig.servicePlanName(currentPlan.name()); - } else { - servicePlanConfig.servicePlanName(String.format("asp-%s", config.appName())); - } - } + if (skipCreateAzureResource && !Azure.az(AzureAppService.class).appServicePlan(servicePlanConfig.servicePlanResourceGroup(), servicePlanConfig.servicePlanName()).exists()) { + throwForbidCreateResourceWarning("Service plan", servicePlanConfig.servicePlanResourceGroup() + "/" + servicePlanConfig.servicePlanName()); } + + final Runtime runtime = getRuntime(config.runtime()); final IAppServicePlan appServicePlan = new CreateOrUpdateAppServicePlanTask(servicePlanConfig).execute(); - final IWebApp result = webApp.update().withPlan(appServicePlan.id()) - .withRuntime(getRuntime(config.runtime())) + final IAppServiceUpdater draft = webApp.update(); + if (!(StringUtils.equalsIgnoreCase(config.servicePlanResourceGroup(), currentPlan.resourceGroup()) && + StringUtils.equalsIgnoreCase(config.servicePlanName(), currentPlan.name()))) { + draft.withPlan(appServicePlan.id()); + } + if (!webApp.getRuntime().equals(runtime)) { + draft.withRuntime(runtime); + } + final IWebApp result = draft .withDockerConfiguration(getDockerConfiguration(config.runtime())) .withAppSettings(ObjectUtils.firstNonNull(config.appSettings(), new HashMap<>())) .commit(); diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/task/DeployFunctionAppTask.java b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/task/DeployFunctionAppTask.java new file mode 100644 index 0000000000..2375015aed --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/task/DeployFunctionAppTask.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ +package com.microsoft.azure.toolkit.lib.appservice.task; + +import com.azure.core.management.exception.ManagementException; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.toolkit.lib.Azure; +import com.microsoft.azure.toolkit.lib.appservice.AzureAppService; +import com.microsoft.azure.toolkit.lib.appservice.entity.FunctionEntity; +import com.microsoft.azure.toolkit.lib.appservice.model.FunctionDeployType; +import com.microsoft.azure.toolkit.lib.appservice.service.IFunctionApp; +import com.microsoft.azure.toolkit.lib.appservice.service.IFunctionAppBase; +import com.microsoft.azure.toolkit.lib.common.bundle.AzureString; +import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException; +import com.microsoft.azure.toolkit.lib.common.messager.AzureMessager; +import com.microsoft.azure.toolkit.lib.common.task.AzureTask; +import com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemetry; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.zeroturnaround.zip.ZipUtil; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; +import reactor.util.retry.Retry; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class DeployFunctionAppTask extends AzureTask> { + + private static final int SYNC_FUNCTION_MAX_ATTEMPTS = 5; + private static final int SYNC_FUNCTION_DELAY = 1; + private static final int LIST_TRIGGERS_MAX_RETRY = 5; + private static final int LIST_TRIGGERS_RETRY_PERIOD_IN_SECONDS = 10; + private static final String RUNNING = "Running"; + private static final String AUTH_LEVEL = "authLevel"; + private static final String HTTP_TRIGGER = "httpTrigger"; + private static final String LOCAL_SETTINGS_FILE = "local.settings.json"; + private static final String DEPLOY_START = "Starting deployment..."; + private static final String DEPLOY_FINISH = "Deployment done, you may access your resource through %s"; + private static final String HTTP_TRIGGER_URLS = "HTTP Trigger Urls:"; + private static final String NO_ANONYMOUS_HTTP_TRIGGER = "No anonymous HTTP Triggers found in deployed function app, skip list triggers."; + private static final String NO_TRIGGERS_FOUNDED = "No triggers found in deployed function app, " + + "please try recompile the project by `mvn clean package` and deploy again."; + private static final String UNABLE_TO_LIST_NONE_ANONYMOUS_HTTP_TRIGGERS = "Some http trigger urls cannot be displayed " + + "because they are non-anonymous. To access the non-anonymous triggers, please refer to https://aka.ms/azure-functions-key."; + private static final String SKIP_DEPLOYMENT_FOR_DOCKER_APP_SERVICE = "Skip deployment for docker app service"; + private static final String FAILED_TO_LIST_TRIGGERS = "Deployment succeeded, but failed to list http trigger urls."; + private static final String SYNC_TRIGGERS = "Syncing triggers and fetching function information"; + private static final String LIST_TRIGGERS = "Querying triggers..."; + private static final String LIST_TRIGGERS_WITH_RETRY = "Querying triggers (Attempt {0}/{1})..."; + + private final IFunctionAppBase target; + private final File stagingDirectory; + private final FunctionDeployType deployType; + + public DeployFunctionAppTask(@Nonnull IFunctionAppBase target, @Nonnull File stagingFolder, @Nullable FunctionDeployType deployType) { + this.target = target; + this.stagingDirectory = stagingFolder; + this.deployType = deployType; + } + + @Override + public AzureString getTitle() { + return AzureString.format("Deploy artifact to function app %s", target.name()); + } + + @Override + public IFunctionAppBase execute() { + if (target.getRuntime().isDocker()) { + AzureMessager.getMessager().info(SKIP_DEPLOYMENT_FOR_DOCKER_APP_SERVICE); + return target; + } + deployArtifact(); + if (target instanceof IFunctionApp) { + listHTTPTriggerUrls((IFunctionApp) target); + } + return target; + } + + private void deployArtifact() { + AzureMessager.getMessager().info(DEPLOY_START); + // For ftp deploy, we need to upload entire staging directory not the zipped package + final File file = deployType == FunctionDeployType.FTP ? stagingDirectory : packageStagingDirectory(); + final long startTime = System.currentTimeMillis(); + if (deployType == null) { + target.deploy(file); + } else { + target.deploy(file, deployType); + } + AzureTelemetry.getActionContext().setProperty("deploy-cost", String.valueOf(System.currentTimeMillis() - startTime)); + if (!StringUtils.equalsIgnoreCase(target.state(), RUNNING)) { + target.start(); + } + AzureMessager.getMessager().info(String.format(DEPLOY_FINISH, target.hostName())); + } + + private File packageStagingDirectory() { + try { + final File zipFile = Files.createTempFile("azure-functions", ".zip").toFile(); + ZipUtil.pack(stagingDirectory, zipFile); + ZipUtil.removeEntry(zipFile, LOCAL_SETTINGS_FILE); + return zipFile; + } catch (IOException e) { + throw new AzureToolkitRuntimeException("Failed to package function to deploy", e); + } + } + + private void listHTTPTriggerUrls(IFunctionApp target) { + try { + syncTriggers(target); + final List triggers = listFunctions(target); + final List httpFunction = triggers.stream() + .filter(function -> function.getTrigger() != null && + StringUtils.equalsIgnoreCase(function.getTrigger().getType(), HTTP_TRIGGER)) + .collect(Collectors.toList()); + final List anonymousTriggers = httpFunction.stream() + .filter(bindingResource -> bindingResource.getTrigger() != null && + StringUtils.equalsIgnoreCase(bindingResource.getTrigger().getProperty(AUTH_LEVEL), AuthorizationLevel.ANONYMOUS.toString())) + .collect(Collectors.toList()); + if (CollectionUtils.isEmpty(httpFunction) || CollectionUtils.isEmpty(anonymousTriggers)) { + AzureMessager.getMessager().info(NO_ANONYMOUS_HTTP_TRIGGER); + return; + } + AzureMessager.getMessager().info(HTTP_TRIGGER_URLS); + anonymousTriggers.forEach(trigger -> AzureMessager.getMessager().info(String.format("\t %s : %s", trigger.getName(), trigger.getTriggerUrl()))); + if (anonymousTriggers.size() < httpFunction.size()) { + AzureMessager.getMessager().info(UNABLE_TO_LIST_NONE_ANONYMOUS_HTTP_TRIGGERS); + } + } catch (final RuntimeException | InterruptedException e) { + // show warning instead of exception for list triggers + AzureMessager.getMessager().warning(FAILED_TO_LIST_TRIGGERS); + } + } + + // todo: move to app service library + // Refers https://github.com/Azure/azure-functions-core-tools/blob/3.0.3568/src/Azure.Functions.Cli/Actions/AzureActions/PublishFunctionAppAction.cs#L452 + private void syncTriggers(final IFunctionApp functionApp) throws InterruptedException { + AzureMessager.getMessager().info(SYNC_TRIGGERS); + Thread.sleep(5 * 1000); + Mono.fromRunnable(() -> { + try { + Azure.az(AzureAppService.class).getAppServiceManager(functionApp.subscriptionId()) + .functionApps().manager().serviceClient().getWebApps().syncFunctions(functionApp.resourceGroup(), functionApp.name()); + } catch (ManagementException e) { + if (e.getResponse().getStatusCode() == 200) { + // Java SDK throw exception with 200 response, swallow exception in this case + } + } + }).subscribeOn(Schedulers.boundedElastic()) + .retryWhen(Retry.fixedDelay(SYNC_FUNCTION_MAX_ATTEMPTS - 1, Duration.ofSeconds(SYNC_FUNCTION_DELAY))).block(); + } + + private List listFunctions(final IFunctionApp functionApp) { + final int[] count = {0}; + return Mono.fromCallable(() -> { + final AzureString message = count[0]++ == 0 ? + AzureString.fromString(LIST_TRIGGERS) : AzureString.format(LIST_TRIGGERS_WITH_RETRY, count[0], LIST_TRIGGERS_MAX_RETRY); + AzureMessager.getMessager().info(message); + return Optional.ofNullable(functionApp.listFunctions(true)) + .filter(CollectionUtils::isNotEmpty) + .orElseThrow(() -> new AzureToolkitRuntimeException(NO_TRIGGERS_FOUNDED)); + }).subscribeOn(Schedulers.boundedElastic()) + .retryWhen(Retry.fixedDelay(LIST_TRIGGERS_MAX_RETRY - 1, Duration.ofSeconds(LIST_TRIGGERS_RETRY_PERIOD_IN_SECONDS))).block(); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/task/DeployWebAppTask.java b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/task/DeployWebAppTask.java index 9de0cceb9e..186f9ab612 100644 --- a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/task/DeployWebAppTask.java +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/task/DeployWebAppTask.java @@ -6,19 +6,20 @@ package com.microsoft.azure.toolkit.lib.appservice.task; import com.microsoft.azure.toolkit.lib.appservice.model.WebAppArtifact; -import com.microsoft.azure.toolkit.lib.appservice.service.IWebApp; +import com.microsoft.azure.toolkit.lib.appservice.service.IWebAppBase; import com.microsoft.azure.toolkit.lib.common.bundle.AzureString; import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException; import com.microsoft.azure.toolkit.lib.common.messager.AzureMessager; import com.microsoft.azure.toolkit.lib.common.operation.AzureOperation; import com.microsoft.azure.toolkit.lib.common.task.AzureTask; +import com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemetry; import org.apache.commons.lang3.StringUtils; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -public class DeployWebAppTask extends AzureTask { +public class DeployWebAppTask extends AzureTask> { private static final String SKIP_DEPLOYMENT_FOR_DOCKER_APP_SERVICE = "Skip deployment for docker webapp, " + "you can navigate to %s to access your docker webapp."; private static final String DEPLOY_START = "Trying to deploy artifact to %s..."; @@ -28,15 +29,15 @@ public class DeployWebAppTask extends AzureTask { private static final String STOP_APP_DONE = "Successfully stopped Web App."; private static final String START_APP_DONE = "Successfully started Web App."; private static final String RUNNING = "Running"; - private IWebApp webApp; - private List artifacts; - private boolean isStopAppDuringDeployment; + private final IWebAppBase webApp; + private final List artifacts; + private final boolean isStopAppDuringDeployment; - public DeployWebAppTask(IWebApp webApp, List artifacts) { + public DeployWebAppTask(IWebAppBase webApp, List artifacts) { this(webApp, artifacts, false); } - public DeployWebAppTask(IWebApp webApp, List artifacts, boolean isStopAppDuringDeployment) { + public DeployWebAppTask(IWebAppBase webApp, List artifacts, boolean isStopAppDuringDeployment) { this.webApp = webApp; this.artifacts = artifacts; this.isStopAppDuringDeployment = isStopAppDuringDeployment; @@ -44,7 +45,7 @@ public DeployWebAppTask(IWebApp webApp, List artifacts, boolean @Override @AzureOperation(name = "webapp.deploy", params = {"this.webApp.entity().getName()"}, type = AzureOperation.Type.SERVICE) - public IWebApp execute() { + public IWebAppBase execute() { if (webApp.getRuntime().isDocker()) { AzureMessager.getMessager().info(AzureString.format(SKIP_DEPLOYMENT_FOR_DOCKER_APP_SERVICE, "https://" + webApp.hostName())); return webApp; @@ -66,14 +67,15 @@ private void deployArtifacts() { if (artifacts.stream().anyMatch(artifact -> artifact.getDeployType() == null)) { throw new AzureToolkitRuntimeException("missing deployment type for some artifacts."); } - + final long startTime = System.currentTimeMillis(); final List artifactsOneDeploy = this.artifacts.stream() .filter(artifact -> artifact.getDeployType() != null) .collect(Collectors.toList()); artifactsOneDeploy.forEach(resource -> webApp.deploy(resource.getDeployType(), resource.getFile(), resource.getPath())); + AzureTelemetry.getActionContext().setProperty("deploy-cost", String.valueOf(System.currentTimeMillis() - startTime)); } - private static void stopAppService(IWebApp target) { + private static void stopAppService(IWebAppBase target) { AzureMessager.getMessager().info(STOP_APP); target.stop(); // workaround for the resources release problem. @@ -86,7 +88,7 @@ private static void stopAppService(IWebApp target) { AzureMessager.getMessager().info(STOP_APP_DONE); } - private static void startAppService(IWebApp target) { + private static void startAppService(IWebAppBase target) { if (!StringUtils.equalsIgnoreCase(target.state(), RUNNING)) { AzureMessager.getMessager().info(START_APP); target.start(); diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/utils/AppServiceConfigUtils.java b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/utils/AppServiceConfigUtils.java new file mode 100644 index 0000000000..f8632104b2 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/utils/AppServiceConfigUtils.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.appservice.utils; + +import com.microsoft.azure.toolkit.lib.Azure; +import com.microsoft.azure.toolkit.lib.appservice.AzureAppService; +import com.microsoft.azure.toolkit.lib.appservice.config.AppServiceConfig; +import com.microsoft.azure.toolkit.lib.appservice.config.RuntimeConfig; +import com.microsoft.azure.toolkit.lib.appservice.model.JavaVersion; +import com.microsoft.azure.toolkit.lib.appservice.model.OperatingSystem; +import com.microsoft.azure.toolkit.lib.appservice.service.IAppService; +import com.microsoft.azure.toolkit.lib.appservice.service.IAppServicePlan; +import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException; +import com.microsoft.azure.toolkit.lib.common.model.Region; +import com.microsoft.azure.toolkit.lib.legacy.appservice.AppServiceUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Map; + +import static com.microsoft.azure.toolkit.lib.appservice.utils.Utils.mergeObjects; + +public class AppServiceConfigUtils { + private static final String SETTING_DOCKER_IMAGE = "DOCKER_CUSTOM_IMAGE_NAME"; + private static final String SETTING_REGISTRY_SERVER = "DOCKER_REGISTRY_SERVER_URL"; + + public static AppServiceConfig fromAppService(IAppService appService, IAppServicePlan servicePlan) { + AppServiceConfig config = new AppServiceConfig(); + config.appName(appService.name()); + + config.resourceGroup(appService.entity().getResourceGroup()); + config.subscriptionId(Utils.getSubscriptionId(appService.id())); + config.region(appService.entity().getRegion()); + config.pricingTier(servicePlan.entity().getPricingTier()); + RuntimeConfig runtimeConfig = new RuntimeConfig(); + if (AppServiceUtils.isDockerAppService(appService)) { + runtimeConfig.os(OperatingSystem.DOCKER); + final Map settings = appService.entity().getAppSettings(); + + final String imageSetting = settings.get(SETTING_DOCKER_IMAGE); + if (StringUtils.isNotBlank(imageSetting)) { + runtimeConfig.image(imageSetting); + } else { + runtimeConfig.image(appService.entity().getDockerImageName()); + } + final String registryServerSetting = settings.get(SETTING_REGISTRY_SERVER); + if (StringUtils.isNotBlank(registryServerSetting)) { + runtimeConfig.registryUrl(registryServerSetting); + } + } else { + runtimeConfig.os(appService.getRuntime().getOperatingSystem()); + runtimeConfig.webContainer(appService.getRuntime().getWebContainer()); + runtimeConfig.javaVersion(appService.getRuntime().getJavaVersion()); + } + config.runtime(runtimeConfig); + if (servicePlan.entity() != null) { + config.pricingTier(servicePlan.entity().getPricingTier()); + config.servicePlanName(servicePlan.name()); + config.servicePlanResourceGroup(servicePlan.entity().getResourceGroup()); + } + return config; + } + + public static AppServiceConfig buildDefaultWebAppConfig(String subscriptionId, String resourceGroup, String appName, String packaging, JavaVersion javaVersion) { + final AppServiceConfig appServiceConfig = AppServiceConfig.buildDefaultWebAppConfig(resourceGroup, appName, packaging, javaVersion); + final List regions = Azure.az(AzureAppService.class).listSupportedRegions(subscriptionId); + // replace with first region when the default region is not present + appServiceConfig.region(Utils.selectFirstOptionIfCurrentInvalid("region", regions, appServiceConfig.region())); + return appServiceConfig; + } + + public static AppServiceConfig buildDefaultFunctionConfig(String subscriptionId, String resourceGroup, String appName, JavaVersion javaVersion) { + final AppServiceConfig appServiceConfig = AppServiceConfig.buildDefaultFunctionConfig(resourceGroup, appName, javaVersion); + final List regions = Azure.az(AzureAppService.class).listSupportedRegions(subscriptionId); + // replace with first region when the default region is not present + appServiceConfig.region(Utils.selectFirstOptionIfCurrentInvalid("region", regions, appServiceConfig.region())); + return appServiceConfig; + } + + public static void mergeAppServiceConfig(AppServiceConfig to, AppServiceConfig from) { + try { + mergeObjects(to, from); + } catch (IllegalAccessException e) { + throw new AzureToolkitRuntimeException("Cannot copy object for class AppServiceConfig.", e); + } + + if (to.runtime() != from.runtime()) { + mergeRuntime(to.runtime(), from.runtime()); + } + } + + private static void mergeRuntime(RuntimeConfig to, RuntimeConfig from) { + try { + mergeObjects(to, from); + } catch (IllegalAccessException e) { + throw new AzureToolkitRuntimeException("Cannot copy object for class RuntimeConfig.", e); + } + } +} diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/utils/Utils.java b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/utils/Utils.java index ff69c3ed06..9ffa3f7c96 100644 --- a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/utils/Utils.java +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/appservice/utils/Utils.java @@ -12,10 +12,12 @@ import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.reflect.FieldUtils; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.File; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -60,4 +62,27 @@ public static DeployType getDeployTypeByFileExtension(File file) { throw new AzureToolkitRuntimeException("Unsupported file type, please set the deploy type."); } } + + public static T selectFirstOptionIfCurrentInvalid(String name, List options, T value) { + if (options.isEmpty()) { + throw new AzureToolkitRuntimeException(String.format("No %s is available.", name)); + } + return options.contains(value) ? value : options.get(0); + } + + public static void mergeObjects(T to, T from) throws IllegalAccessException { + for (Field field : FieldUtils.getAllFields(from.getClass())) { + if (FieldUtils.readField(field, to, true) == null) { + final Object value = FieldUtils.readField(field, from, true); + if (value != null) { + FieldUtils.writeField(field, to, value, true); + } + } + } + } + + public static void throwForbidCreateResourceWarning(String resourceType, String name) { + throw new AzureToolkitRuntimeException(String.format("%s(%s) cannot be found, if you want to create azure resources please remove command line arguments: " + + "`-Dazure.resource.create.skip=true` or `-DskipCreateAzureResource`.", resourceType, name)); + } } diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/legacy/appservice/AppServiceUtils.java b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/legacy/appservice/AppServiceUtils.java index a766fe6c1e..0914599bad 100644 --- a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/legacy/appservice/AppServiceUtils.java +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/java/com/microsoft/azure/toolkit/lib/legacy/appservice/AppServiceUtils.java @@ -7,7 +7,7 @@ import com.microsoft.azure.toolkit.lib.appservice.model.OperatingSystem; import com.microsoft.azure.toolkit.lib.appservice.model.PricingTier; -import com.microsoft.azure.toolkit.lib.appservice.service.IWebApp; +import com.microsoft.azure.toolkit.lib.appservice.service.IAppService; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; @@ -41,8 +41,8 @@ public static DockerImageType getDockerImageType(final String imageName, final b } } - public static boolean isDockerAppService(IWebApp webapp) { - return webapp != null && webapp.getRuntime() != null && webapp.getRuntime().isDocker(); + public static boolean isDockerAppService(IAppService appService) { + return appService != null && appService.getRuntime() != null && appService.getRuntime().isDocker(); } } diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/com/microsoft/azure/toolkit/operation/title/base/appservice.properties b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/com/microsoft/azure/toolkit/operation/title/base/appservice.properties index e0efebd1d2..f5cecf0a35 100644 --- a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/com/microsoft/azure/toolkit/operation/title/base/appservice.properties +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/com/microsoft/azure/toolkit/operation/title/base/appservice.properties @@ -11,3 +11,4 @@ appservice|deployment.get.id=get Azure App Service deployment slot by id({0}) appservice|deployment.get.name|app|rg|subscription=get Azure App Service deployment slot({0}) of app({1}) appservice.get_client.subscription=get Azure App Service rest client of subscription({0}) appservice|plan.create_update=create or update Azure App Service plan ({0}) from config +appservice.check_name=check name availability for ({0}) \ No newline at end of file diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/com/microsoft/azure/toolkit/operation/title/base/functionapp.properties b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/com/microsoft/azure/toolkit/operation/title/base/functionapp.properties index c6a2d3119f..4aa54379f1 100644 --- a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/com/microsoft/azure/toolkit/operation/title/base/functionapp.properties +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/com/microsoft/azure/toolkit/operation/title/base/functionapp.properties @@ -2,3 +2,4 @@ functionapp.get.id=get Azure Functions app({0}) functionapp.get.name|rg|sid=get Azure Functions app({0}) in resource group{(1)} functionapp.list.subscription|selected=list Azure Functions apps of selected subscriptions functionapp.list.subscription=list Azure Functions apps of subscription({0}) +functionapp|mojo.deploy=deploy to Azure Function App with resource creation or updating diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/com/microsoft/azure/toolkit/operation/title/base/webapp.properties b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/com/microsoft/azure/toolkit/operation/title/base/webapp.properties index 2fbadd240c..875251e16f 100644 --- a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/com/microsoft/azure/toolkit/operation/title/base/webapp.properties +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/com/microsoft/azure/toolkit/operation/title/base/webapp.properties @@ -5,5 +5,6 @@ webapp.get.name|rg|sid=get Azure Web App({0}) in resource group({1}) webapp.list.subscription|selected=list Azure Web Apps of selected subscriptions webapp.list.subscription=list Azure Web Apps of subscription({0}) webapp|runtime.list.os|version=list available Azure Web App runtimes with os({0}) and java version({1}) +webapp|mojo.deploy=deploy to Azure Web App with resource creation or updating webapp.create=create Azure Web App ({0}) from config webapp.update=update Azure Web App ({0}) from config diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/schema/appservice/AppServicePlan.json b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/schema/appservice/AppServicePlan.json new file mode 100644 index 0000000000..109cd55b80 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/schema/appservice/AppServicePlan.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "App Service Plan - Update", + "description": "Schema for AppServicePlanConfig when update existing service plan", + "properties": { + "subscriptionId": { + "$ref": "classpath:///schema/common/UUID.json" + }, + "servicePlanResourceGroup": { + "$ref": "classpath:///schema/common/ResourceGroupName.json" + }, + "servicePlanName": { + "$ref": "classpath:///schema/appservice/AppServicePlanName.json" + }, + "os": { + "$ref": "classpath:///schema/appservice/Runtime.json#/definitions/os" + }, + "region": { + "type": "object" + }, + "pricingTier": { + "type": "object" + } + }, + "required": [ + "subscriptionId", + "servicePlanName", + "servicePlanResourceGroup" + ] +} diff --git a/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/schema/appservice/CreateAppServicePlan.json b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/schema/appservice/CreateAppServicePlan.json new file mode 100644 index 0000000000..9671330f7f --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-appservice-lib/src/main/resources/schema/appservice/CreateAppServicePlan.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "App Service Plan - Update", + "description": "Schema for AppServicePlanConfig when update existing service plan", + "allOf": [ + { + "$ref": "classpath:///schema/appservice/AppServicePlan.json" + } + ] +} diff --git a/azure-toolkit-libs/azure-toolkit-auth-lib/pom.xml b/azure-toolkit-libs/azure-toolkit-auth-lib/pom.xml index 4d01040b2c..4d0f6549f7 100644 --- a/azure-toolkit-libs/azure-toolkit-auth-lib/pom.xml +++ b/azure-toolkit-libs/azure-toolkit-auth-lib/pom.xml @@ -5,7 +5,7 @@ azure-toolkit-libs com.microsoft.azure - 0.11.0 + 0.12.1 4.0.0 @@ -15,14 +15,6 @@ com.azure azure-core - - com.azure - azure-core-http-okhttp - - - com.squareup.okhttp3 - okhttp - org.apache.commons commons-lang3 @@ -63,11 +55,6 @@ com.microsoft.azure azure-client-runtime - - com.azure.resourcemanager - azure-resourcemanager - - com.azure azure-identity diff --git a/azure-toolkit-libs/azure-toolkit-auth-lib/src/main/java/com/microsoft/azure/toolkit/lib/auth/AzureAccount.java b/azure-toolkit-libs/azure-toolkit-auth-lib/src/main/java/com/microsoft/azure/toolkit/lib/auth/AzureAccount.java index d7a6f3b9c2..eefa75186d 100644 --- a/azure-toolkit-libs/azure-toolkit-auth-lib/src/main/java/com/microsoft/azure/toolkit/lib/auth/AzureAccount.java +++ b/azure-toolkit-libs/azure-toolkit-auth-lib/src/main/java/com/microsoft/azure/toolkit/lib/auth/AzureAccount.java @@ -6,22 +6,15 @@ package com.microsoft.azure.toolkit.lib.auth; import com.azure.core.credential.TokenCredential; -import com.azure.core.http.policy.HttpLogDetailLevel; -import com.azure.core.http.policy.HttpPipelinePolicy; import com.azure.core.management.AzureEnvironment; -import com.azure.core.management.profile.AzureProfile; import com.azure.identity.SharedTokenCacheCredential; import com.azure.identity.SharedTokenCacheCredentialBuilder; import com.azure.identity.TokenCachePersistenceOptions; -import com.azure.resourcemanager.AzureResourceManager; -import com.azure.resourcemanager.resources.fluentcore.policy.ProviderRegistrationPolicy; import com.azure.resourcemanager.resources.models.Location; -import com.azure.resourcemanager.resources.models.Providers; import com.azure.resourcemanager.resources.models.RegionType; import com.azure.resourcemanager.resources.models.Subscription; import com.google.common.base.Preconditions; import com.microsoft.azure.toolkit.lib.Azure; -import com.microsoft.azure.toolkit.lib.AzureConfiguration; import com.microsoft.azure.toolkit.lib.account.IAzureAccount; import com.microsoft.azure.toolkit.lib.auth.core.azurecli.AzureCliAccount; import com.microsoft.azure.toolkit.lib.auth.core.devicecode.DeviceCodeAccount; @@ -241,7 +234,8 @@ public List listRegions(String subscriptionId) { * see doc for: az account list-locations -o table */ public List listRegions() { - return Flux.fromIterable(getSubscriptions()).parallel().map(com.microsoft.azure.toolkit.lib.common.model.Subscription::getId) + return Flux.fromIterable(Azure.az(IAzureAccount.class).account().getSelectedSubscriptions()) + .parallel().map(com.microsoft.azure.toolkit.lib.common.model.Subscription::getId) .map(this::listRegions) .sequential().collectList() .map(regionSet -> regionSet.stream() @@ -276,37 +270,7 @@ private static Map> buildAccountMap() { // todo: share codes with other library which leverage track2 mgmt sdk @Cacheable(cacheName = "Subscription", key = "$subscriptionId") private Subscription getSubscription(String subscriptionId) { - return getAzureResourceManager(subscriptionId).subscriptions().getById(subscriptionId); + return getResourceManager(subscriptionId).subscriptions().getById(subscriptionId); } - // todo: share codes with other library which leverage track2 mgmt sdk - @Cacheable(cacheName = "AzureResourceManager", key = "$subscriptionId") - private AzureResourceManager getAzureResourceManager(String subscriptionId) { - // make sure it is signed in. - account(); - final AzureConfiguration config = Azure.az().config(); - final String userAgent = config.getUserAgent(); - final HttpLogDetailLevel logDetailLevel = config.getLogLevel() == null ? - HttpLogDetailLevel.NONE : HttpLogDetailLevel.valueOf(config.getLogLevel()); - final AzureProfile azureProfile = new AzureProfile(account.getEnvironment()); - - final Providers providers = AzureResourceManager.configure() - .withPolicy(getUserAgentPolicy(userAgent)) - .authenticate(account.getTokenCredential(subscriptionId), azureProfile) - .withSubscription(subscriptionId).providers(); - return AzureResourceManager.configure() - .withLogLevel(logDetailLevel) - .withPolicy(getUserAgentPolicy(userAgent)) // set user agent with policy - .withPolicy(new ProviderRegistrationPolicy(providers)) // add policy to auto register resource providers - .authenticate(account.getTokenCredential(subscriptionId), azureProfile) - .withSubscription(subscriptionId); - } - - private HttpPipelinePolicy getUserAgentPolicy(String userAgent) { - return (httpPipelineCallContext, httpPipelineNextPolicy) -> { - final String previousUserAgent = httpPipelineCallContext.getHttpRequest().getHeaders().getValue("User-Agent"); - httpPipelineCallContext.getHttpRequest().setHeader("User-Agent", String.format("%s %s", userAgent, previousUserAgent)); - return httpPipelineNextPolicy.process(); - }; - } } diff --git a/azure-toolkit-libs/azure-toolkit-auth-lib/src/main/java/com/microsoft/azure/toolkit/lib/auth/TokenCredentialManager.java b/azure-toolkit-libs/azure-toolkit-auth-lib/src/main/java/com/microsoft/azure/toolkit/lib/auth/TokenCredentialManager.java index 0bb9a2f2bd..9b46c2938f 100644 --- a/azure-toolkit-libs/azure-toolkit-auth-lib/src/main/java/com/microsoft/azure/toolkit/lib/auth/TokenCredentialManager.java +++ b/azure-toolkit-libs/azure-toolkit-auth-lib/src/main/java/com/microsoft/azure/toolkit/lib/auth/TokenCredentialManager.java @@ -6,18 +6,16 @@ package com.microsoft.azure.toolkit.lib.auth; import com.azure.core.credential.TokenCredential; -import com.azure.core.http.ProxyOptions; -import com.azure.core.http.okhttp.OkHttpAsyncHttpClientBuilder; import com.azure.core.http.policy.FixedDelay; import com.azure.core.http.policy.HttpPipelinePolicy; import com.azure.core.http.policy.RetryPolicy; import com.azure.core.management.AzureEnvironment; import com.azure.core.management.profile.AzureProfile; import com.azure.core.util.logging.ClientLogger; -import com.azure.resourcemanager.AzureResourceManager; +import com.azure.resourcemanager.resources.ResourceManager; import com.azure.resourcemanager.resources.models.Tenant; import com.microsoft.azure.toolkit.lib.Azure; -import com.microsoft.azure.toolkit.lib.AzureConfiguration; +import com.microsoft.azure.toolkit.lib.AzureService; import com.microsoft.azure.toolkit.lib.common.model.Subscription; import com.microsoft.azure.toolkit.lib.common.utils.Utils; import lombok.Getter; @@ -27,7 +25,6 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; -import java.net.InetSocketAddress; import java.time.Duration; import java.util.ArrayList; import java.util.Collection; @@ -70,7 +67,7 @@ public Mono> listSubscriptions(List tenantIds) { .collect(Collectors.toList())); } - private static Mono> listSubscriptionsInTenant(AzureResourceManager.Authenticated client, String tenantId) { + private static Mono> listSubscriptionsInTenant(ResourceManager.Authenticated client, String tenantId) { return client.subscriptions().listAsync() .map(s -> toSubscriptionEntity(tenantId, s)).collectList().onErrorResume(ex -> { // warn and ignore, should modify here if IMessage is ready @@ -89,35 +86,23 @@ private static Subscription toSubscriptionEntity(String tenantId, return subscriptionEntity; } - private AzureResourceManager.Authenticated createAzureClient(AzureEnvironment env, String tenantId) { + private ResourceManager.Authenticated createAzureClient(AzureEnvironment env, String tenantId) { AzureProfile profile = new AzureProfile(env); return configureAzure().authenticate(this.createTokenCredentialForTenant(tenantId), profile); } - private AzureResourceManager.Authenticated createAzureClient(AzureEnvironment env) { + private ResourceManager.Authenticated createAzureClient(AzureEnvironment env) { AzureProfile profile = new AzureProfile(env); return configureAzure().authenticate(this.rootCredentialSupplier.get(), profile); } /** - * TODO: share the same code for creating AzureResourceManager.Configurable + * TODO: share the same code for creating ResourceManager.Configurable */ - private static AzureResourceManager.Configurable configureAzure() { - OkHttpAsyncHttpClientBuilder builder = new OkHttpAsyncHttpClientBuilder(); - final AzureConfiguration config = Azure.az().config(); - if (StringUtils.isNotBlank(config.getProxySource())) { - final ProxyOptions proxyOptions = new ProxyOptions(ProxyOptions.Type.HTTP, - new InetSocketAddress(config.getHttpProxyHost(), config.getHttpProxyPort()) - ); - if (StringUtils.isNoneBlank(config.getProxyUsername(), config.getProxyPassword())) { - proxyOptions.setCredentials(config.getProxyUsername(), config.getProxyPassword()); - } - builder.proxy(proxyOptions); - } - + private static ResourceManager.Configurable configureAzure() { // disable retry for getting tenant and subscriptions - return AzureResourceManager.configure() - .withHttpClient(builder.build()) + return ResourceManager.configure() + .withHttpClient(AzureService.getDefaultHttpClient()) .withPolicy(createUserAgentPolicy()) .withRetryPolicy(new RetryPolicy(new FixedDelay(0, Duration.ofSeconds(0)))); } diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/pom.xml b/azure-toolkit-libs/azure-toolkit-common-lib/pom.xml index 42c0f195df..baa433b3b9 100644 --- a/azure-toolkit-libs/azure-toolkit-common-lib/pom.xml +++ b/azure-toolkit-libs/azure-toolkit-common-lib/pom.xml @@ -5,13 +5,17 @@ azure-toolkit-libs com.microsoft.azure - 0.11.0 + 0.12.1 4.0.0 azure-toolkit-common-lib + + com.azure + azure-core-http-netty + com.networknt json-schema-validator @@ -84,10 +88,6 @@ org.reflections reflections - - com.squareup.okhttp3 - okhttp - org.apache.httpcomponents httpclient @@ -120,6 +120,15 @@ + + + src/main/resources + true + + schema/**/*.json + + + diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/AzureService.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/AzureService.java index 0b6d63ecd1..a844d0fa72 100644 --- a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/AzureService.java +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/AzureService.java @@ -5,22 +5,124 @@ package com.microsoft.azure.toolkit.lib; +import com.azure.core.http.HttpClient; +import com.azure.core.http.ProxyOptions; +import com.azure.core.http.netty.NettyAsyncHttpClientBuilder; +import com.azure.core.http.policy.HttpLogDetailLevel; +import com.azure.core.http.policy.HttpPipelinePolicy; +import com.azure.core.management.profile.AzureProfile; +import com.azure.resourcemanager.resources.ResourceManager; +import com.azure.resourcemanager.resources.fluentcore.policy.ProviderRegistrationPolicy; +import com.azure.resourcemanager.resources.models.ProviderResourceType; +import com.azure.resourcemanager.resources.models.Providers; +import com.microsoft.azure.toolkit.lib.account.IAccount; import com.microsoft.azure.toolkit.lib.account.IAzureAccount; +import com.microsoft.azure.toolkit.lib.common.cache.Cacheable; +import com.microsoft.azure.toolkit.lib.common.entity.IAzureBaseResource; +import com.microsoft.azure.toolkit.lib.common.entity.IAzureModule; +import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException; +import com.microsoft.azure.toolkit.lib.common.model.Region; import com.microsoft.azure.toolkit.lib.common.model.Subscription; -import com.microsoft.azure.toolkit.lib.common.operation.AzureOperation; +import io.netty.resolver.AddressResolverGroup; +import io.netty.resolver.DefaultAddressResolverGroup; +import io.netty.resolver.NoopAddressResolverGroup; +import org.apache.commons.lang3.StringUtils; +import java.net.InetSocketAddress; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; -public interface AzureService { +import static com.microsoft.azure.toolkit.lib.Azure.az; + +public interface AzureService extends IAzureModule { default List getSubscriptions() { return Azure.az(IAzureAccount.class).account().getSelectedSubscriptions(); } - default String name() { - return this.getClass().getSimpleName(); + default List listSupportedRegions(String subscriptionId) { + String[] names = StringUtils.split(name(), "/"); + if (names.length != 2) { + throw new AzureToolkitRuntimeException(String.format("It is illegal to list regions for service '%s'.", name())); + } + final String provider = names[0]; + final String resourceType = names[1]; + List allRegionList = az(IAzureAccount.class).listRegions(subscriptionId); + List result = new ArrayList<>(); + final ResourceManager resourceManager = getResourceManager(subscriptionId); + resourceManager.providers().getByName(provider).resourceTypes() + .stream().filter(type -> StringUtils.equalsIgnoreCase(type.resourceType(), resourceType)) + .findAny().map(ProviderResourceType::locations) + .ifPresent(list -> result.addAll(list.stream().map(Region::fromName).filter(allRegionList::contains).collect(Collectors.toList()))); + return result.isEmpty() ? allRegionList : result; + } + + @Cacheable(cacheName = "resource/{}/manager", key = "$subscriptionId") + default ResourceManager getResourceManager(String subscriptionId) { + // make sure it is signed in. + final IAccount account = az(IAzureAccount.class).account(); + final AzureConfiguration config = Azure.az().config(); + final String userAgent = config.getUserAgent(); + final HttpLogDetailLevel logDetailLevel = config.getLogLevel() == null ? + HttpLogDetailLevel.NONE : HttpLogDetailLevel.valueOf(config.getLogLevel()); + final AzureProfile azureProfile = new AzureProfile(account.getEnvironment()); + + final Providers providers = ResourceManager.configure() + .withHttpClient(getDefaultHttpClient()) + .withPolicy(getUserAgentPolicy(userAgent)) + .authenticate(account.getTokenCredential(subscriptionId), azureProfile) + .withSubscription(subscriptionId).providers(); + return ResourceManager.configure() + .withHttpClient(getDefaultHttpClient()) + .withLogLevel(logDetailLevel) + .withPolicy(getUserAgentPolicy(userAgent)) // set user agent with policy + .withPolicy(new ProviderRegistrationPolicy(providers)) // add policy to auto register resource providers + .authenticate(account.getTokenCredential(subscriptionId), azureProfile) + .withSubscription(subscriptionId); + } + + static HttpPipelinePolicy getUserAgentPolicy(String userAgent) { + return (httpPipelineCallContext, httpPipelineNextPolicy) -> { + final String previousUserAgent = httpPipelineCallContext.getHttpRequest().getHeaders().getValue("User-Agent"); + httpPipelineCallContext.getHttpRequest().setHeader("User-Agent", String.format("%s %s", userAgent, previousUserAgent)); + return httpPipelineNextPolicy.process(); + }; + } + + class HttpClientHolder { + private static HttpClient defaultHttpClient = null; + + private static synchronized HttpClient createHttpClient() { + if (defaultHttpClient != null) { + return defaultHttpClient; + } + + AddressResolverGroup resolverGroup; + ProxyOptions proxyOptions = null; + final AzureConfiguration config = Azure.az().config(); + if (StringUtils.isNotBlank(config.getProxySource())) { + proxyOptions = new ProxyOptions(ProxyOptions.Type.HTTP, + new InetSocketAddress(config.getHttpProxyHost(), config.getHttpProxyPort()) + ); + if (StringUtils.isNoneBlank(config.getProxyUsername(), config.getProxyPassword())) { + proxyOptions.setCredentials(config.getProxyUsername(), config.getProxyPassword()); + } + resolverGroup = NoopAddressResolverGroup.INSTANCE; + } else { + resolverGroup = DefaultAddressResolverGroup.INSTANCE; + } + reactor.netty.http.client.HttpClient nettyHttpClient = + reactor.netty.http.client.HttpClient.create() + .resolver(resolverGroup); + NettyAsyncHttpClientBuilder builder = new NettyAsyncHttpClientBuilder(nettyHttpClient); + Optional.ofNullable(proxyOptions).map(proxy -> builder.proxy(proxy)); + defaultHttpClient = builder.build(); + return defaultHttpClient; + } } - @AzureOperation(name = "common|service.refresh", params = "this.name()", type = AzureOperation.Type.SERVICE) - default void refresh() { + static HttpClient getDefaultHttpClient() { + return HttpClientHolder.createHttpClient(); } } diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/account/IAccount.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/account/IAccount.java index 2fa82565a9..7af0ecf00b 100644 --- a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/account/IAccount.java +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/account/IAccount.java @@ -5,6 +5,8 @@ package com.microsoft.azure.toolkit.lib.account; +import com.azure.core.credential.TokenCredential; +import com.azure.core.management.AzureEnvironment; import com.microsoft.azure.toolkit.lib.common.model.Subscription; import java.util.List; @@ -18,4 +20,8 @@ public interface IAccount { Subscription getSubscription(String subscriptionId); String portalUrl(); + + AzureEnvironment getEnvironment(); + + TokenCredential getTokenCredential(String subscriptionId); } diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/account/IAzureAccount.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/account/IAzureAccount.java index bf5049426a..79f69be988 100644 --- a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/account/IAzureAccount.java +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/account/IAzureAccount.java @@ -6,7 +6,12 @@ package com.microsoft.azure.toolkit.lib.account; import com.microsoft.azure.toolkit.lib.AzureService; +import com.microsoft.azure.toolkit.lib.common.model.Region; + +import java.util.List; public interface IAzureAccount extends AzureService { IAccount account(); + + List listRegions(String subscriptionId); } diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/action/Action.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/action/Action.java new file mode 100644 index 0000000000..45a090a141 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/action/Action.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.common.action; + +import com.microsoft.azure.toolkit.lib.common.bundle.AzureString; +import com.microsoft.azure.toolkit.lib.common.entity.IAzureBaseResource; +import com.microsoft.azure.toolkit.lib.common.operation.AzureOperation; +import com.microsoft.azure.toolkit.lib.common.operation.IAzureOperation; +import com.microsoft.azure.toolkit.lib.common.task.AzureTask; +import com.microsoft.azure.toolkit.lib.common.task.AzureTaskManager; +import com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemetry; +import com.microsoft.azure.toolkit.lib.common.view.IView; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.apache.commons.lang3.StringUtils; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; +import java.util.function.Consumer; +import java.util.function.Predicate; + +@Accessors(chain = true, fluent = true) +public class Action { + public static final String SOURCE = "ACTION_SOURCE"; + public static final Id REQUIRE_AUTH = Id.of("action.common.requireAuth"); + @Nonnull + private List, BiConsumer>> handlers = new ArrayList<>(); + @Nullable + @Getter + private ActionView.Builder view; + @Setter + private boolean authRequired = true; + + public Action(@Nullable ActionView.Builder view) { + this.view = view; + } + + public Action(@Nonnull Consumer handler) { + this.registerHandler((d, e) -> true, (d, e) -> handler.accept(d)); + } + + public Action(@Nonnull BiConsumer handler) { + this.registerHandler((d, e) -> true, handler); + } + + public Action(@Nonnull Consumer handler, @Nullable ActionView.Builder view) { + this.view = view; + this.registerHandler((d, e) -> true, (d, e) -> handler.accept(d)); + } + + public Action(@Nonnull BiConsumer handler, @Nullable ActionView.Builder view) { + this.view = view; + this.registerHandler((d, e) -> true, handler); + } + + private Action(@Nonnull List, BiConsumer>> handlers, @Nullable ActionView.Builder view) { + this.view = view; + this.handlers = handlers; + } + + @Nullable + public IView.Label view(D source) { + return Objects.nonNull(this.view) ? this.view.toActionView(source) : null; + } + + @SuppressWarnings("unchecked") + public BiConsumer handler(D source, Object e) { + for (int i = this.handlers.size() - 1; i >= 0; i--) { + final AbstractMap.SimpleEntry, BiConsumer> p = this.handlers.get(i); + final BiPredicate condition = (BiPredicate) p.getKey(); + final BiConsumer handler = (BiConsumer) p.getValue(); + if (condition.test(source, e)) { + return handler; + } + } + return null; + } + + public void handle(D source, Object e) { + final Runnable runnable = () -> { + final BiConsumer handler = this.handler(source, e); + if (Objects.nonNull(handler)) { + final AzureString title = Optional.ofNullable(this.view).map(b -> b.title).map(t -> t.apply(source)) + .orElse(AzureString.fromString(IAzureOperation.UNKNOWN_NAME)); + final AzureTask task = new AzureTask<>(title, () -> handle(source, e, handler)); + task.setType(AzureOperation.Type.ACTION.name()); + AzureTaskManager.getInstance().runInBackground(task); + } + }; + if (this.authRequired) { + final Action requireAuth = AzureActionManager.getInstance().getAction(REQUIRE_AUTH); + if (Objects.nonNull(requireAuth)) { + requireAuth.handle(runnable, e); + } + } else { + runnable.run(); + } + } + + protected void handle(D source, Object e, BiConsumer handler) { + if (source instanceof IAzureBaseResource) { + AzureTelemetry.getActionContext().setProperty("subscriptionId", ((IAzureBaseResource) source).subscriptionId()); + AzureTelemetry.getActionContext().setProperty("resourceType", source.getClass().getSimpleName()); + } + handler.accept(source, e); + } + + public void handle(D source) { + this.handle(source, null); + } + + public void registerHandler(@Nonnull Predicate condition, @Nonnull Consumer handler) { + this.handlers.add(new AbstractMap.SimpleEntry<>((d, e) -> condition.test(d), (d, e) -> handler.accept(d))); + } + + public void registerHandler(@Nonnull BiPredicate condition, @Nonnull BiConsumer handler) { + this.handlers.add(new AbstractMap.SimpleEntry<>(condition, handler)); + } + + public static class Id { + @Nonnull + private final String id; + + private Id(@Nonnull String id) { + this.id = id; + } + + public static Id of(@Nonnull String id) { + assert StringUtils.isNotBlank(id) : "action id can not be blank"; + return new Id<>(id); + } + + @Nonnull + public String getId() { + return id; + } + } +} + diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/action/ActionGroup.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/action/ActionGroup.java new file mode 100644 index 0000000000..5c6c39957e --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/action/ActionGroup.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.common.action; + +import com.microsoft.azure.toolkit.lib.common.view.IView; +import lombok.Getter; +import lombok.experimental.Accessors; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.List; + +@Getter +@Accessors(chain = true, fluent = true) +public class ActionGroup { + @Nullable + private IView.Label view; + private final List actions; + + public ActionGroup(@Nonnull List actions) { + this.actions = actions; + } + + public ActionGroup(@Nonnull Object... actions) { + this.actions = Arrays.asList(actions); + } + + public ActionGroup(@Nonnull List actions, @Nullable IView.Label view) { + this.view = view; + this.actions = actions; + } + + @Getter + @Accessors(chain = true, fluent = true) + public static class Proxy extends ActionGroup { + @Nullable + private final String id; + @Nonnull + private final ActionGroup group; + + public Proxy(@Nonnull ActionGroup group) { + super(group.actions, group.view); + this.id = null; + this.group = group; + } + + public Proxy(@Nonnull ActionGroup group, @Nonnull String id) { + super(group.actions, group.view); + this.id = id; + this.group = group; + } + } +} diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/action/ActionView.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/action/ActionView.java new file mode 100644 index 0000000000..d53f19f252 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/action/ActionView.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.common.action; + +import com.microsoft.azure.toolkit.lib.common.bundle.AzureString; +import com.microsoft.azure.toolkit.lib.common.view.IView; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Optional; +import java.util.function.Function; + +@Getter +@RequiredArgsConstructor +@AllArgsConstructor +public class ActionView implements IView.Label { + + @Nonnull + private final String label; + private final String iconPath; + @Nullable + private AzureString title; + private final boolean enabled; + + @Override + public String getDescription() { + return Optional.ofNullable(this.title).map(AzureString::toString).orElse(null); + } + + @Override + public void dispose() { + } + + @RequiredArgsConstructor + @Setter + @Getter + @Accessors(chain = true, fluent = true) + public static class Builder { + @Nonnull + protected final Function label; + @Nullable + protected Function iconPath; + @Nullable + protected Function title; + @Nullable + protected Function enabled = s -> true; + + public Builder(String label) { + this(s -> label); + } + + public Builder(String label, String iconPath) { + this(s -> label); + this.iconPath = (s) -> iconPath; + } + + public ActionView toActionView(Object s) { + try { + final Boolean e = Optional.ofNullable(this.enabled).map(p -> p.apply(s)).orElse(true); + final String i = Optional.ofNullable(this.iconPath).map(p -> p.apply(s)).orElse(null); + final AzureString t = Optional.ofNullable(this.title).map(p -> p.apply(s)).orElse(null); + return new ActionView(this.label.apply(s), i, t, e); + } catch (final Exception e) { + return new ActionView("", "", false); + } + } + } +} diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/action/AzureActionManager.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/action/AzureActionManager.java new file mode 100644 index 0000000000..9dda6eaee0 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/action/AzureActionManager.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.common.action; + +import com.microsoft.azure.toolkit.lib.common.messager.AzureMessager; +import lombok.Getter; + +import javax.annotation.Nonnull; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; +import java.util.function.Consumer; +import java.util.function.Predicate; + +public abstract class AzureActionManager { + + @Getter + private static AzureActionManager instance; + + protected static void register(AzureActionManager manager) { + if (instance != null) { + AzureMessager.getDefaultMessager().warning("ActionManager is already registered", null); + return; + } + instance = manager; + } + + public abstract void registerAction(Action.Id id, Action action); + + public void registerAction(Action.Id id, Consumer action) { + this.registerAction(id, new Action<>(action)); + } + + public abstract Action getAction(Action.Id id); + + public abstract void registerGroup(String id, ActionGroup group); + + public abstract ActionGroup getGroup(String id); + + public void registerHandler(@Nonnull Action.Id id, @Nonnull Predicate condition, @Nonnull Consumer handler) { + final Action action = this.getAction(id); + action.registerHandler(condition, handler); + } + + public void registerHandler(@Nonnull Action.Id id, @Nonnull BiPredicate condition, @Nonnull BiConsumer handler) { + final Action action = this.getAction(id); + action.registerHandler(condition, handler); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/entity/IAzureBaseResource.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/entity/IAzureBaseResource.java new file mode 100644 index 0000000000..b40d5896aa --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/entity/IAzureBaseResource.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.common.entity; + +import com.azure.resourcemanager.resources.fluentcore.arm.ResourceId; +import com.microsoft.azure.toolkit.lib.Azure; +import com.microsoft.azure.toolkit.lib.account.IAccount; +import com.microsoft.azure.toolkit.lib.account.IAzureAccount; +import com.microsoft.azure.toolkit.lib.common.model.Subscription; + +import javax.annotation.Nullable; + +public interface IAzureBaseResource { + String REST_SEGMENT_JOB_MANAGEMENT_TENANTID = "/#@"; + String REST_SEGMENT_JOB_MANAGEMENT_RESOURCE = "/resource"; + + @Nullable + default P parent() { + return null; + } + + // todo: Change to Nonnull + @Nullable + default IAzureModule module() { + return null; + } + + IAzureBaseResource refresh(); + + boolean exists(); + + String name(); + + String id(); + + default String status() { + return null; + } + + default void refreshStatus() { + } + + default String subscriptionId() { + return ResourceId.fromString(id()).subscriptionId(); + } + + default String resourceGroup() { + return ResourceId.fromString(id()).resourceGroupName(); + } + + default Subscription subscription() { + return Azure.az(IAzureAccount.class).account().getSubscription(this.subscriptionId()); + } + + default String portalUrl() { + final IAccount account = Azure.az(IAzureAccount.class).account(); + Subscription subscription = account.getSubscription(this.subscriptionId()); + return account.portalUrl() + REST_SEGMENT_JOB_MANAGEMENT_TENANTID + subscription.getTenantId() + REST_SEGMENT_JOB_MANAGEMENT_RESOURCE + this.id(); + } + + interface Status { + // unstable states + String UNSTABLE = "UNSTABLE"; + String PENDING = "PENDING"; + + // Draft + String DRAFT = "DRAFT"; + + // stable states + String STABLE = "STABLE"; + String LOADING = "LOADING"; + String ERROR = "ERROR"; + String RUNNING = "RUNNING"; + String STOPPED = "STOPPED"; + String UNKNOWN = "UNKNOWN"; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/entity/IAzureModule.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/entity/IAzureModule.java new file mode 100644 index 0000000000..63ddc83881 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/entity/IAzureModule.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.common.entity; + +import com.microsoft.azure.toolkit.lib.common.operation.AzureOperation; + +import javax.annotation.Nullable; + +public interface IAzureModule { + + @Nullable + default P getParent() { + return null; + } + + default String name() { + return this.getClass().getSimpleName(); + } + + @AzureOperation(name = "common|service.refresh", params = "this.name()", type = AzureOperation.Type.SERVICE) + default void refresh() { + } +} + diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/entity/IAzureResource.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/entity/IAzureResource.java index e30f3ab3ea..2c6b2c56d0 100644 --- a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/entity/IAzureResource.java +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/entity/IAzureResource.java @@ -5,30 +5,14 @@ package com.microsoft.azure.toolkit.lib.common.entity; -import com.azure.resourcemanager.resources.fluentcore.arm.ResourceId; -import com.microsoft.azure.toolkit.lib.Azure; -import com.microsoft.azure.toolkit.lib.account.IAccount; -import com.microsoft.azure.toolkit.lib.account.IAzureAccount; -import com.microsoft.azure.toolkit.lib.common.model.Subscription; - -public interface IAzureResource { - - String REST_SEGMENT_JOB_MANAGEMENT_TENANTID = "/#@"; - String REST_SEGMENT_JOB_MANAGEMENT_RESOURCE = "/resource"; +import org.apache.commons.lang3.NotImplementedException; +import javax.annotation.Nullable; +public interface IAzureResource extends IAzureBaseResource { IAzureResource refresh(); - boolean exists(); - T entity(); - default String status() { - return null; - } - - default void refreshStatus() { - } - default String name() { return this.entity().getName(); } @@ -37,39 +21,13 @@ default String id() { return this.entity().getId(); } - default String subscriptionId() { - return ResourceId.fromString(id()).subscriptionId(); + @Nullable + default IAzureBaseResource parent() { + throw new NotImplementedException(); } - default String resourceGroup() { - return ResourceId.fromString(id()).resourceGroupName(); - } - - default Subscription subscription() { - return Azure.az(IAzureAccount.class).account().getSubscription(this.subscriptionId()); - } - - default String portalUrl() { - final IAccount account = Azure.az(IAzureAccount.class).account(); - Subscription subscription = account.getSubscription(this.subscriptionId()); - return account.portalUrl() - + REST_SEGMENT_JOB_MANAGEMENT_TENANTID - + subscription.getTenantId() - + REST_SEGMENT_JOB_MANAGEMENT_RESOURCE - + this.id(); - } - - interface Status { - // unstable states - String UNSTABLE = "UNSTABLE"; - String PENDING = "PENDING"; - - // stable states - String STABLE = "STABLE"; - String LOADING = "LOADING"; - String ERROR = "ERROR"; - String RUNNING = "RUNNING"; - String STOPPED = "STOPPED"; - String UNKNOWN = "UNKNOWN"; + @Nullable + default IAzureModule module() { + throw new NotImplementedException(); } } diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/exception/RestExceptionHandlerInterceptor.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/exception/RestExceptionHandlerInterceptor.java deleted file mode 100644 index c58b3b0d28..0000000000 --- a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/exception/RestExceptionHandlerInterceptor.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - */ - -package com.microsoft.azure.toolkit.lib.common.exception; - -import com.google.common.base.Throwables; -import com.microsoft.aad.adal4j.AuthenticationException; -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -import java.io.IOException; -import java.net.UnknownHostException; -import java.util.List; - -/** - * Interceptor to handle REST API related exceptions - */ -public class RestExceptionHandlerInterceptor implements Interceptor { - @Override - public Response intercept(final Chain chain) throws IOException { - try { - final Request request = chain.request(); - return chain.proceed(request); - } catch (final Exception ex) { - final List exceptions = Throwables.getCausalChain(ex); - if (exceptions.stream().anyMatch(e -> e instanceof UnknownHostException)) { - throw new AzureToolkitRuntimeException("Unknown host! You network condition maybe unstable, please try later."); - } else if (exceptions.stream().anyMatch(e -> e instanceof AuthenticationException)) { - throw new AzureToolkitRuntimeException("Invalid authentication! You may sign in again or run \"az login\" if using Azure CLI credential"); - } - throw ex; - } - } -} diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/AzureHtmlMessage.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/AzureHtmlMessage.java new file mode 100644 index 0000000000..5e55a1abbd --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/AzureHtmlMessage.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.common.messager; + +import com.microsoft.azure.toolkit.lib.common.bundle.AzureString; +import com.microsoft.azure.toolkit.lib.common.operation.IAzureOperation; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.util.regex.Pattern.compile; + +public class AzureHtmlMessage extends AzureMessage { + static final Pattern URL_PATTERN = compile("\\s+https?://(www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)"); + + public AzureHtmlMessage(@Nonnull Type type, @Nonnull AzureString message) { + super(type, message); + } + + public AzureHtmlMessage(IAzureMessage raw) { + super(raw.getType(), raw.getMessage()); + if (raw instanceof AzureMessage) { + this.setValueDecorator(((AzureMessage) raw).getValueDecorator()); + } + this.setTitle(raw.getTitle()); + this.setPayload(raw.getPayload()); + this.setActions(raw.getActions()); + } + + @Nonnull + @Override + public String getContent() { + return transformURLIntoLinks(super.getContent()); + } + + @Nullable + @Override + protected String getCause(@Nonnull Throwable throwable) { + final String color = getErrorColor(); + return Optional.ofNullable(super.getCause(throwable)) + .map(cause -> String.format("%s", color, cause)) + .orElse(null); + } + + @Nullable + @Override + protected String getErrorAction(@Nonnull Throwable throwable) { + return Optional.ofNullable(super.getErrorAction(throwable)) + .map(a -> String.format("

    %s

    ", a)) + .orElse(null); + } + + @Override + protected String getDetailItem(IAzureOperation o) { + return String.format("
  • %s
  • ", super.getDetailItem(o)); + } + + @Override + public String decorateValue(@Nonnull Object p, @Nullable Supplier dft) { + String result = super.decorateValue(p, null); + if (Objects.isNull(result)) { + final String color = getValueColor(); + final String font = "'JetBrains Mono', Consolas, 'Liberation Mono', Menlo, Courier, monospace"; + result = String.format("%s", color, font, p); + } + return Objects.isNull(result) && Objects.nonNull(dft) ? dft.get() : result; + } + + private static String transformURLIntoLinks(String text) { + final Matcher m = URL_PATTERN.matcher(text); + final StringBuffer sb = new StringBuffer(); + while (m.find()) { + final String found = m.group(0); + m.appendReplacement(sb, "" + found + ""); + } + m.appendTail(sb); + return sb.toString(); + } + + protected String getErrorColor() { + return "#FF0000"; + } + + protected String getValueColor() { + return "#0000FF"; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/AzureMessage.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/AzureMessage.java index 320f2bda99..8adf92469f 100644 --- a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/AzureMessage.java +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/AzureMessage.java @@ -8,6 +8,7 @@ import com.azure.core.exception.HttpResponseException; import com.azure.core.management.exception.ManagementException; import com.google.common.collect.Streams; +import com.microsoft.azure.toolkit.lib.common.action.Action; import com.microsoft.azure.toolkit.lib.common.bundle.AzureString; import com.microsoft.azure.toolkit.lib.common.cache.Cacheable; import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitException; @@ -52,7 +53,7 @@ public class AzureMessage implements IAzureMessage { @Nullable protected Object payload; @Nullable - protected Action[] actions; + protected Action[] actions; protected ValueDecorator valueDecorator; @Nonnull @@ -64,14 +65,8 @@ public String getContent() { final List operations = this.getOperations(); final String failure = operations.stream().findFirst().map(IAzureOperation::getTitle) .map(azureString -> "Failed to " + this.decorateText(azureString, azureString::getString)).orElse("Failed to proceed"); - final String cause = Optional.ofNullable(this.getCause(throwable)) - .map(StringUtils::uncapitalize) - .map(c -> "," + (c.endsWith(".") ? c : c + '.')) - .orElse(""); - final String errorAction = Optional.ofNullable(this.getErrorAction(throwable)) - .map(StringUtils::capitalize) - .map(c -> System.lineSeparator() + (c.endsWith(".") ? c : c + '.')) - .orElse(""); + final String cause = Optional.ofNullable(this.getCause(throwable)).map(c -> ", " + c).orElse(""); + final String errorAction = Optional.ofNullable(this.getErrorAction(throwable)).map(c -> System.lineSeparator() + c).orElse(""); return failure + cause + errorAction; } @@ -112,7 +107,12 @@ protected String getCause(@Nonnull Throwable throwable) { } else if (root instanceof HttpResponseException) { cause = ((HttpResponseException) root).getResponse().getBodyAsString().block(); } - return StringUtils.firstNonBlank(cause, root.getMessage()); + final String causeMsg = StringUtils.firstNonBlank(cause, root.getMessage()); + return Optional.ofNullable(causeMsg) + .filter(StringUtils::isNotBlank) + .map(StringUtils::uncapitalize) + .map(c -> c.endsWith(".") ? c : c + '.') + .orElse(null); } @Nullable @@ -138,6 +138,8 @@ protected String getErrorAction(@Nonnull Throwable throwable) { .map(t -> t instanceof AzureToolkitRuntimeException ? ((AzureToolkitRuntimeException) t).getAction() : ((AzureToolkitException) t).getAction()) .filter(StringUtils::isNotBlank) .findFirst() + .map(StringUtils::capitalize) + .map(c -> c.endsWith(".") ? c : c + '.') .orElse(null); } @@ -189,7 +191,7 @@ public String getTitle() { @Nonnull @Override - public Action[] getActions() { + public Action[] getActions() { return ObjectUtils.firstNonNull(this.actions, new Action[0]); } diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/IAzureMessage.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/IAzureMessage.java index 44e75b96dd..2ca2449c4d 100644 --- a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/IAzureMessage.java +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/IAzureMessage.java @@ -5,13 +5,12 @@ package com.microsoft.azure.toolkit.lib.common.messager; -import com.microsoft.azure.toolkit.lib.common.bundle.AzureBundle; +import com.microsoft.azure.toolkit.lib.common.action.Action; import com.microsoft.azure.toolkit.lib.common.bundle.AzureString; import com.microsoft.azure.toolkit.lib.common.bundle.CustomDecoratable; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.text.MessageFormat; import java.util.Arrays; import java.util.Objects; import java.util.function.Supplier; @@ -35,7 +34,7 @@ default String getContent() { Object getPayload(); @Nullable - Action[] getActions(); + Action[] getActions(); default boolean show() { return AzureMessager.getMessager().show(this); @@ -82,10 +81,4 @@ enum Type { interface ValueDecorator { String decorateValue(@Nonnull Object p, @Nullable IAzureMessage message); } - - interface Action { - String name(); - - void actionPerformed(IAzureMessage message); - } } diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/IAzureMessager.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/IAzureMessager.java index 392ca54359..d4e8eaff93 100644 --- a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/IAzureMessager.java +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/IAzureMessager.java @@ -1,5 +1,11 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + package com.microsoft.azure.toolkit.lib.common.messager; +import com.microsoft.azure.toolkit.lib.common.action.Action; import com.microsoft.azure.toolkit.lib.common.bundle.AzureString; import javax.annotation.Nonnull; @@ -9,39 +15,39 @@ public interface IAzureMessager { String DEFAULT_TITLE = "Azure"; - default void success(@Nonnull String message, String title, IAzureMessage.Action... actions) { + default void success(@Nonnull String message, String title, Action... actions) { this.show(this.buildMessage(IAzureMessage.Type.SUCCESS, AzureString.fromString(message), title, actions, null)); } - default void success(@Nonnull AzureString message, String title, IAzureMessage.Action... actions) { + default void success(@Nonnull AzureString message, String title, Action... actions) { this.show(this.buildMessage(IAzureMessage.Type.SUCCESS, message, title, actions, null)); } - default void info(@Nonnull String message, String title, IAzureMessage.Action... actions) { + default void info(@Nonnull String message, String title, Action... actions) { this.show(this.buildMessage(IAzureMessage.Type.INFO, AzureString.fromString(message), title, actions, null)); } - default void info(@Nonnull AzureString message, String title, IAzureMessage.Action... actions) { + default void info(@Nonnull AzureString message, String title, Action... actions) { this.show(this.buildMessage(IAzureMessage.Type.INFO, message, title, actions, null)); } - default void warning(@Nonnull String message, String title, IAzureMessage.Action... actions) { + default void warning(@Nonnull String message, String title, Action... actions) { this.show(this.buildMessage(IAzureMessage.Type.WARNING, AzureString.fromString(message), title, actions, null)); } - default void warning(@Nonnull AzureString message, String title, IAzureMessage.Action... actions) { + default void warning(@Nonnull AzureString message, String title, Action... actions) { this.show(this.buildMessage(IAzureMessage.Type.WARNING, message, title, actions, null)); } - default void error(@Nonnull String message, String title, IAzureMessage.Action... actions) { + default void error(@Nonnull String message, String title, Action... actions) { this.show(this.buildMessage(IAzureMessage.Type.ERROR, AzureString.fromString(message), title, actions, null)); } - default void error(@Nonnull AzureString message, String title, IAzureMessage.Action... actions) { + default void error(@Nonnull AzureString message, String title, Action... actions) { this.show(this.buildMessage(IAzureMessage.Type.ERROR, message, title, actions, null)); } - default void error(@Nonnull Throwable throwable, String title, IAzureMessage.Action... actions) { + default void error(@Nonnull Throwable throwable, String title, Action... actions) { final String message = Optional.ofNullable(throwable.getMessage()).orElse(throwable.getClass().getSimpleName()); this.show(this.buildMessage(IAzureMessage.Type.ERROR, AzureString.fromString(message), title, actions, throwable)); } @@ -63,39 +69,39 @@ default void alert(@Nonnull AzureString message, String title) { } default void success(@Nonnull String message, String title) { - this.success(message, title, new IAzureMessage.Action[0]); + this.success(message, title, new Action[0]); } default void success(@Nonnull AzureString message, String title) { - this.success(message, title, new IAzureMessage.Action[0]); + this.success(message, title, new Action[0]); } default void info(@Nonnull String message, String title) { - this.info(message, title, new IAzureMessage.Action[0]); + this.info(message, title, new Action[0]); } default void info(@Nonnull AzureString message, String title) { - this.info(message, title, new IAzureMessage.Action[0]); + this.info(message, title, new Action[0]); } default void warning(@Nonnull String message, String title) { - this.warning(message, title, new IAzureMessage.Action[0]); + this.warning(message, title, new Action[0]); } default void warning(@Nonnull AzureString message, String title) { - this.warning(message, title, new IAzureMessage.Action[0]); + this.warning(message, title, new Action[0]); } default void error(@Nonnull String message, String title) { - this.error(message, title, new IAzureMessage.Action[0]); + this.error(message, title, new Action[0]); } default void error(@Nonnull AzureString message, String title) { - this.error(message, title, new IAzureMessage.Action[0]); + this.error(message, title, new Action[0]); } default void error(@Nonnull Throwable throwable, String title) { - this.error(throwable, title, new IAzureMessage.Action[0]); + this.error(throwable, title, new Action[0]); } default boolean confirm(@Nonnull String message) { @@ -151,7 +157,7 @@ default void error(@Nonnull Throwable throwable) { } default IAzureMessage buildMessage(@Nonnull IAzureMessage.Type type, @Nonnull AzureString content, - @Nullable String title, @Nullable IAzureMessage.Action[] actions, @Nullable Object payload) { + @Nullable String title, @Nullable Action[] actions, @Nullable Object payload) { final AzureMessage message = new AzureMessage(type, content).setPayload(payload).setActions(actions).setTitle(title); if (this instanceof IAzureMessage.ValueDecorator) { message.setValueDecorator((IAzureMessage.ValueDecorator) this); diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/OpenInBrowserMessageAction.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/OpenInBrowserMessageAction.java deleted file mode 100644 index 645c4da27b..0000000000 --- a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/OpenInBrowserMessageAction.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.microsoft.azure.toolkit.lib.common.messager; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; - -import java.awt.*; -import java.net.URI; - -@RequiredArgsConstructor -@Getter -public class OpenInBrowserMessageAction implements IAzureMessage.Action { - private final String name; - private final String url; - - @Override - public String name() { - return this.name; - } - - @SneakyThrows - @Override - public void actionPerformed(IAzureMessage message) { - if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { - Desktop.getDesktop().browse(new URI(this.url)); - } - } - - @Override - public String toString() { - return String.format("[%s](%s)", this.name, this.url); - } -} \ No newline at end of file diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/SimpleMessageAction.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/SimpleMessageAction.java deleted file mode 100644 index 0cedfe72e6..0000000000 --- a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/messager/SimpleMessageAction.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.microsoft.azure.toolkit.lib.common.messager; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -import java.util.function.Consumer; - -@RequiredArgsConstructor -@Getter -public class SimpleMessageAction implements IAzureMessage.Action { - private final String name; - private final Consumer handler; - - @Override - public String name() { - return this.name; - } - - @Override - public void actionPerformed(IAzureMessage payload) { - this.handler.accept(payload); - } - - @Override - public String toString() { - return String.format("[%s]", this.name); - } -} \ No newline at end of file diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/model/Region.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/model/Region.java index e4480eb5cd..4f4d2e900c 100644 --- a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/model/Region.java +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/model/Region.java @@ -4,6 +4,7 @@ */ package com.microsoft.azure.toolkit.lib.common.model; +import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.google.common.collect.Sets; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; @@ -21,6 +22,7 @@ @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) public class Region implements ExpandableParameter { public static final Region US_EAST = new Region("eastus", "East US"); public static final Region US_EAST2 = new Region("eastus2", "East US 2"); diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/operation/AzureOperationAspect.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/operation/AzureOperationAspect.java index 5466920830..8dce594e55 100644 --- a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/operation/AzureOperationAspect.java +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/operation/AzureOperationAspect.java @@ -5,7 +5,7 @@ package com.microsoft.azure.toolkit.lib.common.operation; -import com.microsoft.azure.toolkit.lib.common.entity.IAzureResource; +import com.microsoft.azure.toolkit.lib.common.entity.IAzureBaseResource; import com.microsoft.azure.toolkit.lib.common.event.AzureEventBus; import com.microsoft.azure.toolkit.lib.common.event.AzureOperationEvent; import com.microsoft.azure.toolkit.lib.common.task.AzureTaskContext; @@ -72,8 +72,8 @@ public void afterThrowing(JoinPoint point, Throwable e) throws Throwable { final AzureOperationEvent event = new AzureOperationEvent(target, operation, AzureOperationEvent.Stage.ERROR); AzureEventBus.emit(operation.getName(), event); } - if (source instanceof IAzureResource) { - ((IAzureResource) source).refresh(); + if (source instanceof IAzureBaseResource) { + ((IAzureBaseResource) source).refresh(); } if (!(e instanceof RuntimeException)) { throw e; // do not wrap checked exception diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/task/AzureTask.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/task/AzureTask.java index 6706918db8..beac934feb 100644 --- a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/task/AzureTask.java +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/task/AzureTask.java @@ -38,6 +38,7 @@ public class AzureTask implements IAzureOperation { @Nonnull @Builder.Default private String type = "ASYNC"; + private Monitor monitor; public AzureTask() { this((Supplier) null); @@ -126,6 +127,7 @@ public AzureTask(@Nullable Object project, @Nullable AzureString title, boolean this.project = project; this.title = title; this.cancellable = cancellable; + this.monitor = new DefaultMonitor(); this.supplier = supplier; this.modality = modality; } @@ -152,4 +154,24 @@ public T execute() { public enum Modality { DEFAULT, ANY, NONE } + + public interface Monitor { + void cancel(); + + boolean isCancelled(); + } + + public static class DefaultMonitor implements Monitor { + private boolean cancelled = false; + + @Override + public void cancel() { + this.cancelled = true; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + } } diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/utils/Utils.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/utils/Utils.java index 15fa311055..df60f0cf14 100644 --- a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/utils/Utils.java +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/utils/Utils.java @@ -18,18 +18,20 @@ import java.io.InputStream; import java.math.BigInteger; import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import java.util.function.Predicate; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.stream.Collectors; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Predicate; public class Utils { private static final boolean isWindows = System.getProperty("os.name").contains("Windows"); @@ -38,6 +40,11 @@ public class Utils { private static final String WAR = "war"; private static final String EAR = "ear"; private static final String SUBSCRIPTIONS = "subscriptions"; + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyMMddHHmmss"); + + public static String getTimestamp() { + return DATE_FORMAT.format(new Date()); + } public static String getArtifactCompileVersion(File artifact) throws AzureExecutionException { try (JarFile jarFile = new JarFile(artifact)) { diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/validator/SchemaValidator.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/validator/SchemaValidator.java index 84cbe00e89..8299caa1ca 100644 --- a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/validator/SchemaValidator.java +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/validator/SchemaValidator.java @@ -9,11 +9,14 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.microsoft.azure.toolkit.lib.common.bundle.AzureString; +import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException; import com.microsoft.azure.toolkit.lib.common.messager.AzureMessager; import com.networknt.schema.JsonSchema; import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SpecVersion; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.reflections.Reflections; import org.reflections.scanners.ResourcesScanner; @@ -22,11 +25,13 @@ import javax.annotation.Nullable; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -35,6 +40,8 @@ import static com.fasterxml.jackson.databind.MapperFeature.AUTO_DETECT_IS_GETTERS; public class SchemaValidator { + private static final Path SCHEMA_ROOT = Paths.get("schema"); + private static final String INVALID_PARAMETER_ERROR_MESSAGE = "Invalid parameters founded, please correct the value with messages below:"; private final Map schemaMap = new HashMap<>(); private final JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); @@ -47,10 +54,13 @@ public class SchemaValidator { } private SchemaValidator() { - final Set resources = new Reflections("schema", new ResourcesScanner()).getResources(Pattern.compile(".*\\.json")); - resources.stream().map(resource -> Pair.of(resource, SchemaValidator.class.getResourceAsStream("/" + resource))) + Optional.of(new Reflections("schema/", new ResourcesScanner())) + .filter(reflections -> CollectionUtils.isNotEmpty(reflections.getStore().keySet())) + .map(reflections -> reflections.getResources(Pattern.compile(".*\\.json"))) + .orElse(Collections.emptySet()) + .stream().map(resource -> Pair.of(resource, SchemaValidator.class.getResourceAsStream("/" + resource))) .filter(pair -> pair.getValue() != null) - .forEach(pair -> registerSchema(FilenameUtils.getBaseName(pair.getKey()), pair.getValue())); + .forEach(pair -> registerSchema(getSchemaId(pair.getKey()), pair.getValue())); } public static SchemaValidator getInstance() { @@ -81,20 +91,39 @@ public List validate(@Nonnull final String schemaId, @Nonnull return validate(schemaId, objectMapper.convertValue(value, JsonNode.class), pathPrefix); } - public List validate(@Nonnull final String schemaId, @Nonnull final JsonNode value) { - return validate(schemaId, value, "$"); - } - public List validate(@Nonnull final String schemaId, @Nonnull final JsonNode value, @Nullable final String pathPrefix) { if (!schemaMap.containsKey(schemaId)) { AzureMessager.getMessager().warning(AzureString.format("Skip validation as schema %s was not registered", schemaId)); return Collections.emptyList(); } - return validate(schemaMap.get(schemaId), value, pathPrefix); + return schemaMap.get(schemaId).validate(value, value, pathPrefix).stream().map(ValidationMessage::fromRawMessage).collect(Collectors.toList()); } - private List validate(@Nonnull final JsonSchema schema, @Nonnull final JsonNode value, @Nullable final String pathPrefix) { - return schema.validate(value, value, pathPrefix).stream().map(ValidationMessage::fromRawMessage).collect(Collectors.toList()); + public void validateAndThrow(@Nonnull final String schemaId, @Nonnull final Object value) { + validateAndThrow(schemaId, value, "$"); + } + + public void validateAndThrow(@Nonnull final String schemaId, @Nonnull final Object value, @Nullable final String pathPrefix) { + validateAndThrow(schemaId, objectMapper.convertValue(value, JsonNode.class), pathPrefix); + } + + public void validateAndThrow(@Nonnull final String schemaId, @Nonnull final JsonNode value, @Nullable final String pathPrefix) { + final List result = validate(schemaId, value, pathPrefix); + if (CollectionUtils.isNotEmpty(result)) { + final String errorDetails = result.stream().map(message -> message.getMessage().toString()).collect(Collectors.joining(StringUtils.LF)); + throw new AzureToolkitRuntimeException(String.join(StringUtils.LF, INVALID_PARAMETER_ERROR_MESSAGE, errorDetails)); + } + } + + private static String getSchemaId(final String path) { + try { + final Path schemaPath = Paths.get(FilenameUtils.removeExtension(path)); + final Path relativePath = SCHEMA_ROOT.relativize(schemaPath); + return FilenameUtils.separatorsToUnix(relativePath.toString()); + } catch (IllegalArgumentException e) { + // fallback to schema path for path parse issue + return path; + } } private static class LazyHolder { diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/view/IView.java b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/view/IView.java new file mode 100644 index 0000000000..97ffb5bc04 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/java/com/microsoft/azure/toolkit/lib/common/view/IView.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.common.view; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface IView { + + void dispose(); + + void refresh(); + + interface Label extends IView { + String getLabel(); + + String getIconPath(); + + String getDescription(); + + default boolean isEnabled() { + return true; + } + + default void refresh() { + } + + @Getter + @RequiredArgsConstructor + @AllArgsConstructor + class Static implements Label { + @Nonnull + protected final String label; + @Nullable + protected String iconPath; + @Nullable + protected String description; + + public Static(String title, String iconPath) { + this(title, iconPath, null); + } + + @Override + public void dispose() { + } + } + } +} diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/appservice/AppServiceName.json b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/appservice/AppServiceName.json new file mode 100644 index 0000000000..5bc6a7c4fe --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/appservice/AppServiceName.json @@ -0,0 +1,9 @@ +{ + "title": "App Service Name", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Name for Azure App Service", + "type": "string", + "pattern": "^[a-zA-Z0-9\\-]+$", + "minLength": 2, + "maxLength": 60 +} diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/appservice/AppServicePlanName.json b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/appservice/AppServicePlanName.json new file mode 100644 index 0000000000..954261e4bf --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/appservice/AppServicePlanName.json @@ -0,0 +1,9 @@ +{ + "title": "App Service Plan Name", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Name for Azure App Service Plan", + "type": "string", + "pattern": "^[a-zA-Z0-9\\-]+$", + "minLength": 1, + "maxLength": 40 +} diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/appservice/DeploymentSlotName.json b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/appservice/DeploymentSlotName.json new file mode 100644 index 0000000000..4e1fdf3efd --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/appservice/DeploymentSlotName.json @@ -0,0 +1,9 @@ +{ + "title": "App Service Deployment Slot Name", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Name for Azure App Service deployment slot", + "type": "string", + "pattern": "^[A-Za-z0-9-]+$", + "minLength": 1, + "maxLength": 60 +} diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/appservice/Runtime.json b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/appservice/Runtime.json new file mode 100644 index 0000000000..4d9db6abe8 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/appservice/Runtime.json @@ -0,0 +1,42 @@ +{ + "title": "Runtime", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Runtime configuration for Maven plugin for App Service", + "type": "object", + "properties": { + "os": { + "$ref": "#/definitions/os" + }, + "webContainer": { + "$ref": "classpath:///schema/common/NonEmptyString.json" + }, + "javaVersion": { + "$ref": "classpath:///schema/common/NonEmptyString.json" + }, + "image": { + "$ref": "classpath:///schema/common/NonEmptyString.json" + }, + "serverId": { + "$ref": "classpath:///schema/common/NonEmptyString.json" + }, + "registryUrl": { + "type": "string", + "pattern": "^https.*" + } + }, + "dependencies": { + "serverId": [ + "image" + ], + "registryUrl": [ + "image" + ] + }, + "definitions": { + "os": { + "description": "The operating system for app service", + "type": "string", + "pattern": "(?i)^(windows|linux|docker)$" + } + } +} diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/common/AuthConfiguration.json b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/common/AuthConfiguration.json new file mode 100644 index 0000000000..98c3218dd4 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/common/AuthConfiguration.json @@ -0,0 +1,81 @@ +{ + "title": "AuthConfiguration", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "The auth config for accessing azure resources", + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/auth-type" + }, + "client": { + "$ref": "classpath:///schema/common/UUID.json" + }, + "tenant": { + "$ref": "classpath:///schema/common/UUID.json" + }, + "key": { + "description": "Password", + "type": "string" + }, + "certificate": { + "description": "The absolute path of your certificate", + "type": "string" + }, + "certificatePassword": { + "description": "The password for your certificate, if there is any", + "type": "string" + }, + "environment": { + "$ref": "classpath:///schema/common/AzureEnvironment.json" + } + }, + "allOf": [ + { + "if": { + "properties": { + "type": { + "pattern": "(?i)^service_principal$" + } + }, + "required": [ + "type" + ] + }, + "then": { + "anyOf": [ + { + "required": [ + "client", + "tenant", + "key" + ] + }, + { + "required": [ + "client", + "tenant", + "certificate" + ] + } + ] + } + } + ], + "not": { + "required": [ + "key", + "certificate" + ] + }, + "dependencies": { + "certificatePassword": [ + "certificate" + ] + }, + "definitions": { + "auth-type": { + "type": "string", + "pattern": "(?i)^(auto|service_principal|managed_identity|azure_cli|vscode|intellij|azure_auth_maven_plugin|device_code|oauth2|visual_studio)$" + } + } +} diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/common/AzureEnvironment.json b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/common/AzureEnvironment.json new file mode 100644 index 0000000000..cda609e0f4 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/common/AzureEnvironment.json @@ -0,0 +1,8 @@ +{ + "title": "Azure Environment", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "The Azure cloud environment", + "type": "string", + "default": "AZURE", + "pattern": "(?i)^(AZURE|AZURE_CHINA|AZURE_GERMANY|AZURE_US_GOVERNMENT)$" +} diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/common/NonEmptyString.json b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/common/NonEmptyString.json new file mode 100644 index 0000000000..29523c9705 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/common/NonEmptyString.json @@ -0,0 +1,7 @@ +{ + "title": "None Empty String", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Schema for non-empty string", + "type": "string", + "minLength": 1 +} diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/common/ResourceGroupName.json b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/common/ResourceGroupName.json new file mode 100644 index 0000000000..7883f0a8bc --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/common/ResourceGroupName.json @@ -0,0 +1,9 @@ +{ + "title": "Azure Resource Group", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Name for Azure Resource Group", + "type": "string", + "pattern": "^[a-zA-Z0-9\\.\\_\\-\\(\\)]+$", + "minLength": 2, + "maxLength": 90 +} diff --git a/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/common/UUID.json b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/common/UUID.json new file mode 100644 index 0000000000..5bd02ac18f --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-common-lib/src/main/resources/schema/common/UUID.json @@ -0,0 +1,7 @@ +{ + "title": "UUID", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Universally Unique Identifier", + "type": "string", + "format": "uuid" +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/pom.xml b/azure-toolkit-libs/azure-toolkit-compute-lib/pom.xml new file mode 100644 index 0000000000..f026327e98 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/pom.xml @@ -0,0 +1,121 @@ + + + + azure-toolkit-libs + com.microsoft.azure + 0.12.1 + + 4.0.0 + + azure-toolkit-compute-lib + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + + + com.nickwongdev + aspectj-maven-plugin + + false + 1.8 + 1.8 + ignore + 1.8 + UTF-8 + false + true + true + + + + com.microsoft.azure + azure-toolkit-common-lib + + + + + + compile-with-aspectj + process-classes + + + ${project.build.directory}/classes + + + + compile + + + + test-compile-with-aspectj + process-test-classes + + test-compile + + + + ${project.build.directory}/test-classes + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + private + false + + + + attach-javadocs + + jar + + + + + + + + + org.projectlombok + lombok + compile + + + com.azure.resourcemanager + azure-resourcemanager-compute + + + com.microsoft.azure + azure-toolkit-auth-lib + + + com.microsoft.azure + azure-toolkit-common-lib + + + com.microsoft.azure + azure-toolkit-storage-lib + + + com.microsoft.azure + azure-toolkit-resource-lib + + + + diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/AbstractAzureResource.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/AbstractAzureResource.java new file mode 100644 index 0000000000..f927421e88 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/AbstractAzureResource.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute; + +import com.azure.core.management.exception.ManagementException; +import com.azure.resourcemanager.resources.fluentcore.arm.ResourceId; +import com.azure.resourcemanager.resources.fluentcore.arm.models.HasId; +import com.microsoft.azure.toolkit.lib.common.entity.IAzureBaseResource; +import com.microsoft.azure.toolkit.lib.common.event.AzureEventBus; +import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException; +import com.microsoft.azure.toolkit.lib.common.task.AzureTaskManager; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpStatus; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Objects; + +@NoArgsConstructor +public abstract class AbstractAzureResource implements IAzureBaseResource, P> { + @Getter + protected String name; + @Getter + protected String resourceGroup; + @Getter + protected String subscriptionId; + @Getter + protected String id; + + protected T remote; + protected String status = null; + protected boolean isRefreshed = false; + + public AbstractAzureResource(@Nonnull final String id) { + final ResourceId resourceId = ResourceId.fromString(id); + this.id = id; + this.name = resourceId.name(); + this.resourceGroup = resourceId.resourceGroupName(); + this.subscriptionId = resourceId.subscriptionId(); + } + + public AbstractAzureResource(@Nonnull final T resource) { + this(resource.id()); + this.remote = resource; + this.id = resource.id(); + } + + @Override + public AbstractAzureResource refresh() { + try { + this.remote = loadRemote(); + this.isRefreshed = true; + } catch (final ManagementException e) { + if (HttpStatus.SC_NOT_FOUND == e.getResponse().getStatusCode()) { + this.remote = null; + this.isRefreshed = true; + } else { + throw e; + } + } + return this; + } + + @Override + public boolean exists() { + if (Objects.isNull(this.remote) && !this.isRefreshed) { + this.refresh(); + } + return Objects.nonNull(this.remote); + } + + @Override + public String name() { + return this.name; + } + + @Override + public String id() { + return getId(); + } + + @Nonnull + protected final T remote() { + if (!exists()) { + throw new AzureToolkitRuntimeException(String.format("Target resource %s does not exist.", name)); + } + return this.remote; + } + + @Override + public String status() { + if (Objects.nonNull(this.status)) { + return this.status; + } else { + this.refreshStatus(); + return Status.LOADING; + } + } + + public final void refreshStatus() { + AzureTaskManager.getInstance().runOnPooledThread(() -> this.status(this.refresh().loadStatus())); + } + + protected final void status(@Nonnull String status) { + final String oldStatus = this.status; + this.status = status; + if (!StringUtils.equalsIgnoreCase(oldStatus, this.status)) { + AzureEventBus.emit("common|resource.status_changed", this); + } + } + + @Nullable + protected abstract T loadRemote(); + + protected String loadStatus() { + return Status.RUNNING; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/AbstractAzureResourceModule.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/AbstractAzureResourceModule.java new file mode 100644 index 0000000000..fd1f571442 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/AbstractAzureResourceModule.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ +package com.microsoft.azure.toolkit.lib.compute; + +import com.azure.core.credential.TokenCredential; +import com.azure.core.http.policy.HttpLogDetailLevel; +import com.azure.core.http.policy.HttpPipelinePolicy; +import com.azure.core.management.profile.AzureProfile; +import com.azure.resourcemanager.resources.fluentcore.arm.AzureConfigurable; +import com.azure.resourcemanager.resources.fluentcore.arm.Manager; +import com.azure.resourcemanager.resources.fluentcore.arm.ResourceId; +import com.microsoft.azure.toolkit.lib.Azure; +import com.microsoft.azure.toolkit.lib.AzureConfiguration; +import com.microsoft.azure.toolkit.lib.AzureService; +import com.microsoft.azure.toolkit.lib.SubscriptionScoped; +import com.microsoft.azure.toolkit.lib.auth.Account; +import com.microsoft.azure.toolkit.lib.auth.AzureAccount; +import com.microsoft.azure.toolkit.lib.common.entity.IAzureBaseResource; +import com.microsoft.azure.toolkit.lib.common.model.Subscription; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public abstract class AbstractAzureResourceModule extends SubscriptionScoped> + implements AzureService { + + public AbstractAzureResourceModule(@NotNull Function, AbstractAzureResourceModule> creator, + @Nullable List subscriptions) { + super(creator, subscriptions); + } + + public AbstractAzureResourceModule(@NotNull Function, AbstractAzureResourceModule> creator) { + super(creator); + } + + public List list() { + return getSubscriptions().stream().parallel() + .flatMap(subscription -> list(subscription.getId()).stream()) + .collect(Collectors.toList()); + } + + @Nonnull + public T get(@Nonnull final String id) { + final ResourceId resourceId = ResourceId.fromString(id); + return get(resourceId.subscriptionId(), resourceId.resourceGroupName(), resourceId.name()); + } + + @Nonnull + public T get(@Nonnull final String resourceGroup, @Nonnull final String name) { + return get(getDefaultSubscription().getId(), resourceGroup, name); + } + + protected abstract List list(@Nonnull final String subscriptionId); + + @Nonnull + protected abstract T get(@Nonnull final String subscriptionId, @Nonnull final String resourceGroup, @Nonnull final String name); + + protected static , T extends Manager> T getResourceManager( + final String subscriptionId, Supplier> configurableSupplier, AuthenticationMethod authenticationMethod) { + final Account account = Azure.az(AzureAccount.class).account(); + final AzureConfiguration config = Azure.az().config(); + final String userAgent = config.getUserAgent(); + final HttpLogDetailLevel logLevel = Optional.ofNullable(config.getLogLevel()).map(HttpLogDetailLevel::valueOf).orElse(HttpLogDetailLevel.NONE); + final AzureProfile azureProfile = new AzureProfile(null, subscriptionId, account.getEnvironment()); + final TokenCredential tokenCredential = account.getTokenCredential(subscriptionId); + final R configurable = configurableSupplier.get().withPolicy(getUserAgentPolicy(userAgent)).withLogLevel(logLevel); + return authenticationMethod.apply(configurable, tokenCredential, azureProfile); + } + + @FunctionalInterface + protected interface AuthenticationMethod, T extends Manager> { + T apply(R configurable, TokenCredential tokenCredential, AzureProfile azureProfile); + } + + protected static HttpPipelinePolicy getUserAgentPolicy(String userAgent) { + return (httpPipelineCallContext, httpPipelineNextPolicy) -> { + final String previousUserAgent = httpPipelineCallContext.getHttpRequest().getHeaders().getValue("User-Agent"); + httpPipelineCallContext.getHttpRequest().setHeader("User-Agent", String.format("%s %s", userAgent, previousUserAgent)); + return httpPipelineNextPolicy.process(); + }; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/AzureResourceDraft.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/AzureResourceDraft.java new file mode 100644 index 0000000000..ab556b56ba --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/AzureResourceDraft.java @@ -0,0 +1,12 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute; + +import com.microsoft.azure.toolkit.lib.common.entity.IAzureBaseResource; + +public interface AzureResourceDraft { + +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/ip/AzurePublicIpAddress.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/ip/AzurePublicIpAddress.java new file mode 100644 index 0000000000..c5fabd769d --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/ip/AzurePublicIpAddress.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.ip; + +import com.azure.resourcemanager.compute.ComputeManager; +import com.azure.resourcemanager.network.models.PublicIpAddresses; +import com.microsoft.azure.toolkit.lib.common.event.AzureOperationEvent; +import com.microsoft.azure.toolkit.lib.common.model.Subscription; +import com.microsoft.azure.toolkit.lib.compute.AbstractAzureResourceModule; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.stream.Collectors; + +public class AzurePublicIpAddress extends AbstractAzureResourceModule implements AzureOperationEvent.Source { + + public AzurePublicIpAddress() { + super(AzurePublicIpAddress::new); + } + + private AzurePublicIpAddress(@Nonnull final List subscriptions) { + super(AzurePublicIpAddress::new, subscriptions); + } + + @Override + public List list(@Nonnull String subscriptionId) { + return getPublicIpAddressManager(subscriptionId).list().stream().map(ip -> new PublicIpAddress(ip, this)).collect(Collectors.toList()); + } + + @Nonnull + @Override + public PublicIpAddress get(@Nonnull final String subscriptionId, @Nonnull final String resourceGroup, @Nonnull final String name) { + final PublicIpAddresses virtualMachinesManager = getPublicIpAddressManager(subscriptionId); + return new PublicIpAddress(virtualMachinesManager.getByResourceGroup(resourceGroup, name), this); + } + + public PublicIpAddress create(@Nonnull final DraftPublicIpAddress draftPublicIpAddress) { + return draftPublicIpAddress.create(this); + } + + PublicIpAddresses getPublicIpAddressManager(String subscriptionId) { + return getResourceManager(subscriptionId, ComputeManager::configure, ComputeManager.Configurable::authenticate).networkManager().publicIpAddresses(); + } + + @Override + public String name() { + return "PublicIpAddress"; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/ip/DraftPublicIpAddress.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/ip/DraftPublicIpAddress.java new file mode 100644 index 0000000000..faa2b40545 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/ip/DraftPublicIpAddress.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.ip; + +import com.azure.resourcemanager.resources.fluentcore.arm.models.HasId; +import com.microsoft.azure.toolkit.lib.common.model.Region; +import com.microsoft.azure.toolkit.lib.common.utils.Utils; +import com.microsoft.azure.toolkit.lib.compute.AzureResourceDraft; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.jetbrains.annotations.Nullable; + +import javax.annotation.Nonnull; +import java.util.Optional; + +@Getter +@Setter +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class DraftPublicIpAddress extends PublicIpAddress implements AzureResourceDraft { + private Region region; + private String leafDomainLabel; + + public DraftPublicIpAddress(@Nonnull final String subscriptionId, @Nonnull final String resourceGroup, @Nonnull final String name) { + super(getResourceId(subscriptionId, resourceGroup, name), null); + } + + public static DraftPublicIpAddress getDefaultPublicIpAddressDraft() { + final DraftPublicIpAddress publicIpAddress = new DraftPublicIpAddress(); + publicIpAddress.setName(String.format("public-ip-%s", Utils.getTimestamp())); + return publicIpAddress; + } + + public void setSubscriptionId(final String subscriptionId) { + this.subscriptionId = subscriptionId; + } + + public void setResourceGroup(final String resourceGroup) { + this.resourceGroup = resourceGroup; + } + + public void setName(final String name) { + this.name = name; + } + + @Override + public boolean hasAssignedNetworkInterface() { + return Optional.ofNullable(remote).map(ignore -> super.hasAssignedNetworkInterface()).orElse(false); + } + + @Override + public String getId() { + return Optional.ofNullable(remote).map(HasId::id).orElseGet(() -> getResourceId(subscriptionId, resourceGroup, name)); + } + + PublicIpAddress create(AzurePublicIpAddress module) { + this.module = module; + this.remote = module.getPublicIpAddressManager(subscriptionId).define(name) + .withRegion(region.getName()) + .withExistingResourceGroup(resourceGroup) + .withLeafDomainLabel(leafDomainLabel) + .create(); + refreshStatus(); + module.refresh(); + return this; + } + + @Override + public String status() { + return Optional.ofNullable(remote).map(ignore -> super.status()).orElse(Status.DRAFT); + } + + @Nullable + @Override + protected com.azure.resourcemanager.network.models.PublicIpAddress loadRemote() { + return Optional.ofNullable(remote).map(ignore -> super.loadRemote()).orElse(null); + } + + private static String getResourceId(@Nonnull final String subscriptionId, @Nonnull final String resourceGroup, @Nonnull final String name) { + return String.format("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/publicIPAddresses/%s", subscriptionId, resourceGroup, name); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/ip/PublicIpAddress.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/ip/PublicIpAddress.java new file mode 100644 index 0000000000..187f690f5a --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/ip/PublicIpAddress.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.ip; + +import com.microsoft.azure.toolkit.lib.common.entity.IAzureBaseResource; +import com.microsoft.azure.toolkit.lib.common.entity.IAzureModule; +import com.microsoft.azure.toolkit.lib.common.event.AzureOperationEvent; +import com.microsoft.azure.toolkit.lib.common.model.Region; +import com.microsoft.azure.toolkit.lib.compute.AbstractAzureResource; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class PublicIpAddress extends AbstractAzureResource + implements AzureOperationEvent.Source { + + protected AzurePublicIpAddress module; + + public PublicIpAddress(@Nonnull final String id, final AzurePublicIpAddress azureClient) { + super(id); + this.module = azureClient; + } + + public PublicIpAddress(@Nonnull final com.azure.resourcemanager.network.models.PublicIpAddress resource, final AzurePublicIpAddress module) { + super(resource); + this.module = module; + } + + @Override + public IAzureModule, ? extends IAzureBaseResource> module() { + return module; + } + + public Region getRegion() { + return Region.fromName(remote().regionName()); + } + + public boolean hasAssignedNetworkInterface() { + return remote().hasAssignedNetworkInterface(); + } + + @Nullable + @Override + protected com.azure.resourcemanager.network.models.PublicIpAddress loadRemote() { + return module.getPublicIpAddressManager(getSubscriptionId()).getByResourceGroup(resourceGroup(), name()); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/network/AzureNetwork.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/network/AzureNetwork.java new file mode 100644 index 0000000000..ec2a01c13f --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/network/AzureNetwork.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.network; + +import com.azure.resourcemanager.compute.ComputeManager; +import com.azure.resourcemanager.network.models.Networks; +import com.microsoft.azure.toolkit.lib.common.event.AzureOperationEvent; +import com.microsoft.azure.toolkit.lib.common.model.Subscription; +import com.microsoft.azure.toolkit.lib.compute.AbstractAzureResourceModule; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.stream.Collectors; + +public class AzureNetwork extends AbstractAzureResourceModule implements AzureOperationEvent.Source { + public AzureNetwork() { + super(AzureNetwork::new); + } + + private AzureNetwork(@Nonnull final List subscriptions) { + super(AzureNetwork::new, subscriptions); + } + + @Override + public List list(@Nonnull String subscriptionId) { + return getNetworkManager(subscriptionId).list().stream().map(network -> new Network(network, this)).collect(Collectors.toList()); + } + + @Nonnull + @Override + public Network get(@Nonnull String subscriptionId, @Nonnull String resourceGroup, @Nonnull String name) { + final Networks networks = getNetworkManager(subscriptionId); + return new Network(networks.getByResourceGroup(resourceGroup, name), this); + } + + public Network create(@Nonnull final DraftNetwork draftNetwork) { + return draftNetwork.create(this); + } + + Networks getNetworkManager(@Nonnull final String subscriptionId) { + return getResourceManager(subscriptionId, ComputeManager::configure, ComputeManager.Configurable::authenticate).networkManager().networks(); + } + + @Override + public String name() { + return "Network"; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/network/DraftNetwork.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/network/DraftNetwork.java new file mode 100644 index 0000000000..244866984a --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/network/DraftNetwork.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.network; + +import com.azure.resourcemanager.network.models.Network.DefinitionStages.WithCreateAndSubnet; +import com.azure.resourcemanager.resources.fluentcore.arm.models.HasId; +import com.microsoft.azure.toolkit.lib.common.model.Region; +import com.microsoft.azure.toolkit.lib.common.utils.Utils; +import com.microsoft.azure.toolkit.lib.compute.AzureResourceDraft; +import com.microsoft.azure.toolkit.lib.compute.network.model.Subnet; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; + +import javax.annotation.Nonnull; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +@Getter +@Setter +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class DraftNetwork extends Network implements AzureResourceDraft { + + private Region region; + private String addressSpace; + + private String subnet; + private String subnetAddressSpace; + + public DraftNetwork(@Nonnull final String subscriptionId, @Nonnull final String resourceGroup, @Nonnull final String name) { + super(getResourceId(subscriptionId, resourceGroup, name), null); + } + + public static DraftNetwork getDefaultNetworkDraft() { + final DraftNetwork draftNetwork = new DraftNetwork(); + draftNetwork.setName(String.format("network-%s", Utils.getTimestamp())); + draftNetwork.setAddressSpace("10.0.2.0/24"); + draftNetwork.setSubnet("default"); + draftNetwork.setSubnetAddressSpace("10.0.2.0/24"); + return draftNetwork; + } + + public void setSubscriptionId(final String subscriptionId) { + this.subscriptionId = subscriptionId; + } + + public void setResourceGroup(final String resourceGroup) { + this.resourceGroup = resourceGroup; + } + + public void setName(final String name) { + this.name = name; + } + + @Override + public String getId() { + return Optional.ofNullable(remote).map(HasId::id).orElseGet(() -> getResourceId(subscriptionId, resourceGroup, name)); + } + + Network create(final AzureNetwork module) { + this.module = module; + WithCreateAndSubnet withCreateAndSubnet = module.getNetworkManager(subscriptionId).define(name) + .withRegion(region.getName()) + .withExistingResourceGroup(resourceGroup) + .withAddressSpace(addressSpace); + if (StringUtils.isNotEmpty(subnet)) { + withCreateAndSubnet = withCreateAndSubnet.withSubnet(subnet, subnetAddressSpace); + } + this.remote = withCreateAndSubnet.create(); + refreshStatus(); + module.refresh(); + return this; + } + + @Override + public List subnets() { + return Optional.ofNullable(remote).map(ignore -> super.subnets()).orElseGet(() -> Collections.singletonList(new Subnet(subnet, subnetAddressSpace))); + } + + @Override + public String status() { + return Optional.ofNullable(remote).map(ignore -> super.status()).orElse(Status.DRAFT); + } + + @Nullable + @Override + protected com.azure.resourcemanager.network.models.Network loadRemote() { + return Optional.ofNullable(remote).map(ignore -> super.loadRemote()).orElse(null); + } + + private static String getResourceId(@Nonnull final String subscriptionId, @Nonnull final String resourceGroup, @Nonnull final String name) { + return String.format("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s", subscriptionId, resourceGroup, name); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/network/Network.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/network/Network.java new file mode 100644 index 0000000000..1eb56ea20b --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/network/Network.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.network; + +import com.microsoft.azure.toolkit.lib.common.entity.IAzureBaseResource; +import com.microsoft.azure.toolkit.lib.common.entity.IAzureModule; +import com.microsoft.azure.toolkit.lib.common.event.AzureOperationEvent; +import com.microsoft.azure.toolkit.lib.common.model.Region; +import com.microsoft.azure.toolkit.lib.compute.AbstractAzureResource; +import com.microsoft.azure.toolkit.lib.compute.network.model.Subnet; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.stream.Collectors; + +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class Network extends AbstractAzureResource + implements AzureOperationEvent.Source { + protected AzureNetwork module; + + public Network(@Nonnull String id, @Nullable final AzureNetwork module) { + super(id); + this.module = module; + } + + public Network(@Nonnull final com.azure.resourcemanager.network.models.Network resource, @Nonnull final AzureNetwork module) { + super(resource); + this.module = module; + } + + public List subnets() { + return remote.subnets().values().stream().map(Subnet::new).collect(Collectors.toList()); + } + + @Override + public IAzureModule, + ? extends IAzureBaseResource> module() { + return module; + } + + public Region getRegion() { + return Region.fromName(remote().regionName()); + } + + @Nullable + @Override + protected com.azure.resourcemanager.network.models.Network loadRemote() { + return module.getNetworkManager(getSubscriptionId()).getByResourceGroup(getResourceGroup(), getName()); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/network/model/Subnet.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/network/model/Subnet.java new file mode 100644 index 0000000000..574a977486 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/network/model/Subnet.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ +package com.microsoft.azure.toolkit.lib.compute.network.model; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@Getter +@EqualsAndHashCode +public class Subnet { + private final String name; + private final String addressSpace; + + public Subnet(String name, String addressSpace) { + this.name = name; + this.addressSpace = addressSpace; + } + + public Subnet(com.azure.resourcemanager.network.models.Subnet resource) { + this.name = resource.name(); + this.addressSpace = resource.addressPrefix(); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/security/AzureNetworkSecurityGroup.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/security/AzureNetworkSecurityGroup.java new file mode 100644 index 0000000000..019c576980 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/security/AzureNetworkSecurityGroup.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.security; + +import com.azure.resourcemanager.compute.ComputeManager; +import com.azure.resourcemanager.network.models.NetworkSecurityGroups; +import com.microsoft.azure.toolkit.lib.common.event.AzureOperationEvent; +import com.microsoft.azure.toolkit.lib.common.model.Subscription; +import com.microsoft.azure.toolkit.lib.compute.AbstractAzureResourceModule; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.stream.Collectors; + +public class AzureNetworkSecurityGroup extends AbstractAzureResourceModule + implements AzureOperationEvent.Source { + public AzureNetworkSecurityGroup() { + super(AzureNetworkSecurityGroup::new); + } + + private AzureNetworkSecurityGroup(@Nullable List subscriptions) { + super(AzureNetworkSecurityGroup::new, subscriptions); + } + + @Override + public List list(@Nonnull String subscriptionId) { + return getSecurityGroupManager(subscriptionId).list().stream().map(group -> new NetworkSecurityGroup(group, this)).collect(Collectors.toList()); + } + + @NotNull + @Override + public NetworkSecurityGroup get(@Nonnull String subscriptionId, @Nonnull String resourceGroup, @Nonnull String name) { + final NetworkSecurityGroups securityGroupManager = getSecurityGroupManager(subscriptionId); + return new NetworkSecurityGroup(securityGroupManager.getByResourceGroup(resourceGroup, name), this); + } + + public NetworkSecurityGroup create(@Nonnull final DraftNetworkSecurityGroup draftNetworkSecurityGroup) { + return draftNetworkSecurityGroup.create(this); + } + + NetworkSecurityGroups getSecurityGroupManager(final String subscriptionId) { + return getResourceManager(subscriptionId, ComputeManager::configure, ComputeManager.Configurable::authenticate).networkManager().networkSecurityGroups(); + } + + @Override + public String name() { + return "NetworkSecurityGroup"; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/security/DraftNetworkSecurityGroup.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/security/DraftNetworkSecurityGroup.java new file mode 100644 index 0000000000..5157cdca2f --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/security/DraftNetworkSecurityGroup.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.security; + +import com.azure.resourcemanager.network.models.NetworkSecurityGroup.DefinitionStages.WithCreate; +import com.azure.resourcemanager.network.models.NetworkSecurityRule; +import com.azure.resourcemanager.network.models.NetworkSecurityRule.DefinitionStages.WithDestinationAddressOrSecurityGroup; +import com.azure.resourcemanager.network.models.NetworkSecurityRule.DefinitionStages.WithSourceAddressOrSecurityGroup; +import com.azure.resourcemanager.network.models.NetworkSecurityRule.DefinitionStages.WithSourcePort; +import com.azure.resourcemanager.network.models.SecurityRuleProtocol; +import com.azure.resourcemanager.resources.fluentcore.arm.models.HasId; +import com.microsoft.azure.toolkit.lib.common.model.Region; +import com.microsoft.azure.toolkit.lib.compute.AzureResourceDraft; +import com.microsoft.azure.toolkit.lib.compute.security.model.SecurityRule; +import io.jsonwebtoken.lang.Collections; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Optional; + +@Getter +@Setter +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class DraftNetworkSecurityGroup extends NetworkSecurityGroup implements AzureResourceDraft { + private static final int BASE_PRIORITY = 300; + private static final int PRIORITY_STEP = 20; + + private Region region; + private List securityRuleList; + + public DraftNetworkSecurityGroup(@Nonnull final String subscriptionId, @Nonnull final String resourceGroup, @Nonnull final String name) { + super(getResourceId(subscriptionId, resourceGroup, name), null); + } + + public void setSubscriptionId(final String subscriptionId) { + this.subscriptionId = subscriptionId; + } + + public void setResourceGroup(final String resourceGroup) { + this.resourceGroup = resourceGroup; + } + + public void setName(final String name) { + this.name = name; + } + + @Override + public String getId() { + return Optional.ofNullable(remote).map(HasId::id).orElseGet(() -> getResourceId(subscriptionId, resourceGroup, name)); + } + + NetworkSecurityGroup create(final AzureNetworkSecurityGroup module) { + this.module = module; + WithCreate withCreate = module.getSecurityGroupManager(subscriptionId) + .define(name) + .withRegion(region.getName()) + .withExistingResourceGroup(resourceGroup); + if (!Collections.isEmpty(securityRuleList)) { + applySecurityRule(withCreate, securityRuleList); + } + this.remote = withCreate.create(); + refreshStatus(); + module.refresh(); + return this; + } + + private static void applySecurityRule(WithCreate withCreate, List securityRuleList) { + for (int priority = BASE_PRIORITY, count = 0; count < securityRuleList.size(); count++, priority += PRIORITY_STEP) { + applySecurityRule(withCreate, securityRuleList.get(count), priority); + } + } + + private static void applySecurityRule(final WithCreate withCreate, final SecurityRule securityRule, final int priority) { + final WithSourceAddressOrSecurityGroup withSource = withCreate.defineRule(securityRule.getName()).allowInbound(); + final WithSourcePort withSourcePort = securityRule.getFromAddresses() != null ? + withSource.fromAddresses(securityRule.getFromAddresses()) : withSource.fromAnyAddress(); + final WithDestinationAddressOrSecurityGroup withDestination = securityRule.getFromPort() != null ? + withSourcePort.fromPort(securityRule.getFromPort()) : withSourcePort.fromAnyPort(); + final NetworkSecurityRule.DefinitionStages.WithDestinationPort withDestPort = securityRule.getToAddresses() != null ? + withDestination.toAddresses(securityRule.getToAddresses()) : withDestination.toAnyAddress(); + final NetworkSecurityRule.DefinitionStages.WithProtocol withProtocol = securityRule.getToPort() != null ? + withDestPort.toPort(securityRule.getToPort()) : withDestPort.toAnyPort(); + final NetworkSecurityRule.DefinitionStages.WithAttach withAttach = securityRule.getProtocol() == SecurityRule.Protocol.ALL ? + withProtocol.withAnyProtocol() : withProtocol.withProtocol(SecurityRuleProtocol.fromString(securityRule.getProtocol().name())); + withAttach.withPriority(priority).attach(); + } + + @Override + public String status() { + return Optional.ofNullable(remote).map(ignore -> super.status()).orElse(Status.DRAFT); + } + + @Nullable + @Override + protected com.azure.resourcemanager.network.models.NetworkSecurityGroup loadRemote() { + return Optional.ofNullable(remote).map(ignore -> super.loadRemote()).orElse(null); + } + + private static String getResourceId(@Nonnull final String subscriptionId, @Nonnull final String resourceGroup, @Nonnull final String name) { + return String.format("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/networkSecurityGroups/%s", subscriptionId, resourceGroup, name); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/security/NetworkSecurityGroup.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/security/NetworkSecurityGroup.java new file mode 100644 index 0000000000..5b1193d4c3 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/security/NetworkSecurityGroup.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.security; + +import com.microsoft.azure.toolkit.lib.common.entity.IAzureBaseResource; +import com.microsoft.azure.toolkit.lib.common.entity.IAzureModule; +import com.microsoft.azure.toolkit.lib.common.event.AzureOperationEvent; +import com.microsoft.azure.toolkit.lib.common.model.Region; +import com.microsoft.azure.toolkit.lib.compute.AbstractAzureResource; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class NetworkSecurityGroup extends AbstractAzureResource + implements AzureOperationEvent.Source { + + protected AzureNetworkSecurityGroup module; + + public NetworkSecurityGroup(@Nonnull final String id, @Nullable final AzureNetworkSecurityGroup module) { + super(id); + this.module = module; + } + + public NetworkSecurityGroup(@Nonnull final com.azure.resourcemanager.network.models.NetworkSecurityGroup resource, + @Nonnull final AzureNetworkSecurityGroup module) { + super(resource); + this.module = module; + } + + @Override + public IAzureModule, + ? extends IAzureBaseResource> module() { + return module; + } + + public Region getRegion() { + return Region.fromName(remote().regionName()); + } + + @Nullable + @Override + protected com.azure.resourcemanager.network.models.NetworkSecurityGroup loadRemote() { + return module.getSecurityGroupManager(getSubscriptionId()).getByResourceGroup(getResourceGroup(), getName()); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/security/model/SecurityRule.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/security/model/SecurityRule.java new file mode 100644 index 0000000000..e6db84cc69 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/security/model/SecurityRule.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.security.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder() +@EqualsAndHashCode() +public class SecurityRule { + public static final SecurityRule SSH_RULE = SecurityRule.builder().name("SSH") + .toPort(22).protocol(Protocol.TCP).build(); + public static final SecurityRule HTTP_RULE = SecurityRule.builder().name("HTTP") + .toPort(80).protocol(Protocol.TCP).build(); + public static final SecurityRule HTTPS_RULE = SecurityRule.builder().name("HTTPS") + .toPort(443).protocol(Protocol.TCP).build(); + public static final SecurityRule RDP_RULE = SecurityRule.builder().name("RDP") + .toPort(3389).protocol(Protocol.TCP).build(); + + private String name; + private String[] fromAddresses; + private Integer fromPort; + private String[] toAddresses; + private Integer toPort; + private Protocol protocol; + + @AllArgsConstructor + public enum Protocol { + TCP("Tcp"), + UDP("Udp"), + ICMP("Icmp"), + ESP("Esp"), + ASTERISK("*"), + AH("Ah"), + ALL("all"); + + private String value; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureImage.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureImage.java new file mode 100644 index 0000000000..a70b264fab --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureImage.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.vm; + +import com.azure.resourcemanager.compute.models.ImageReference; +import com.azure.resourcemanager.compute.models.KnownLinuxVirtualMachineImage; +import com.azure.resourcemanager.compute.models.KnownWindowsVirtualMachineImage; +import com.azure.resourcemanager.compute.models.VirtualMachineImage; +import com.microsoft.azure.toolkit.lib.compute.vm.model.OperatingSystem; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import javax.annotation.Nonnull; + +@EqualsAndHashCode +public class AzureImage { + public static final AzureImage WINDOWS_DESKTOP_10_20H1_PRO = new AzureImage(KnownWindowsVirtualMachineImage.WINDOWS_DESKTOP_10_20H1_PRO); + public static final AzureImage WINDOWS_SERVER_2019_DATACENTER = new AzureImage(KnownWindowsVirtualMachineImage.WINDOWS_SERVER_2019_DATACENTER); + public static final AzureImage WINDOWS_SERVER_2019_DATACENTER_WITH_CONTAINER = + new AzureImage(KnownWindowsVirtualMachineImage.WINDOWS_SERVER_2019_DATACENTER_WITH_CONTAINERS); + public static final AzureImage WINDOWS_SERVER_2016_DATACENTER = new AzureImage(KnownWindowsVirtualMachineImage.WINDOWS_SERVER_2016_DATACENTER); + public static final AzureImage WINDOWS_SERVER_2012_R2_DATACENTER = new AzureImage(KnownWindowsVirtualMachineImage.WINDOWS_SERVER_2012_R2_DATACENTER); + public static final AzureImage UBUNTU_SERVER_16_04_LTS = new AzureImage(KnownLinuxVirtualMachineImage.UBUNTU_SERVER_16_04_LTS); + public static final AzureImage UBUNTU_SERVER_18_04_LTS = new AzureImage(KnownLinuxVirtualMachineImage.UBUNTU_SERVER_18_04_LTS); + public static final AzureImage DEBIAN_9 = new AzureImage(KnownLinuxVirtualMachineImage.DEBIAN_9); + public static final AzureImage DEBIAN_10 = new AzureImage(KnownLinuxVirtualMachineImage.DEBIAN_10); + public static final AzureImage CENTOS_8_1 = new AzureImage(KnownLinuxVirtualMachineImage.CENTOS_8_1); + public static final AzureImage CENTOS_8_3 = new AzureImage(KnownLinuxVirtualMachineImage.CENTOS_8_3); + @Deprecated + public static final AzureImage OPENSUSE_LEAP_15_1 = new AzureImage(KnownLinuxVirtualMachineImage.OPENSUSE_LEAP_15_1); + public static final AzureImage OPENSUSE_LEAP_15 = new AzureImage(KnownLinuxVirtualMachineImage.OPENSUSE_LEAP_15); + @Deprecated + public static final AzureImage SLES_15_SP1 = new AzureImage(KnownLinuxVirtualMachineImage.SLES_15_SP1); + public static final AzureImage SLES_15 = new AzureImage(KnownLinuxVirtualMachineImage.SLES_15); + public static final AzureImage REDHAT_RHEL_8_2 = new AzureImage(KnownLinuxVirtualMachineImage.REDHAT_RHEL_8_2); + public static final AzureImage ORACLE_LINUX_8_1 = new AzureImage(KnownLinuxVirtualMachineImage.ORACLE_LINUX_8_1); + + @Nonnull + @Getter(value = AccessLevel.PACKAGE) + @EqualsAndHashCode.Exclude + private final ImageReference imageReference; + + @Nonnull + @Getter + private final OperatingSystem operatingSystem; + @Getter + private final String id; + @Getter + private final String publisherName; + @Getter + private final String offer; + @Getter + private final String sku; + @Getter + private final String version; + + public AzureImage(@Nonnull VirtualMachineImage virtualMachineImage) { + this(OperatingSystem.fromString(virtualMachineImage.osDiskImage().operatingSystem().name()), virtualMachineImage.imageReference()); + } + + public AzureImage(@Nonnull KnownWindowsVirtualMachineImage windowsVirtualMachineImage) { + this(OperatingSystem.Windows, windowsVirtualMachineImage.imageReference()); + } + + public AzureImage(@Nonnull KnownLinuxVirtualMachineImage linuxVirtualMachineImage) { + this(OperatingSystem.Linux, linuxVirtualMachineImage.imageReference()); + } + + public AzureImage(final OperatingSystem operatingSystem, final ImageReference imageReference) { + this.operatingSystem = operatingSystem; + this.id = imageReference.id(); + this.publisherName = imageReference.publisher(); + this.offer = imageReference.offer(); + this.sku = imageReference.sku(); + this.version = imageReference.version(); + + this.imageReference = imageReference; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureImageOffer.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureImageOffer.java new file mode 100644 index 0000000000..d3dfb45fe2 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureImageOffer.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.vm; + +import com.azure.resourcemanager.compute.models.VirtualMachineOffer; +import com.microsoft.azure.toolkit.lib.common.model.Region; +import lombok.Getter; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.stream.Collectors; + +public class AzureImageOffer { + @Getter + private final AzureImagePublisher publisher; + private final VirtualMachineOffer virtualMachineOffer; + + AzureImageOffer(@Nonnull AzureImagePublisher publisher, @Nonnull VirtualMachineOffer virtualMachineOffer) { + this.publisher = publisher; + this.virtualMachineOffer = virtualMachineOffer; + } + + public String name() { + return virtualMachineOffer.name(); + } + + public Region region() { + return Region.fromName(virtualMachineOffer.region().name()); + } + + public List skus() { + return virtualMachineOffer.skus().list().stream().map(sku -> new AzureImageSku(this, sku)).collect(Collectors.toList()); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureImagePublisher.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureImagePublisher.java new file mode 100644 index 0000000000..07f7f5a26c --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureImagePublisher.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.vm; + +import com.azure.resourcemanager.compute.models.VirtualMachinePublisher; +import com.microsoft.azure.toolkit.lib.common.model.Region; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.stream.Collectors; + +public class AzureImagePublisher { + private final VirtualMachinePublisher virtualMachinePublisher; + + AzureImagePublisher(@Nonnull VirtualMachinePublisher virtualMachinePublisher) { + this.virtualMachinePublisher = virtualMachinePublisher; + } + + public String name() { + return virtualMachinePublisher.name(); + } + + public Region region() { + return Region.fromName(virtualMachinePublisher.region().name()); + } + + public List offers() { + return virtualMachinePublisher.offers().list().stream().map(offer -> new AzureImageOffer(this, offer)).collect(Collectors.toList()); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureImageSku.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureImageSku.java new file mode 100644 index 0000000000..43566a8ff0 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureImageSku.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.vm; + +import com.azure.resourcemanager.compute.models.VirtualMachineSku; +import com.microsoft.azure.toolkit.lib.common.model.Region; +import lombok.Getter; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.stream.Collectors; + +public class AzureImageSku { + @Getter + private final AzureImageOffer publisher; + private final VirtualMachineSku virtualMachineSku; + + AzureImageSku(@Nonnull AzureImageOffer imageOffer, @Nonnull VirtualMachineSku virtualMachineSku) { + this.publisher = imageOffer; + this.virtualMachineSku = virtualMachineSku; + } + + public String name() { + return virtualMachineSku.name(); + } + + public Region region() { + return Region.fromName(virtualMachineSku.region().name()); + } + + public List images() { + return virtualMachineSku.images().list().stream().map(AzureImage::new).collect(Collectors.toList()); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureVirtualMachine.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureVirtualMachine.java new file mode 100644 index 0000000000..f433092dfe --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureVirtualMachine.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.vm; + +import com.azure.resourcemanager.compute.ComputeManager; +import com.azure.resourcemanager.compute.models.AvailabilitySet; +import com.azure.resourcemanager.compute.models.ComputeResourceType; +import com.azure.resourcemanager.compute.models.KnownLinuxVirtualMachineImage; +import com.azure.resourcemanager.compute.models.KnownWindowsVirtualMachineImage; +import com.azure.resourcemanager.compute.models.VirtualMachines; +import com.microsoft.azure.toolkit.lib.common.cache.CacheEvict; +import com.microsoft.azure.toolkit.lib.common.cache.CacheManager; +import com.microsoft.azure.toolkit.lib.common.cache.Cacheable; +import com.microsoft.azure.toolkit.lib.common.event.AzureOperationEvent; +import com.microsoft.azure.toolkit.lib.common.model.Region; +import com.microsoft.azure.toolkit.lib.common.model.Subscription; +import com.microsoft.azure.toolkit.lib.common.operation.AzureOperation; +import com.microsoft.azure.toolkit.lib.compute.AbstractAzureResourceModule; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Slf4j +public class AzureVirtualMachine extends AbstractAzureResourceModule implements AzureOperationEvent.Source { + + private static final List linuxImages = + Arrays.stream(KnownLinuxVirtualMachineImage.values()).map(AzureImage::new).collect(Collectors.toList()); + private static final List windowsImages = + Arrays.stream(KnownWindowsVirtualMachineImage.values()).map(AzureImage::new).collect(Collectors.toList()); + private static final List images = + Collections.unmodifiableList(Stream.of(linuxImages, windowsImages).flatMap(List::stream).collect(Collectors.toList())); + + public AzureVirtualMachine() { // for SPI + super(AzureVirtualMachine::new); + } + + private AzureVirtualMachine(@Nonnull final List subscriptions) { + super(AzureVirtualMachine::new, subscriptions); + } + + @Cacheable(cacheName = "compute/{}/vm", key = "$subscriptionId") + public List list(@Nonnull final String subscriptionId) { + final VirtualMachines virtualMachines = getVirtualMachinesManager(subscriptionId); + return virtualMachines.list().stream() + .map(vm -> new VirtualMachine(vm, this)).collect(Collectors.toList()); + } + + @Nonnull + public VirtualMachine get(@Nonnull final String subscriptionId, @Nonnull final String resourceGroup, @Nonnull final String name) { + final VirtualMachines virtualMachinesManager = getVirtualMachinesManager(subscriptionId); + return new VirtualMachine(virtualMachinesManager.getByResourceGroup(resourceGroup, name), this); + } + + public List availabilitySets() { + return availabilitySets(getDefaultSubscription().getId()); + } + + public List availabilitySets(@Nonnull final String subscriptionId) { + return getVirtualMachinesManager(subscriptionId).manager().availabilitySets().list().stream().map(AvailabilitySet::name).collect(Collectors.toList()); + } + + public List publishers(final Region region) { + return publishers(getDefaultSubscription().getId(), region); + } + + public List publishers(final String subscriptionId, final Region region) { + return getVirtualMachinesManager(subscriptionId).manager().virtualMachineImages() + .publishers().listByRegion(region.getName()).stream().map(AzureImagePublisher::new).collect(Collectors.toList()); + } + + public List listPricing(final Region region) { + return listPricing(getDefaultSubscription().getId(), region); + } + + public List listPricing(final String subscriptionId, final Region region) { + return getVirtualMachinesManager(subscriptionId).manager().computeSkus() + .listByRegionAndResourceType(com.azure.core.management.Region.fromName(region.getName()), ComputeResourceType.VIRTUALMACHINES).stream() + .map(AzureVirtualMachineSize::new).collect(Collectors.toList()); + } + + public List getKnownImages() { + return images; + } + + @Nonnull + public VirtualMachine create(@Nonnull final DraftVirtualMachine config) { + return config.create(this); + } + + VirtualMachines getVirtualMachinesManager(String subscriptionId) { + return getResourceManager(subscriptionId, ComputeManager::configure, ComputeManager.Configurable::authenticate).virtualMachines(); + } + + @AzureOperation(name = "common|service.refresh", params = "this.name()", type = AzureOperation.Type.SERVICE) + public void refresh() { + try { + CacheManager.evictCache("compute/{}/vm", CacheEvict.ALL); + } catch (ExecutionException e) { + log.warn("failed to evict cache", e); + } + } + + @Override + public String name() { + return "Virtual Machine"; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureVirtualMachineSize.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureVirtualMachineSize.java new file mode 100644 index 0000000000..2dfd4bd2ab --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/AzureVirtualMachineSize.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.vm; + +import com.azure.resourcemanager.compute.models.ComputeSku; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@Getter +@EqualsAndHashCode +public class AzureVirtualMachineSize { + public static final AzureVirtualMachineSize Standard_D2 = new AzureVirtualMachineSize("Standard_D2"); + public static final AzureVirtualMachineSize Standard_D2_v2 = new AzureVirtualMachineSize("Standard_D2_v2"); + public static final AzureVirtualMachineSize Standard_DS2 = new AzureVirtualMachineSize("Standard_DS2"); + public static final AzureVirtualMachineSize Standard_D2s_v3 = new AzureVirtualMachineSize("Standard_D2s_v3"); + public static final AzureVirtualMachineSize Standard_D4s_v3 = new AzureVirtualMachineSize("Standard_D4s_v3"); + public static final AzureVirtualMachineSize Standard_E2s_v3 = new AzureVirtualMachineSize("Standard_E2s_v3"); + + private final String name; + + public AzureVirtualMachineSize(final ComputeSku size) { + this.name = size.name().toString(); + } + + public AzureVirtualMachineSize(final String name) { + this.name = name; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/DraftVirtualMachine.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/DraftVirtualMachine.java new file mode 100644 index 0000000000..479451d8fb --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/DraftVirtualMachine.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.vm; + +import com.azure.resourcemanager.compute.models.AvailabilitySet; +import com.azure.resourcemanager.compute.models.VirtualMachine.DefinitionStages.WithCreate; +import com.azure.resourcemanager.compute.models.VirtualMachine.DefinitionStages.WithLinuxCreateManagedOrUnmanaged; +import com.azure.resourcemanager.compute.models.VirtualMachine.DefinitionStages.WithLinuxRootPasswordOrPublicKeyManagedOrUnmanaged; +import com.azure.resourcemanager.compute.models.VirtualMachine.DefinitionStages.WithProximityPlacementGroup; +import com.azure.resourcemanager.compute.models.VirtualMachineEvictionPolicyTypes; +import com.azure.resourcemanager.network.models.NetworkInterface; +import com.azure.resourcemanager.resources.fluentcore.arm.models.HasId; +import com.azure.resourcemanager.storage.models.StorageAccount; +import com.microsoft.azure.toolkit.lib.common.bundle.AzureString; +import com.microsoft.azure.toolkit.lib.common.messager.AzureMessager; +import com.microsoft.azure.toolkit.lib.common.model.Region; +import com.microsoft.azure.toolkit.lib.common.utils.Utils; +import com.microsoft.azure.toolkit.lib.compute.AzureResourceDraft; +import com.microsoft.azure.toolkit.lib.compute.ip.DraftPublicIpAddress; +import com.microsoft.azure.toolkit.lib.compute.ip.PublicIpAddress; +import com.microsoft.azure.toolkit.lib.compute.network.DraftNetwork; +import com.microsoft.azure.toolkit.lib.compute.network.Network; +import com.microsoft.azure.toolkit.lib.compute.network.model.Subnet; +import com.microsoft.azure.toolkit.lib.compute.security.DraftNetworkSecurityGroup; +import com.microsoft.azure.toolkit.lib.compute.security.NetworkSecurityGroup; +import com.microsoft.azure.toolkit.lib.compute.security.model.SecurityRule; +import com.microsoft.azure.toolkit.lib.compute.vm.model.AuthenticationType; +import com.microsoft.azure.toolkit.lib.compute.vm.model.AzureSpotConfig; +import com.microsoft.azure.toolkit.lib.compute.vm.model.OperatingSystem; +import com.microsoft.azure.toolkit.lib.storage.StorageManagerFactory; +import com.microsoft.azure.toolkit.lib.storage.model.StorageAccountConfig; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; + +import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.Optional; + +import static com.azure.resourcemanager.compute.models.VirtualMachineEvictionPolicyTypes.DEALLOCATE; +import static com.azure.resourcemanager.compute.models.VirtualMachineEvictionPolicyTypes.DELETE; +import static com.microsoft.azure.toolkit.lib.compute.vm.model.AzureSpotConfig.EvictionPolicy.StopAndDeallocate; + +@Getter +@Setter +@NoArgsConstructor +public class DraftVirtualMachine extends VirtualMachine implements AzureResourceDraft { + private Region region; + private AzureImage image; + private Network network; + private Subnet subnet; + private PublicIpAddress ipAddress; + private NetworkSecurityGroup securityGroup; + private String userName; + private AuthenticationType authenticationType; + private String password; + private String sshKey; + private AzureVirtualMachineSize size; + private String availabilitySet; + private StorageAccountConfig storageAccount; + private AzureSpotConfig azureSpotConfig; + + public DraftVirtualMachine(@Nonnull final String subscriptionId, @Nonnull final String resourceGroup, @Nonnull final String name) { + super(getResourceId(subscriptionId, resourceGroup, name), null); + } + + public static DraftVirtualMachine getDefaultVirtualMachineDraft() { + final DraftVirtualMachine virtualMachine = new DraftVirtualMachine(); + virtualMachine.setRegion(Region.US_CENTRAL); + virtualMachine.setImage(AzureImage.UBUNTU_SERVER_18_04_LTS); + virtualMachine.setSize(AzureVirtualMachineSize.Standard_D2s_v3); + virtualMachine.setNetwork(DraftNetwork.getDefaultNetworkDraft()); + virtualMachine.setIpAddress(DraftPublicIpAddress.getDefaultPublicIpAddressDraft()); + final DraftNetworkSecurityGroup defaultSecurityGroup = new DraftNetworkSecurityGroup(); + defaultSecurityGroup.setSecurityRuleList(Arrays.asList(SecurityRule.SSH_RULE)); + virtualMachine.setSecurityGroup(defaultSecurityGroup); + return virtualMachine; + } + + public void setSubscriptionId(final String subscriptionId) { + this.subscriptionId = subscriptionId; + } + + public void setResourceGroup(final String resourceGroup) { + this.resourceGroup = resourceGroup; + } + + public void setName(final String name) { + this.name = name; + } + + @Override + public String getId() { + return Optional.ofNullable(remote).map(HasId::id).orElseGet(() -> getResourceId(subscriptionId, resourceGroup, name)); + } + + @Override + public String status() { + return Optional.ofNullable(remote).map(ignore -> super.status()).orElse(Status.DRAFT); + } + + @Nullable + @Override + protected com.azure.resourcemanager.compute.models.VirtualMachine loadRemote() { + return Optional.ofNullable(remote).map(ignore -> super.loadRemote()).orElse(null); + } + + VirtualMachine create(final AzureVirtualMachine module) { + this.module = module; + final NetworkInterface.DefinitionStages.WithCreate interfaceWithCreate = module.getVirtualMachinesManager(subscriptionId).manager().networkManager() + .networkInterfaces().define(name + "-interface-" + Utils.getTimestamp()) + .withRegion(this.getRegion().getName()) + .withExistingResourceGroup(this.getResourceGroup()) + .withExistingPrimaryNetwork(this.getNetworkClient()) + .withSubnet(subnet.getName()) + .withPrimaryPrivateIPAddressDynamic(); + if (ipAddress != null) { + final com.azure.resourcemanager.network.models.PublicIpAddress publicIpAddressClient = getPublicIpAddressClient(); + if (publicIpAddressClient.hasAssignedNetworkInterface()) { + AzureMessager.getMessager().warning(AzureString.format("Can not assign public ip %s to vm %s, which has been assigned to %s", + ipAddress.getName(), name, publicIpAddressClient.getAssignedNetworkInterfaceIPConfiguration().name())); + } else { + interfaceWithCreate.withExistingPrimaryPublicIPAddress(getPublicIpAddressClient()); + } + } + if (securityGroup != null) { + interfaceWithCreate.withExistingNetworkSecurityGroup(getSecurityGroupClient()); + } + final NetworkInterface networkInterface = interfaceWithCreate.create(); + final WithProximityPlacementGroup withProximityPlacementGroup = module.getVirtualMachinesManager(subscriptionId).define(this.getName()) + .withRegion(this.getRegion().getName()) + .withExistingResourceGroup(this.getResourceGroup()) + .withExistingPrimaryNetworkInterface(networkInterface); + final WithCreate withCreate = configureImage(withProximityPlacementGroup); + if (StringUtils.isNotEmpty(availabilitySet)) { + withCreate.withExistingAvailabilitySet(getAvailabilitySetClient()); + } + if (storageAccount != null) { + withCreate.withExistingStorageAccount(getStorageAccountClient()); + } + if (azureSpotConfig != null) { + final VirtualMachineEvictionPolicyTypes evictionPolicyTypes = azureSpotConfig.getPolicy() == StopAndDeallocate ? DEALLOCATE : DELETE; + withCreate.withSpotPriority(evictionPolicyTypes).withMaxPrice(azureSpotConfig.getMaximumPrice()); + } + try { + this.remote = withCreate.create(); + } catch (Exception e) { + // clean up resource once creation failed + networkInterface.manager().networkInterfaces().deleteById(networkInterface.id()); + throw e; + } + refreshStatus(); + module.refresh(); + return this; + } + + private StorageAccount getStorageAccountClient() { + return StorageManagerFactory.create(subscriptionId).storageAccounts().getById(storageAccount.getId()); + } + + private WithCreate configureImage(final WithProximityPlacementGroup withCreate) { + if (getImage().getOperatingSystem() == OperatingSystem.Windows) { + return withCreate.withSpecificWindowsImageVersion(image.getImageReference()) + .withAdminUsername(userName).withAdminPassword(password).withSize(size.getName()); + } else { + final WithLinuxRootPasswordOrPublicKeyManagedOrUnmanaged withLinuxImage = + withCreate.withSpecificLinuxImageVersion(image.getImageReference()).withRootUsername(userName); + final WithLinuxCreateManagedOrUnmanaged withLinuxAuthentication = authenticationType == AuthenticationType.Password ? + withLinuxImage.withRootPassword(password) : withLinuxImage.withSsh(sshKey); + return withLinuxAuthentication.withSize(size.getName()); + } + } + + private com.azure.resourcemanager.network.models.NetworkSecurityGroup getSecurityGroupClient() { + return Optional.ofNullable(module).map(parent -> parent.getVirtualMachinesManager(subscriptionId).manager()) + .map(manager -> manager.networkManager().networkSecurityGroups().getByResourceGroup(resourceGroup, securityGroup.name())).orElse(null); + } + + private AvailabilitySet getAvailabilitySetClient() { + return Optional.ofNullable(module).map(parent -> parent.getVirtualMachinesManager(subscriptionId).manager()) + .map(manager -> manager.availabilitySets().getByResourceGroup(resourceGroup, availabilitySet)).orElse(null); + } + + private com.azure.resourcemanager.network.models.PublicIpAddress getPublicIpAddressClient() { + return Optional.ofNullable(module).map(parent -> parent.getVirtualMachinesManager(subscriptionId).manager()) + .map(manager -> manager.networkManager().publicIpAddresses().getById(ipAddress.getId())).orElse(null); + } + + private com.azure.resourcemanager.network.models.Network getNetworkClient() { + return Optional.ofNullable(module).map(parent -> parent.getVirtualMachinesManager(subscriptionId).manager()) + .map(manager -> manager.networkManager().networks().getById(network.getId())).orElse(null); + } + + private static String getResourceId(@Nonnull final String subscriptionId, @Nonnull final String resourceGroup, @Nonnull final String name) { + return String.format("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s", subscriptionId, resourceGroup, name); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/VirtualMachine.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/VirtualMachine.java new file mode 100644 index 0000000000..a316f4086d --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/VirtualMachine.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.vm; + +import com.azure.resourcemanager.compute.models.PowerState; +import com.microsoft.azure.toolkit.lib.common.entity.IAzureBaseResource; +import com.microsoft.azure.toolkit.lib.common.entity.IAzureModule; +import com.microsoft.azure.toolkit.lib.common.entity.Removable; +import com.microsoft.azure.toolkit.lib.common.event.AzureOperationEvent; +import com.microsoft.azure.toolkit.lib.common.model.Region; +import com.microsoft.azure.toolkit.lib.compute.AbstractAzureResource; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Objects; +import java.util.Optional; + +@NoArgsConstructor +public class VirtualMachine extends AbstractAzureResource + implements AzureOperationEvent.Source, Removable { + + protected AzureVirtualMachine module; + + public VirtualMachine(@Nonnull String id, @Nullable AzureVirtualMachine module) { + super(id); + this.module = module; + } + + public VirtualMachine(@Nonnull com.azure.resourcemanager.compute.models.VirtualMachine resource, @Nonnull AzureVirtualMachine module) { + super(resource); + this.module = module; + } + + @Nonnull + @Override + public IAzureModule, + ? extends IAzureBaseResource> module() { + return module; + } + + public void start() { + this.status(Status.PENDING); + remote().start(); + this.refreshStatus(); + } + + public void stop() { + this.status(Status.PENDING); + remote().powerOff(); + this.refreshStatus(); + } + + public void restart() { + this.status(Status.PENDING); + remote().restart(); + this.refreshStatus(); + } + + public Region getRegion() { + return Region.fromName(remote().regionName()); + } + + @Override + protected String loadStatus() { + final String powerState = Optional.ofNullable(remote().powerState()).map(Objects::toString).orElse(StringUtils.EMPTY); + if (StringUtils.equalsIgnoreCase(powerState, PowerState.RUNNING.toString())) { + return Status.RUNNING; + } else if (StringUtils.equalsAnyIgnoreCase(powerState, PowerState.DEALLOCATING.toString(), PowerState.STOPPING.toString(), + PowerState.STARTING.toString())) { + return Status.PENDING; + } else if (StringUtils.equalsAnyIgnoreCase(powerState, PowerState.STOPPED.toString(), PowerState.DEALLOCATED.toString())) { + return Status.STOPPED; + } else { + return Status.UNKNOWN; + } + } + + @Nullable + @Override + protected com.azure.resourcemanager.compute.models.VirtualMachine loadRemote() { + return module.getVirtualMachinesManager(subscriptionId).getByResourceGroup(resourceGroup, name); + } + + @Override + public void remove() { + if (this.exists()) { + this.status(Status.PENDING); + this.remote().manager().virtualMachines().deleteById(id()); + this.module.refresh(); + } + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/model/AuthenticationType.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/model/AuthenticationType.java new file mode 100644 index 0000000000..af3833a4b7 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/model/AuthenticationType.java @@ -0,0 +1,10 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ +package com.microsoft.azure.toolkit.lib.compute.vm.model; + +public enum AuthenticationType { + Password, + SSH +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/model/AzureSpotConfig.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/model/AzureSpotConfig.java new file mode 100644 index 0000000000..6f341c024c --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/model/AzureSpotConfig.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.vm.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class AzureSpotConfig { + private final double maximumPrice; + private final EvictionType type; + private final EvictionPolicy policy; + + public enum EvictionType { + CapacityOnly, + PriceOrCapacity; + } + + public enum EvictionPolicy { + StopAndDeallocate, + Delete; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/model/OperatingSystem.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/model/OperatingSystem.java new file mode 100644 index 0000000000..f9309e1dd1 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/model/OperatingSystem.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.vm.model; + +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; + +public enum OperatingSystem { + Windows, + Linux; + + public static OperatingSystem fromString(String value) { + return Arrays.stream(values()).filter(operatingSystem -> StringUtils.equalsIgnoreCase(operatingSystem.name(), value)) + .findFirst().orElse(null); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/task/CreateVirtualMachineTask.java b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/task/CreateVirtualMachineTask.java new file mode 100644 index 0000000000..ddda3a554f --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/java/com/microsoft/azure/toolkit/lib/compute/vm/task/CreateVirtualMachineTask.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.compute.vm.task; + +import com.microsoft.azure.toolkit.lib.Azure; +import com.microsoft.azure.toolkit.lib.common.bundle.AzureString; +import com.microsoft.azure.toolkit.lib.common.entity.IAzureBaseResource; +import com.microsoft.azure.toolkit.lib.common.task.AzureTask; +import com.microsoft.azure.toolkit.lib.compute.ip.AzurePublicIpAddress; +import com.microsoft.azure.toolkit.lib.compute.ip.DraftPublicIpAddress; +import com.microsoft.azure.toolkit.lib.compute.ip.PublicIpAddress; +import com.microsoft.azure.toolkit.lib.compute.network.AzureNetwork; +import com.microsoft.azure.toolkit.lib.compute.network.DraftNetwork; +import com.microsoft.azure.toolkit.lib.compute.network.Network; +import com.microsoft.azure.toolkit.lib.compute.security.AzureNetworkSecurityGroup; +import com.microsoft.azure.toolkit.lib.compute.security.DraftNetworkSecurityGroup; +import com.microsoft.azure.toolkit.lib.compute.security.NetworkSecurityGroup; +import com.microsoft.azure.toolkit.lib.compute.vm.AzureVirtualMachine; +import com.microsoft.azure.toolkit.lib.compute.vm.DraftVirtualMachine; +import com.microsoft.azure.toolkit.lib.compute.vm.VirtualMachine; +import com.microsoft.azure.toolkit.lib.resource.task.CreateResourceGroupTask; +import com.microsoft.azure.toolkit.lib.storage.model.StorageAccountConfig; +import com.microsoft.azure.toolkit.lib.storage.service.AzureStorageAccount; +import com.microsoft.azure.toolkit.lib.storage.service.StorageAccount; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +public class CreateVirtualMachineTask extends AzureTask { + private final DraftVirtualMachine draftVirtualMachine; + private final List> subTasks; + private VirtualMachine result; + + public CreateVirtualMachineTask(final DraftVirtualMachine draftVirtualMachine) { + this.draftVirtualMachine = draftVirtualMachine; + this.subTasks = this.initTasks(); + } + + private List> initTasks() { + final List> tasks = new ArrayList<>(); + tasks.add(new CreateResourceGroupTask(draftVirtualMachine.subscriptionId(), draftVirtualMachine.resourceGroup(), draftVirtualMachine.getRegion())); + // Create Virtual Network + final Network network = draftVirtualMachine.getNetwork(); + if (network instanceof DraftNetwork && StringUtils.equalsIgnoreCase(network.status(), IAzureBaseResource.Status.DRAFT)) { + final AzureString title = AzureString.format("Create new virtual network({0})", network.getName()); + tasks.add(new AzureTask<>(title, () -> Azure.az(AzureNetwork.class).create((DraftNetwork) network))); + } + // Create Public IP + final PublicIpAddress publicIpAddress = draftVirtualMachine.getIpAddress(); + if (publicIpAddress instanceof DraftPublicIpAddress && StringUtils.equalsIgnoreCase(publicIpAddress.status(), IAzureBaseResource.Status.DRAFT)) { + final AzureString title = AzureString.format("Create new public ip address({0})", publicIpAddress.getName()); + tasks.add(new AzureTask<>(title, () -> Azure.az(AzurePublicIpAddress.class).create((DraftPublicIpAddress) publicIpAddress))); + } + // Create Security Group + final NetworkSecurityGroup securityGroup = draftVirtualMachine.getSecurityGroup(); + if (securityGroup instanceof DraftNetworkSecurityGroup && StringUtils.equalsIgnoreCase(securityGroup.status(), IAzureBaseResource.Status.DRAFT)) { + final AzureString title = AzureString.format("Create security group ({0})", securityGroup.getName()); + tasks.add(new AzureTask(title, () -> + Azure.az(AzureNetworkSecurityGroup.class).create((DraftNetworkSecurityGroup) securityGroup))); + } + // Create Storage Account + // todo: migrate storage account to draft style + final StorageAccountConfig storageAccount = draftVirtualMachine.getStorageAccount(); + if (storageAccount != null && StringUtils.isEmpty(storageAccount.getId())) { + tasks.add(new CreateResourceGroupTask(storageAccount.getSubscriptionId(), storageAccount.getResourceGroupName(), storageAccount.getRegion())); + final AzureString title = AzureString.format("Create storage account ({0})", storageAccount.getName()); + tasks.add(new AzureTask(title, () -> { + final StorageAccount result = Azure.az(AzureStorageAccount.class).create(storageAccount).commit(); + storageAccount.setId(result.id()); + })); + } + // Create VM + final AzureString title = AzureString.format("Create virtual machine ({0})", draftVirtualMachine.getName()); + tasks.add(new AzureTask<>(title, () -> { + CreateVirtualMachineTask.this.result = Azure.az(AzureVirtualMachine.class).create(draftVirtualMachine); + return CreateVirtualMachineTask.this.result; + })); + return tasks; + } + + public VirtualMachine execute() { + this.subTasks.forEach(t -> t.getSupplier().get()); + return this.result; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/resources/META-INF/services/com.microsoft.azure.toolkit.lib.AzureService b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/resources/META-INF/services/com.microsoft.azure.toolkit.lib.AzureService new file mode 100644 index 0000000000..1c2471e130 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-compute-lib/src/main/resources/META-INF/services/com.microsoft.azure.toolkit.lib.AzureService @@ -0,0 +1,4 @@ +com.microsoft.azure.toolkit.lib.compute.vm.AzureVirtualMachine +com.microsoft.azure.toolkit.lib.compute.network.AzureNetwork +com.microsoft.azure.toolkit.lib.compute.ip.AzurePublicIpAddress +com.microsoft.azure.toolkit.lib.compute.security.AzureNetworkSecurityGroup diff --git a/azure-toolkit-libs/azure-toolkit-database-lib/pom.xml b/azure-toolkit-libs/azure-toolkit-database-lib/pom.xml index dcddfc3072..1624e14778 100644 --- a/azure-toolkit-libs/azure-toolkit-database-lib/pom.xml +++ b/azure-toolkit-libs/azure-toolkit-database-lib/pom.xml @@ -5,7 +5,7 @@ azure-toolkit-libs com.microsoft.azure - 0.11.0 + 0.12.1 4.0.0 @@ -107,4 +107,4 @@ - \ No newline at end of file + diff --git a/azure-toolkit-libs/azure-toolkit-mysql-lib/pom.xml b/azure-toolkit-libs/azure-toolkit-mysql-lib/pom.xml index 4b27002c93..e88ecd81b8 100644 --- a/azure-toolkit-libs/azure-toolkit-mysql-lib/pom.xml +++ b/azure-toolkit-libs/azure-toolkit-mysql-lib/pom.xml @@ -5,7 +5,7 @@ azure-toolkit-libs com.microsoft.azure - 0.11.0 + 0.12.1 4.0.0 diff --git a/azure-toolkit-libs/azure-toolkit-mysql-lib/src/main/java/com/microsoft/azure/toolkit/lib/mysql/AzureMySql.java b/azure-toolkit-libs/azure-toolkit-mysql-lib/src/main/java/com/microsoft/azure/toolkit/lib/mysql/AzureMySql.java index f0107aa9b9..3c12d04569 100644 --- a/azure-toolkit-libs/azure-toolkit-mysql-lib/src/main/java/com/microsoft/azure/toolkit/lib/mysql/AzureMySql.java +++ b/azure-toolkit-libs/azure-toolkit-mysql-lib/src/main/java/com/microsoft/azure/toolkit/lib/mysql/AzureMySql.java @@ -15,7 +15,6 @@ import com.azure.resourcemanager.resources.fluentcore.arm.ResourceId; import com.microsoft.azure.toolkit.lib.AzureService; import com.microsoft.azure.toolkit.lib.SubscriptionScoped; -import com.microsoft.azure.toolkit.lib.auth.AzureAccount; import com.microsoft.azure.toolkit.lib.common.event.AzureOperationEvent; import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException; import com.microsoft.azure.toolkit.lib.common.model.Region; @@ -27,20 +26,11 @@ import org.apache.commons.lang3.StringUtils; import javax.annotation.Nonnull; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -import static com.microsoft.azure.toolkit.lib.Azure.az; - public class AzureMySql extends SubscriptionScoped implements AzureService { - private static final List MYSQL_SUPPORTED_REGIONS = Arrays.asList( - "australiacentral", "australiacentral2", "australiaeast", "australiasoutheast", "brazilsouth", "canadacentral", "canadaeast", "centralindia", - "centralus", "eastasia", "eastus2", "eastus", "francecentral", "francesouth", "germanywestcentral", "japaneast", "japanwest", "koreacentral", - "koreasouth", "northcentralus", "northeurope", "southafricanorth", "southafricawest", "southcentralus", "southindia", "southeastasia", - "norwayeast", "switzerlandnorth", "uaenorth", "uksouth", "ukwest", "westcentralus", "westeurope", "westindia", "westus", "westus2", - "centraluseuap", "eastus2euap"); - private static final String NAME_AVAILABILITY_CHECK_TYPE = "Microsoft.DBforMySQL/servers"; + private static final String MYSQL_PROVIDER_AND_RESOURCE = "Microsoft.DBforMySQL/servers"; public AzureMySql() { super(AzureMySql::new); @@ -80,16 +70,12 @@ private static int getTierPriority(PerformanceTierProperties tier) { public boolean checkNameAvailability(String name) { MySqlManager mySqlManager = MySqlManagerFactory.create(getDefaultSubscription().getId()); - NameAvailabilityRequest request = new NameAvailabilityRequest().withName(name).withType(NAME_AVAILABILITY_CHECK_TYPE); + NameAvailabilityRequest request = new NameAvailabilityRequest().withName(name).withType(MYSQL_PROVIDER_AND_RESOURCE); return mySqlManager.checkNameAvailabilities().execute(request).nameAvailable(); } public List listSupportedRegions() { - List locationList = az(AzureAccount.class).listRegions(getDefaultSubscription().getId()); - return locationList.stream() - .filter(e -> MYSQL_SUPPORTED_REGIONS.contains(e.getName())) - .distinct() - .collect(Collectors.toList()); + return listSupportedRegions(getDefaultSubscription().getId()); } public List listSupportedVersions() { @@ -172,4 +158,8 @@ public AzureOperationEvent.Source getEventSource() { return new AzureOperationEvent.Source() {}; } } + + public String name() { + return MYSQL_PROVIDER_AND_RESOURCE; + } } diff --git a/azure-toolkit-libs/azure-toolkit-mysql-lib/src/main/java/com/microsoft/azure/toolkit/lib/mysql/MySqlManagerFactory.java b/azure-toolkit-libs/azure-toolkit-mysql-lib/src/main/java/com/microsoft/azure/toolkit/lib/mysql/MySqlManagerFactory.java index 73652c1c4a..aa7255a45c 100644 --- a/azure-toolkit-libs/azure-toolkit-mysql-lib/src/main/java/com/microsoft/azure/toolkit/lib/mysql/MySqlManagerFactory.java +++ b/azure-toolkit-libs/azure-toolkit-mysql-lib/src/main/java/com/microsoft/azure/toolkit/lib/mysql/MySqlManagerFactory.java @@ -11,6 +11,7 @@ import com.azure.resourcemanager.mysql.MySqlManager; import com.microsoft.azure.toolkit.lib.Azure; import com.microsoft.azure.toolkit.lib.AzureConfiguration; +import com.microsoft.azure.toolkit.lib.AzureService; import com.microsoft.azure.toolkit.lib.auth.Account; import com.microsoft.azure.toolkit.lib.auth.AzureAccount; import com.microsoft.azure.toolkit.lib.common.cache.Cacheable; @@ -30,6 +31,7 @@ public static MySqlManager create(String subscriptionId) { final HttpLogDetailLevel logLevel = Optional.ofNullable(config.getLogLevel()).map(HttpLogDetailLevel::valueOf).orElse(HttpLogDetailLevel.NONE); final AzureProfile azureProfile = new AzureProfile(null, subscriptionId, account.getEnvironment()); return MySqlManager.configure() + .withHttpClient(AzureService.getDefaultHttpClient()) .withLogOptions(new HttpLogOptions().setLogLevel(logLevel)) .withPolicy(getUserAgentPolicy(userAgent)) .authenticate(account.getTokenCredential(subscriptionId), azureProfile); diff --git a/azure-toolkit-libs/azure-toolkit-redis-lib/pom.xml b/azure-toolkit-libs/azure-toolkit-redis-lib/pom.xml new file mode 100644 index 0000000000..c4d80b3bcf --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-redis-lib/pom.xml @@ -0,0 +1,119 @@ + + + + azure-toolkit-libs + com.microsoft.azure + 0.12.1 + + 4.0.0 + + azure-toolkit-redis-lib + + + + + com.microsoft.azure + azure-toolkit-common-lib + + + + com.microsoft.azure + azure-toolkit-auth-lib + + + + org.projectlombok + lombok + provided + + + + com.azure.resourcemanager + azure-resourcemanager-redis + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + + + com.nickwongdev + aspectj-maven-plugin + + false + 1.8 + 1.8 + ignore + 1.8 + UTF-8 + false + true + true + + + + com.microsoft.azure + azure-toolkit-common-lib + + + + + + compile-with-aspectj + process-classes + + + ${project.build.directory}/classes + + + + compile + + + + test-compile-with-aspectj + process-test-classes + + test-compile + + + + ${project.build.directory}/test-classes + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + private + false + + + + attach-javadocs + + jar + + + ${javadoc.opts} + + + + + + + diff --git a/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/AzureRedis.java b/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/AzureRedis.java new file mode 100644 index 0000000000..bf126fce1a --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/AzureRedis.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.redis; + +import com.azure.core.management.exception.ManagementError; +import com.azure.core.management.exception.ManagementException; +import com.azure.resourcemanager.redis.RedisManager; +import com.azure.resourcemanager.redis.fluent.RedisClient; +import com.azure.resourcemanager.redis.models.CheckNameAvailabilityParameters; +import com.azure.resourcemanager.resources.fluentcore.arm.ResourceId; +import com.microsoft.azure.toolkit.lib.AzureService; +import com.microsoft.azure.toolkit.lib.SubscriptionScoped; +import com.microsoft.azure.toolkit.lib.common.entity.CheckNameAvailabilityResultEntity; +import com.microsoft.azure.toolkit.lib.common.event.AzureOperationEvent; +import com.microsoft.azure.toolkit.lib.common.model.Subscription; +import com.microsoft.azure.toolkit.lib.common.operation.AzureOperation; +import com.microsoft.azure.toolkit.lib.common.task.ICommittable; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.stream.Collectors; + +public class AzureRedis extends SubscriptionScoped implements AzureService, AzureOperationEvent.Source { + public AzureRedis() { + super(AzureRedis::new); + } + + private AzureRedis(@Nonnull final List subscriptions) { + super(AzureRedis::new, subscriptions); + } + + @Nonnull + @AzureOperation(name = "redis.list.subscription|selected", type = AzureOperation.Type.SERVICE) + public List list() { + return getSubscriptions().stream() + .map(subscription -> RedisManagerFactory.create(subscription.getId())) + .flatMap(manager -> manager.redisCaches().list().stream()) + .map(redis -> new RedisCache(redis.manager(), redis)) + .collect(Collectors.toList()); + } + + @AzureOperation(name = "redis.get.id", params = {"id"}, type = AzureOperation.Type.SERVICE) + public RedisCache get(@Nonnull String id) { + com.azure.resourcemanager.redis.models.RedisCache redisCache = + RedisManagerFactory.create(ResourceId.fromString(id).subscriptionId()).redisCaches().getById(id); + return new RedisCache(redisCache.manager(), redisCache); + } + + @AzureOperation(name = "redis.check_name", params = "name", type = AzureOperation.Type.SERVICE) + public CheckNameAvailabilityResultEntity checkNameAvailability(String subscriptionId, String name) { + final RedisManager redisManager = RedisManagerFactory.create(subscriptionId); + RedisClient redis = redisManager.redisCaches().manager().serviceClient().getRedis(); + try { + redis.checkNameAvailability(new CheckNameAvailabilityParameters().withName(name).withType("Microsoft.Cache/redis")); + return new CheckNameAvailabilityResultEntity(true, null); + } catch (ManagementException ex) { + ManagementError value = ex.getValue(); + if (value != null && "NameNotAvailable".equals(value.getCode())) { + return new CheckNameAvailabilityResultEntity(false, String.format("The name '%s' for Redis Cache is not available", name), value.getMessage()); + } + throw ex; + } + } + + public RedisCache create(RedisConfig config) { + return new Creator(config).commit(); + } + + @AllArgsConstructor(access = AccessLevel.PRIVATE) + private static class Creator implements ICommittable, AzureOperationEvent.Source { + + private final RedisConfig config; + + @Override + @AzureOperation(name = "redis.create", params = {"this.config.getName()"}, type = AzureOperation.Type.SERVICE) + public RedisCache commit() { + final com.azure.resourcemanager.redis.models.RedisCache.DefinitionStages.WithSku toCreate = + RedisManagerFactory.create(config.getSubscription().getId()).redisCaches().define(config.getName()) + .withRegion(config.getRegion().getName()) + .withExistingResourceGroup(config.getResourceGroup().getName()); + com.azure.resourcemanager.redis.models.RedisCache.DefinitionStages.WithCreate withCreate = null; + if (config.getPricingTier().isBasic()) { + withCreate = toCreate.withBasicSku(config.getPricingTier().getSize()); + } else if (config.getPricingTier().isStandard()) { + withCreate = toCreate.withStandardSku(config.getPricingTier().getSize()); + } else if (config.getPricingTier().isPremium()) { + withCreate = toCreate.withPremiumSku(config.getPricingTier().getSize()); + } + if (config.isEnableNonSslPort()) { + withCreate = withCreate.withNonSslPort(); + } + final com.azure.resourcemanager.redis.models.RedisCache redisCache = withCreate.create(); + return new RedisCache(redisCache.manager(), redisCache); + } + + public AzureOperationEvent.Source getEventSource() { + return new AzureOperationEvent.Source() {}; + } + } + + @Override + public String name() { + return "Microsoft.Cache/redis"; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/PricingTier.java b/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/PricingTier.java new file mode 100644 index 0000000000..20c156e3f7 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/PricingTier.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.redis; + +import com.google.common.collect.ImmutableList; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class PricingTier { + private static final String BASIC = "Basic"; + private static final String STANDARD = "Standard"; + private static final String PREMIUM = "Premium"; + private String family; + private String capacity; + private String memory; + private boolean replication; + + public static final PricingTier BASIC_C0 = new PricingTier(BASIC, "C0", "250MB", false); + public static final PricingTier BASIC_C1 = new PricingTier(BASIC, "C1", "1GB", false); + public static final PricingTier BASIC_C2 = new PricingTier(BASIC, "C2", "2.5GB", false); + public static final PricingTier BASIC_C3 = new PricingTier(BASIC, "C3", "6GB", false); + public static final PricingTier BASIC_C4 = new PricingTier(BASIC, "C4", "13GB", false); + public static final PricingTier BASIC_C5 = new PricingTier(BASIC, "C5", "26GB", false); + public static final PricingTier BASIC_C6 = new PricingTier(BASIC, "C6", "53GB", false); + + public static final PricingTier STANDARD_C0 = new PricingTier(STANDARD, "C0", "250MB", true); + public static final PricingTier STANDARD_C1 = new PricingTier(STANDARD, "C1", "1GB", true); + public static final PricingTier STANDARD_C2 = new PricingTier(STANDARD, "C2", "2.5GB", true); + public static final PricingTier STANDARD_C3 = new PricingTier(STANDARD, "C3", "6GB", true); + public static final PricingTier STANDARD_C4 = new PricingTier(STANDARD, "C4", "13GB", true); + public static final PricingTier STANDARD_C5 = new PricingTier(STANDARD, "C5", "26GB", true); + public static final PricingTier STANDARD_C6 = new PricingTier(STANDARD, "C6", "53GB", true); + + public static final PricingTier PREMIUM_C1 = new PricingTier(PREMIUM, "C1", "6GB", true); + public static final PricingTier PREMIUM_C2 = new PricingTier(PREMIUM, "C2", "13GB", true); + public static final PricingTier PREMIUM_C3 = new PricingTier(PREMIUM, "C3", "26GB", true); + public static final PricingTier PREMIUM_C4 = new PricingTier(PREMIUM, "C4", "53GB", true); + public static final PricingTier PREMIUM_C5 = new PricingTier(PREMIUM, "C5", "120GB", true); + + public boolean isBasic() { + return StringUtils.equals(family, BASIC); + } + + public boolean isStandard() { + return StringUtils.equals(family, STANDARD); + } + + public boolean isPremium() { + return StringUtils.equals(family, PREMIUM); + } + + public int getSize() { + return Integer.parseInt(this.capacity.substring(1)); + } + + private static final List values = new ImmutableList.Builder().add( + BASIC_C0, BASIC_C1, BASIC_C2, BASIC_C3, BASIC_C4, BASIC_C5, BASIC_C6, + STANDARD_C0, STANDARD_C1, STANDARD_C2, STANDARD_C3, STANDARD_C4, STANDARD_C5, STANDARD_C6, + PREMIUM_C1, PREMIUM_C2, PREMIUM_C3, PREMIUM_C4 + ).build(); + + public static List values() { + return values; + } + + @Override + public String toString() { + return String.format("%s %s (%s)", family, capacity, replication ? (memory + ", Replication") : memory); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/RedisCache.java b/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/RedisCache.java new file mode 100644 index 0000000000..8744697d0a --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/RedisCache.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.redis; + +import com.azure.core.management.exception.ManagementException; +import com.azure.resourcemanager.redis.RedisManager; +import com.microsoft.azure.toolkit.lib.common.entity.AbstractAzureResource; +import com.microsoft.azure.toolkit.lib.common.entity.IAzureResource; +import com.microsoft.azure.toolkit.lib.common.event.AzureOperationEvent; +import com.microsoft.azure.toolkit.lib.common.operation.AzureOperation; +import org.apache.http.HttpStatus; +import org.jetbrains.annotations.Nullable; + +import javax.annotation.Nonnull; + +public class RedisCache extends AbstractAzureResource + implements AzureOperationEvent.Source, IAzureResource { + + @Nonnull + private final RedisManager manager; + + public RedisCache(RedisManager manager, com.azure.resourcemanager.redis.models.RedisCache redis) { + super(new RedisCacheEntity(redis)); + this.manager = manager; + } + + @AzureOperation(name = "redis.delete", params = {"this.name()"}, type = AzureOperation.Type.SERVICE) + public void delete() { + manager.redisCaches().deleteById(this.id()); + refresh(); + } + + @Nullable + @Override + protected com.azure.resourcemanager.redis.models.RedisCache loadRemote() { + try { + this.entity().setRemote(manager.redisCaches().getById(this.entity.getId())); + } catch (ManagementException ex) { + if (HttpStatus.SC_NOT_FOUND == ex.getResponse().getStatusCode()) { + return null; + } else { + throw ex; + } + } + return entity.getRemote(); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/RedisCacheEntity.java b/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/RedisCacheEntity.java new file mode 100644 index 0000000000..8da6a6c6cb --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/RedisCacheEntity.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.redis; + +import com.azure.resourcemanager.redis.models.RedisCache; +import com.azure.resourcemanager.resources.fluentcore.arm.ResourceId; +import com.azure.resourcemanager.resources.fluentcore.arm.models.Resource; +import com.microsoft.azure.toolkit.lib.common.entity.AbstractAzureResource; +import com.microsoft.azure.toolkit.lib.common.model.Region; + +import javax.annotation.Nonnull; +import java.util.Optional; + +public class RedisCacheEntity extends AbstractAzureResource.RemoteAwareResourceEntity { + + @Nonnull + private final ResourceId resourceId; + + public RedisCacheEntity(com.azure.resourcemanager.redis.models.RedisCache redis) { + this.resourceId = ResourceId.fromString(redis.id()); + this.remote = redis; + } + + @Override + public String getSubscriptionId() { + return resourceId.subscriptionId(); + } + + public String getId() { + return resourceId.id(); + } + + @Override + public String getName() { + return resourceId.name(); + } + + public String getResourceGroupName() { + return resourceId.resourceGroupName(); + } + + public Region getRegion() { + return remoteOptional().map(remote -> Region.fromName(remote.regionName())).orElse(null); + } + + public String getType() { + return remoteOptional().map(Resource::type).orElse(null); + } + + public int getSSLPort() { + return remoteOptional().map(RedisCache::sslPort).orElse(-1); + } + + public boolean getNonSslPortEnabled() { + return remoteOptional().map(RedisCache::nonSslPort).orElse(false); + } + + public String getRedisVersion() { + return remoteOptional().map(RedisCache::redisVersion).orElse(null); + } + + public String getPrimaryKey() { + return remoteOptional().map(remote -> remote.keys().primaryKey()).orElse(null); + } + + public String getSecondaryKey() { + return remoteOptional().map(remote -> remote.keys().secondaryKey()).orElse(null); + } + + public String getHostName() { + return remoteOptional().map(RedisCache::hostname).orElse(null); + } + + private Optional remoteOptional() { + return Optional.ofNullable(this.remote); + } +} diff --git a/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/RedisConfig.java b/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/RedisConfig.java new file mode 100644 index 0000000000..796a0a2fab --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/RedisConfig.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.redis; + + +import com.microsoft.azure.toolkit.lib.common.model.Region; +import com.microsoft.azure.toolkit.lib.common.model.ResourceGroup; +import com.microsoft.azure.toolkit.lib.common.model.Subscription; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RedisConfig { + private String name; + private String id; + private ResourceGroup resourceGroup; + private Subscription subscription; + private Region region; + private PricingTier pricingTier; + private boolean enableNonSslPort; +} diff --git a/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/RedisManagerFactory.java b/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/RedisManagerFactory.java new file mode 100644 index 0000000000..2cbe3b447b --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/java/com/microsoft/azure/toolkit/redis/RedisManagerFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.redis; + +import com.azure.core.http.policy.HttpLogDetailLevel; +import com.azure.core.http.policy.HttpPipelinePolicy; +import com.azure.core.management.profile.AzureProfile; +import com.azure.resourcemanager.redis.RedisManager; +import com.microsoft.azure.toolkit.lib.Azure; +import com.microsoft.azure.toolkit.lib.AzureConfiguration; +import com.microsoft.azure.toolkit.lib.AzureService; +import com.microsoft.azure.toolkit.lib.auth.Account; +import com.microsoft.azure.toolkit.lib.auth.AzureAccount; +import com.microsoft.azure.toolkit.lib.common.cache.Cacheable; + +import java.util.Optional; + +public final class RedisManagerFactory { + + private RedisManagerFactory() { + } + + @Cacheable(cacheName = "RedisManager", key = "$subscriptionId") + public static RedisManager create(String subscriptionId) { + final Account account = Azure.az(AzureAccount.class).account(); + final AzureConfiguration config = Azure.az().config(); + final String userAgent = config.getUserAgent(); + final HttpLogDetailLevel logLevel = Optional.ofNullable(config.getLogLevel()).map(HttpLogDetailLevel::valueOf).orElse(HttpLogDetailLevel.NONE); + final AzureProfile azureProfile = new AzureProfile(null, subscriptionId, account.getEnvironment()); + return RedisManager.configure() + .withHttpClient(AzureService.getDefaultHttpClient()) + .withLogLevel(logLevel) + .withPolicy(getUserAgentPolicy(userAgent)) + .authenticate(account.getTokenCredential(subscriptionId), azureProfile); + } + + private static HttpPipelinePolicy getUserAgentPolicy(String userAgent) { + return (httpPipelineCallContext, httpPipelineNextPolicy) -> { + final String previousUserAgent = httpPipelineCallContext.getHttpRequest().getHeaders().getValue("User-Agent"); + httpPipelineCallContext.getHttpRequest().setHeader("User-Agent", String.format("%s %s", userAgent, previousUserAgent)); + return httpPipelineNextPolicy.process(); + }; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/resources/META-INF/services/com.microsoft.azure.toolkit.lib.AzureService b/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/resources/META-INF/services/com.microsoft.azure.toolkit.lib.AzureService new file mode 100644 index 0000000000..eefbfb0023 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/resources/META-INF/services/com.microsoft.azure.toolkit.lib.AzureService @@ -0,0 +1 @@ +com.microsoft.azure.toolkit.redis.AzureRedis diff --git a/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/resources/com/microsoft/azure/toolkit/operation/title/base/redis.properties b/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/resources/com/microsoft/azure/toolkit/operation/title/base/redis.properties new file mode 100644 index 0000000000..72d04c000f --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-redis-lib/src/main/resources/com/microsoft/azure/toolkit/operation/title/base/redis.properties @@ -0,0 +1,5 @@ +redis.create=create redis cache({0}) +redis.delete=delete redis cache({0}) +redis.list.subscription|selected=list Redis Caches of selected subscriptions +redis.check_name=check name availability for redis cahe({0}) +redis.get.id=get Redis cache({0}) \ No newline at end of file diff --git a/azure-toolkit-libs/azure-toolkit-resource-lib/pom.xml b/azure-toolkit-libs/azure-toolkit-resource-lib/pom.xml index 62aca0d1e5..36ff86a00a 100644 --- a/azure-toolkit-libs/azure-toolkit-resource-lib/pom.xml +++ b/azure-toolkit-libs/azure-toolkit-resource-lib/pom.xml @@ -5,7 +5,7 @@ azure-toolkit-libs com.microsoft.azure - 0.11.0 + 0.12.1 4.0.0 diff --git a/azure-toolkit-libs/azure-toolkit-resource-lib/src/main/java/com/microsoft/azure/toolkit/lib/resource/AzureGroup.java b/azure-toolkit-libs/azure-toolkit-resource-lib/src/main/java/com/microsoft/azure/toolkit/lib/resource/AzureGroup.java index d8b5ceb6d1..f65d6999ee 100644 --- a/azure-toolkit-libs/azure-toolkit-resource-lib/src/main/java/com/microsoft/azure/toolkit/lib/resource/AzureGroup.java +++ b/azure-toolkit-libs/azure-toolkit-resource-lib/src/main/java/com/microsoft/azure/toolkit/lib/resource/AzureGroup.java @@ -5,17 +5,9 @@ package com.microsoft.azure.toolkit.lib.resource; -import com.azure.core.http.policy.HttpLogDetailLevel; -import com.azure.core.http.policy.HttpPipelinePolicy; -import com.azure.core.management.profile.AzureProfile; -import com.azure.resourcemanager.resources.ResourceManager; import com.azure.resourcemanager.resources.fluentcore.arm.ResourceId; -import com.microsoft.azure.toolkit.lib.Azure; -import com.microsoft.azure.toolkit.lib.AzureConfiguration; import com.microsoft.azure.toolkit.lib.AzureService; import com.microsoft.azure.toolkit.lib.SubscriptionScoped; -import com.microsoft.azure.toolkit.lib.auth.Account; -import com.microsoft.azure.toolkit.lib.auth.AzureAccount; import com.microsoft.azure.toolkit.lib.common.cache.Cacheable; import com.microsoft.azure.toolkit.lib.common.cache.Preload; import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException; @@ -96,27 +88,4 @@ private static ResourceGroup fromResource(@Nonnull com.azure.resourcemanager.res String id = resource.id(); return ResourceGroup.builder().subscriptionId(subscriptionId).id(id).name(name).region(region).build(); } - - @Cacheable(cacheName = "resource/{}/manager", key = "$subscriptionId") - private ResourceManager getResourceManager(String subscriptionId) { - final Account account = Azure.az(AzureAccount.class).account(); - final AzureConfiguration config = Azure.az().config(); - final String userAgent = config.getUserAgent(); - final HttpLogDetailLevel logDetailLevel = config.getLogLevel() == null ? - HttpLogDetailLevel.NONE : HttpLogDetailLevel.valueOf(config.getLogLevel()); - final AzureProfile azureProfile = new AzureProfile(account.getEnvironment()); - return ResourceManager.configure() - .withLogLevel(logDetailLevel) - .withPolicy(getUserAgentPolicy(userAgent)) // set user agent with policy - .authenticate(account.getTokenCredential(subscriptionId), azureProfile) - .withSubscription(subscriptionId); - } - - private HttpPipelinePolicy getUserAgentPolicy(String userAgent) { - return (httpPipelineCallContext, httpPipelineNextPolicy) -> { - final String previousUserAgent = httpPipelineCallContext.getHttpRequest().getHeaders().getValue("User-Agent"); - httpPipelineCallContext.getHttpRequest().setHeader("User-Agent", String.format("%s %s", userAgent, previousUserAgent)); - return httpPipelineNextPolicy.process(); - }; - } } diff --git a/azure-toolkit-libs/azure-toolkit-springcloud-lib/pom.xml b/azure-toolkit-libs/azure-toolkit-springcloud-lib/pom.xml index 00a3ba103d..e3308f61e0 100644 --- a/azure-toolkit-libs/azure-toolkit-springcloud-lib/pom.xml +++ b/azure-toolkit-libs/azure-toolkit-springcloud-lib/pom.xml @@ -5,7 +5,7 @@ azure-toolkit-libs com.microsoft.azure - 0.11.0 + 0.12.1 4.0.0 diff --git a/azure-toolkit-libs/azure-toolkit-springcloud-lib/src/main/java/com/microsoft/azure/toolkit/lib/springcloud/AzureSpringCloud.java b/azure-toolkit-libs/azure-toolkit-springcloud-lib/src/main/java/com/microsoft/azure/toolkit/lib/springcloud/AzureSpringCloud.java index 8c76356444..5bc8f95c04 100644 --- a/azure-toolkit-libs/azure-toolkit-springcloud-lib/src/main/java/com/microsoft/azure/toolkit/lib/springcloud/AzureSpringCloud.java +++ b/azure-toolkit-libs/azure-toolkit-springcloud-lib/src/main/java/com/microsoft/azure/toolkit/lib/springcloud/AzureSpringCloud.java @@ -18,6 +18,8 @@ import com.microsoft.azure.toolkit.lib.SubscriptionScoped; import com.microsoft.azure.toolkit.lib.auth.Account; import com.microsoft.azure.toolkit.lib.auth.AzureAccount; +import com.microsoft.azure.toolkit.lib.common.cache.CacheEvict; +import com.microsoft.azure.toolkit.lib.common.cache.CacheManager; import com.microsoft.azure.toolkit.lib.common.cache.Cacheable; import com.microsoft.azure.toolkit.lib.common.cache.Preload; import com.microsoft.azure.toolkit.lib.common.event.AzureOperationEvent; @@ -32,6 +34,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @Slf4j @@ -68,16 +71,16 @@ private SpringCloudCluster cluster(@Nonnull SpringCloudClusterEntity cluster) { @Nonnull @Preload @AzureOperation(name = "springcloud|cluster.list.subscription|selected", type = AzureOperation.Type.SERVICE) - public List clusters(boolean... force) { + public List clusters() { return this.getSubscriptions().stream().parallel() - .flatMap(s -> clusters(s.getId(), force).stream()) + .flatMap(s -> clusters(s.getId()).stream()) .collect(Collectors.toList()); } @Nonnull - @Cacheable(cacheName = "asc/{}/clusters", key = "$subscriptionId", condition = "!(force&&force[0])") + @Cacheable(cacheName = "asc/{}/clusters", key = "$subscriptionId") @AzureOperation(name = "springcloud|cluster.list.subscription", params = "subscriptionId", type = AzureOperation.Type.SERVICE) - private List clusters(@Nonnull String subscriptionId, boolean... force) { + private List clusters(@Nonnull String subscriptionId) { try { return getClient(subscriptionId).list().stream() .map(this::cluster) @@ -93,7 +96,11 @@ private List clusters(@Nonnull String subscriptionId, boolea @AzureOperation(name = "common|service.refresh", params = "this.name()", type = AzureOperation.Type.SERVICE) public void refresh() { - this.clusters(true); + try { + CacheManager.evictCache("asc/{}/clusters", CacheEvict.ALL); + } catch (ExecutionException e) { + log.warn("failed to evict cache", e); + } } @Override @@ -110,6 +117,7 @@ protected SpringServices getClient(final String subscriptionId) { final HttpLogDetailLevel logLevel = Optional.ofNullable(config.getLogLevel()).map(HttpLogDetailLevel::valueOf).orElse(HttpLogDetailLevel.NONE); final AzureProfile azureProfile = new AzureProfile(null, subscriptionId, account.getEnvironment()); return AppPlatformManager.configure() + .withHttpClient(AzureService.getDefaultHttpClient()) .withLogLevel(logLevel) .withPolicy(getUserAgentPolicy(userAgent)) // set user agent with policy .authenticate(account.getTokenCredential(subscriptionId), azureProfile) diff --git a/azure-toolkit-libs/azure-toolkit-sqlserver-lib/pom.xml b/azure-toolkit-libs/azure-toolkit-sqlserver-lib/pom.xml index 4a144c0e97..4f60210563 100644 --- a/azure-toolkit-libs/azure-toolkit-sqlserver-lib/pom.xml +++ b/azure-toolkit-libs/azure-toolkit-sqlserver-lib/pom.xml @@ -5,7 +5,7 @@ azure-toolkit-libs com.microsoft.azure - 0.11.0 + 0.12.1 4.0.0 diff --git a/azure-toolkit-libs/azure-toolkit-sqlserver-lib/src/main/java/com/microsoft/azure/toolkit/lib/sqlserver/AzureSqlServer.java b/azure-toolkit-libs/azure-toolkit-sqlserver-lib/src/main/java/com/microsoft/azure/toolkit/lib/sqlserver/AzureSqlServer.java index 545a4623b3..73321cd859 100644 --- a/azure-toolkit-libs/azure-toolkit-sqlserver-lib/src/main/java/com/microsoft/azure/toolkit/lib/sqlserver/AzureSqlServer.java +++ b/azure-toolkit-libs/azure-toolkit-sqlserver-lib/src/main/java/com/microsoft/azure/toolkit/lib/sqlserver/AzureSqlServer.java @@ -4,7 +4,6 @@ */ package com.microsoft.azure.toolkit.lib.sqlserver; -import com.azure.core.management.Region; import com.azure.resourcemanager.resources.fluentcore.arm.ResourceId; import com.azure.resourcemanager.sql.SqlServerManager; import com.azure.resourcemanager.sql.models.CapabilityStatus; @@ -69,7 +68,7 @@ public CheckNameAvailabilityResultEntity checkNameAvailability(String subscripti public boolean checkRegionCapability(String subscriptionId, String region) { SqlServerManager manager = SqlServerManagerFactory.create(subscriptionId); - com.azure.resourcemanager.sql.models.RegionCapabilities capabilities = manager.sqlServers().getCapabilitiesByRegion(Region.fromName(region)); + com.azure.resourcemanager.sql.models.RegionCapabilities capabilities = manager.sqlServers().getCapabilitiesByRegion(com.azure.core.management.Region.fromName(region)); return Objects.nonNull(capabilities.status()) && CapabilityStatus.AVAILABLE == capabilities.status(); } @@ -116,4 +115,7 @@ public AzureOperationEvent.Source getEventSource() { } } + public String name() { + return "Microsoft.SQL/servers"; + } } diff --git a/azure-toolkit-libs/azure-toolkit-sqlserver-lib/src/main/java/com/microsoft/azure/toolkit/lib/sqlserver/SqlServerManagerFactory.java b/azure-toolkit-libs/azure-toolkit-sqlserver-lib/src/main/java/com/microsoft/azure/toolkit/lib/sqlserver/SqlServerManagerFactory.java index e4c219e915..322f6041df 100644 --- a/azure-toolkit-libs/azure-toolkit-sqlserver-lib/src/main/java/com/microsoft/azure/toolkit/lib/sqlserver/SqlServerManagerFactory.java +++ b/azure-toolkit-libs/azure-toolkit-sqlserver-lib/src/main/java/com/microsoft/azure/toolkit/lib/sqlserver/SqlServerManagerFactory.java @@ -1,3 +1,8 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + package com.microsoft.azure.toolkit.lib.sqlserver; import com.azure.core.http.policy.HttpLogDetailLevel; @@ -6,6 +11,7 @@ import com.azure.resourcemanager.sql.SqlServerManager; import com.microsoft.azure.toolkit.lib.Azure; import com.microsoft.azure.toolkit.lib.AzureConfiguration; +import com.microsoft.azure.toolkit.lib.AzureService; import com.microsoft.azure.toolkit.lib.auth.Account; import com.microsoft.azure.toolkit.lib.auth.AzureAccount; import com.microsoft.azure.toolkit.lib.common.cache.Cacheable; @@ -25,6 +31,7 @@ public static SqlServerManager create(String subscriptionId) { final HttpLogDetailLevel logLevel = Optional.ofNullable(config.getLogLevel()).map(HttpLogDetailLevel::valueOf).orElse(HttpLogDetailLevel.NONE); final AzureProfile azureProfile = new AzureProfile(null, subscriptionId, account.getEnvironment()); return SqlServerManager.configure() + .withHttpClient(AzureService.getDefaultHttpClient()) .withLogLevel(logLevel) .withPolicy(getUserAgentPolicy(userAgent)) .authenticate(account.getTokenCredential(subscriptionId), azureProfile); diff --git a/azure-toolkit-libs/azure-toolkit-sqlserver-lib/src/main/java/com/microsoft/azure/toolkit/lib/sqlserver/model/SqlServerEntity.java b/azure-toolkit-libs/azure-toolkit-sqlserver-lib/src/main/java/com/microsoft/azure/toolkit/lib/sqlserver/model/SqlServerEntity.java index aec5e6bc09..88098f3948 100644 --- a/azure-toolkit-libs/azure-toolkit-sqlserver-lib/src/main/java/com/microsoft/azure/toolkit/lib/sqlserver/model/SqlServerEntity.java +++ b/azure-toolkit-libs/azure-toolkit-sqlserver-lib/src/main/java/com/microsoft/azure/toolkit/lib/sqlserver/model/SqlServerEntity.java @@ -75,6 +75,11 @@ public String getState() { return remoteOptional().map(SqlServer::state).orElse(null); } + @Override + public String getVersion() { + return remoteOptional().map(SqlServer::version).orElse(null); + } + private Optional remoteOptional() { return Optional.ofNullable(this.remote); } diff --git a/azure-toolkit-libs/azure-toolkit-storage-lib/pom.xml b/azure-toolkit-libs/azure-toolkit-storage-lib/pom.xml index 3e341398c5..2d10f5b927 100644 --- a/azure-toolkit-libs/azure-toolkit-storage-lib/pom.xml +++ b/azure-toolkit-libs/azure-toolkit-storage-lib/pom.xml @@ -5,7 +5,7 @@ azure-toolkit-libs com.microsoft.azure - 0.11.0 + 0.12.1 4.0.0 @@ -114,4 +114,4 @@ - \ No newline at end of file + diff --git a/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/StorageManagerFactory.java b/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/StorageManagerFactory.java index 6af5ffab41..7cb3e6a370 100644 --- a/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/StorageManagerFactory.java +++ b/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/StorageManagerFactory.java @@ -11,6 +11,7 @@ import com.azure.resourcemanager.storage.StorageManager; import com.microsoft.azure.toolkit.lib.Azure; import com.microsoft.azure.toolkit.lib.AzureConfiguration; +import com.microsoft.azure.toolkit.lib.AzureService; import com.microsoft.azure.toolkit.lib.auth.Account; import com.microsoft.azure.toolkit.lib.auth.AzureAccount; import com.microsoft.azure.toolkit.lib.common.cache.Cacheable; @@ -30,6 +31,7 @@ public static StorageManager create(String subscriptionId) { final HttpLogDetailLevel logLevel = Optional.ofNullable(config.getLogLevel()).map(HttpLogDetailLevel::valueOf).orElse(HttpLogDetailLevel.NONE); final AzureProfile azureProfile = new AzureProfile(null, subscriptionId, account.getEnvironment()); return StorageManager.configure() + .withHttpClient(AzureService.getDefaultHttpClient()) .withLogLevel(logLevel) .withPolicy(getUserAgentPolicy(userAgent)) .authenticate(account.getTokenCredential(subscriptionId), azureProfile); diff --git a/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/model/AccessTier.java b/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/model/AccessTier.java new file mode 100644 index 0000000000..bf7e891667 --- /dev/null +++ b/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/model/AccessTier.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.toolkit.lib.storage.model; + +public enum AccessTier { + HOT("Hot"), + COOL("Cool"); + private final String value; + + AccessTier(String value) { + this.value = value; + } + + public String toString() { + return this.value; + } +} diff --git a/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/model/Kind.java b/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/model/Kind.java index 5f7c2ad015..c3ea408d87 100644 --- a/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/model/Kind.java +++ b/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/model/Kind.java @@ -25,7 +25,8 @@ public class Kind implements ExpandableParameter { public static final Kind FILE_STORAGE = new Kind(Performance.PREMIUM, "FileStorage", "File Storage"); public static final Kind PAGE_BLOB_STORAGE = new Kind(Performance.PREMIUM, "StorageV2", "Page Blobs Storage"); - private static final List values = new ImmutableList.Builder().add(STORAGE, STORAGE_V2, BLOCK_BLOB_STORAGE, FILE_STORAGE, PAGE_BLOB_STORAGE).build(); + private static final List values = new ImmutableList.Builder().add(STORAGE, STORAGE_V2, + BLOCK_BLOB_STORAGE, FILE_STORAGE, PAGE_BLOB_STORAGE).build(); private final Performance performance; private final String name; diff --git a/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/model/StorageAccountConfig.java b/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/model/StorageAccountConfig.java index 31524a64da..2099268b2d 100644 --- a/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/model/StorageAccountConfig.java +++ b/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/model/StorageAccountConfig.java @@ -9,6 +9,7 @@ import com.microsoft.azure.toolkit.lib.common.model.ResourceGroup; import com.microsoft.azure.toolkit.lib.common.model.Subscription; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; @@ -17,6 +18,7 @@ @Getter @Setter @Builder +@EqualsAndHashCode public class StorageAccountConfig implements IStorageAccountEntity { private String name; @@ -28,6 +30,7 @@ public class StorageAccountConfig implements IStorageAccountEntity { private Performance performance; private Kind kind; private Redundancy redundancy; + private AccessTier accessTier; @Override public String getSubscriptionId() { diff --git a/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/service/AzureStorageAccount.java b/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/service/AzureStorageAccount.java index d87d6aefd0..c11b29230d 100644 --- a/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/service/AzureStorageAccount.java +++ b/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/service/AzureStorageAccount.java @@ -5,15 +5,19 @@ package com.microsoft.azure.toolkit.lib.storage.service; - import com.azure.resourcemanager.resources.fluentcore.arm.ResourceId; import com.azure.resourcemanager.storage.StorageManager; +import com.azure.resourcemanager.storage.models.AccessTier; import com.azure.resourcemanager.storage.models.CheckNameAvailabilityResult; import com.azure.resourcemanager.storage.models.Reason; import com.azure.resourcemanager.storage.models.SkuName; import com.azure.resourcemanager.storage.models.StorageAccountSkuType; +import com.microsoft.azure.toolkit.lib.Azure; import com.microsoft.azure.toolkit.lib.AzureService; import com.microsoft.azure.toolkit.lib.SubscriptionScoped; +import com.microsoft.azure.toolkit.lib.common.cache.CacheEvict; +import com.microsoft.azure.toolkit.lib.common.cache.CacheManager; +import com.microsoft.azure.toolkit.lib.common.cache.Cacheable; import com.microsoft.azure.toolkit.lib.common.entity.CheckNameAvailabilityResultEntity; import com.microsoft.azure.toolkit.lib.common.event.AzureOperationEvent; import com.microsoft.azure.toolkit.lib.common.model.Subscription; @@ -24,15 +28,20 @@ import com.microsoft.azure.toolkit.lib.storage.model.Performance; import com.microsoft.azure.toolkit.lib.storage.model.Redundancy; import com.microsoft.azure.toolkit.lib.storage.model.StorageAccountConfig; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -public class AzureStorageAccount extends SubscriptionScoped implements AzureService { +@Slf4j +public class AzureStorageAccount extends SubscriptionScoped + implements AzureService, AzureOperationEvent.Source { public AzureStorageAccount() { super(AzureStorageAccount::new); @@ -44,22 +53,27 @@ private AzureStorageAccount(@Nonnull final List subscriptions) { public List list() { return getSubscriptions().stream() - .map(subscription -> StorageManagerFactory.create(subscription.getId())) - .flatMap(manager -> manager.storageAccounts().list().stream()) - .map(account -> new StorageAccount(account.manager(), account)) + .flatMap(s -> list(s.getId()).stream()) + .collect(Collectors.toList()); + } + + @Cacheable(cacheName = "storage/{}/accounts", key = "$sid") + public List list(String sid) { + return StorageManagerFactory.create(sid).storageAccounts().list().stream() + .map(StorageAccount::new) .collect(Collectors.toList()); } public StorageAccount get(@Nonnull String id) { final com.azure.resourcemanager.storage.models.StorageAccount account = StorageManagerFactory.create(ResourceId.fromString(id).subscriptionId()).storageAccounts().getById(id); - return new StorageAccount(account.manager(), account); + return new StorageAccount(account); } public StorageAccount get(@Nonnull final String resourceGroup, @Nonnull final String name) { final com.azure.resourcemanager.storage.models.StorageAccount account = StorageManagerFactory.create(getDefaultSubscription().getId()).storageAccounts().getByResourceGroup(resourceGroup, name); - return new StorageAccount(account.manager(), account); + return new StorageAccount(account); } public CheckNameAvailabilityResultEntity checkNameAvailability(String subscriptionId, String name) { @@ -84,17 +98,23 @@ public List listSupportedRedundancies(@Nonnull Performance performan .collect(Collectors.toList()); } + @AzureOperation(name = "common|service.refresh", params = "this.name()", type = AzureOperation.Type.SERVICE) + public void refresh() { + try { + CacheManager.evictCache("storage/{}/accounts", CacheEvict.ALL); + } catch (ExecutionException e) { + log.warn("failed to evict cache", e); + } + } + public Creator create(StorageAccountConfig config) { return new Creator(config); } - public class Creator implements ICommittable, AzureOperationEvent.Source { + @RequiredArgsConstructor + public static class Creator implements ICommittable, AzureOperationEvent.Source { - private StorageAccountConfig config; - - Creator(StorageAccountConfig config) { - this.config = config; - } + private final StorageAccountConfig config; @Override @AzureOperation(name = "storage|account.create", params = {"this.config.getName()"}, type = AzureOperation.Type.SERVICE) @@ -111,16 +131,25 @@ public StorageAccount commit() { withCreate = withCreate.withFileStorageAccountKind(); } else if (Objects.equals(Kind.BLOCK_BLOB_STORAGE, config.getKind())) { withCreate = withCreate.withBlockBlobStorageAccountKind(); + } else if (Objects.equals(Kind.BLOB_STORAGE, config.getKind())) { + withCreate = withCreate.withBlobStorageAccountKind().withAccessTier( + Optional.ofNullable(config.getAccessTier()).map(t -> AccessTier.fromString(t.toString())).orElse(null)); } else { withCreate = withCreate.withGeneralPurposeAccountKindV2(); } com.azure.resourcemanager.storage.models.StorageAccount account = withCreate.create(); - return new StorageAccount(account.manager(), account); + Azure.az(AzureStorageAccount.class).refresh(); + return new StorageAccount(account); } + @Nonnull public AzureOperationEvent.Source getEventSource() { - return new AzureOperationEvent.Source() {}; + return new AzureOperationEvent.Source() { + }; } } + public String name() { + return "Microsoft.Storage/storageAccounts"; + } } diff --git a/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/service/StorageAccount.java b/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/service/StorageAccount.java index f3c0c71ea0..ed720e2de5 100644 --- a/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/service/StorageAccount.java +++ b/azure-toolkit-libs/azure-toolkit-storage-lib/src/main/java/com/microsoft/azure/toolkit/lib/storage/service/StorageAccount.java @@ -5,25 +5,31 @@ package com.microsoft.azure.toolkit.lib.storage.service; +import com.azure.core.management.AzureEnvironment; import com.azure.core.management.exception.ManagementException; +import com.azure.resourcemanager.resources.fluentcore.utils.ResourceManagerUtils; import com.azure.resourcemanager.storage.StorageManager; +import com.microsoft.azure.toolkit.lib.Azure; +import com.microsoft.azure.toolkit.lib.auth.AzureCloud; import com.microsoft.azure.toolkit.lib.common.entity.AbstractAzureResource; import com.microsoft.azure.toolkit.lib.common.entity.IAzureResource; +import com.microsoft.azure.toolkit.lib.common.entity.Removable; import com.microsoft.azure.toolkit.lib.common.event.AzureOperationEvent; import com.microsoft.azure.toolkit.lib.common.operation.AzureOperation; import com.microsoft.azure.toolkit.lib.storage.model.StorageAccountEntity; import org.apache.http.HttpStatus; import javax.annotation.Nonnull; +import java.util.Objects; public class StorageAccount extends AbstractAzureResource - implements AzureOperationEvent.Source, IAzureResource { + implements Removable, AzureOperationEvent.Source, IAzureResource { @Nonnull private final StorageManager manager; - public StorageAccount(@Nonnull StorageManager manager, @Nonnull com.azure.resourcemanager.storage.models.StorageAccount server) { + public StorageAccount(@Nonnull com.azure.resourcemanager.storage.models.StorageAccount server) { super(new StorageAccountEntity(server)); - this.manager = manager; + this.manager = server.manager(); } @Override @@ -42,6 +48,27 @@ protected com.azure.resourcemanager.storage.models.StorageAccount loadRemote() { @AzureOperation(name = "storage|account.delete", params = {"this.entity().getName()"}, type = AzureOperation.Type.SERVICE) public void delete() { - manager.storageAccounts().deleteById(this.entity.getId()); + if (this.exists()) { + this.status(Status.PENDING); + manager.storageAccounts().deleteById(this.entity.getId()); + Azure.az(AzureStorageAccount.class).refresh(); + } + } + + @AzureOperation(name = "storage|account.get_connection_string", params = {"this.entity().getName()"}, type = AzureOperation.Type.SERVICE) + public String getConnectionString() { + // see https://github.com/Azure/azure-cli/blob/ac3b190d4d/src/azure-cli/azure/cli/command_modules/storage/operations/account.py#L232 + final AzureEnvironment environment = Azure.az(AzureCloud.class).get(); + return ResourceManagerUtils.getStorageConnectionString(this.name(), getKey(), environment); + } + + @AzureOperation(name = "storage|account.get_key", params = {"this.entity().getName()"}, type = AzureOperation.Type.SERVICE) + public String getKey() { + return Objects.requireNonNull(this.remote()).getKeys().get(0).keyName(); + } + + @Override + public void remove() { + this.delete(); } } diff --git a/azure-toolkit-libs/pom.xml b/azure-toolkit-libs/pom.xml index 8e3f287b4b..3b5e7801ef 100644 --- a/azure-toolkit-libs/pom.xml +++ b/azure-toolkit-libs/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.microsoft.azure azure-toolkit-libs - 0.11.0 + 0.12.1 pom Libs for Azure Toolkits Wrapped libs for Microsoft Azure Toolkits @@ -45,6 +45,8 @@ azure-toolkit-applicationinsights-lib azure-toolkit-storage-lib azure-toolkit-database-lib + azure-toolkit-redis-lib + azure-toolkit-compute-lib @@ -66,19 +68,15 @@ 1.6.7 2.6.3 [2.0,) - 1.0.0-beta 1.0.0-beta.1 - 1.0.0-beta 1.7.12 1.3.0 2.5.0 8.6.6 1.41.1 3.2.2 - 1.6.2 12.9.1 1.4.2 - 11.0.1 4.2.8 4.2.8 4.1.8 @@ -87,7 +85,7 @@ 12.11.1 12.9.1 12.5.1 - + 1.9.2 1.9.7.M3 3.3.0 1.15 @@ -100,17 +98,15 @@ 1.21 2.7 2.1.3 - 2.7.1 2.8.7 30.1.1-jre 4.5.13 - 4.9.1 2.12.3 0.8.7 2.3.2 21.0.1 2.2.14 - 2.5.11 + 3.0.9 4.13.2 1.18.20 3.10.0 @@ -122,15 +118,16 @@ 1.1.1 0.9.1 0.9.12 + 1.0.6 1.9.2 - 9.7 + 9.15 3.1.0 9.9.3 2.10.10 4.4.14 1.2.2 3.0.2-b01 - 3.0.2-b01 + 3.0.2 3.0.1 5.87.0.RELEASE 0.9.0 @@ -140,6 +137,7 @@ 2.0.9 1.0.0 1.0.57 + 2.7.0 @@ -161,6 +159,11 @@ azure-toolkit-common-lib ${azure.toolkit-lib.version} + + com.azure + azure-core-http-netty + ${azure.core.http.netty.version} + com.microsoft.azure azure-toolkit-auth-lib @@ -206,18 +209,17 @@ azure-toolkit-storage-lib ${azure.toolkit-lib.version} - com.microsoft.azure - azure - ${azure.version} - - - javax.xml.bind - jaxb-api - - + azure-toolkit-redis-lib + ${azure.toolkit-lib.version} + + com.microsoft.azure + azure-toolkit-compute-lib + ${azure.toolkit-lib.version} + + com.microsoft.azure azure-annotations @@ -238,11 +240,6 @@ azure-client-authentication ${azure.client.version} - - com.microsoft.azure - azure-mgmt-storage - ${azure.version} - com.microsoft.azure applicationinsights-core @@ -303,21 +300,11 @@ azure-security-keyvault-certificates ${azure.security.keyvault.certificates.version} - - com.microsoft.azure.appplatform.v2020_07_01 - azure-mgmt-appplatform - ${azure.azure-mgmt-appplatform.version} - com.azure azure-core-http-netty ${azure.core-http-netty.version} - - com.microsoft.azure - azure-mgmt-resources - ${azure.version} - com.nimbusds oauth2-oidc-sdk @@ -512,7 +499,7 @@ com.azure.resourcemanager - azure-resourcemanager + azure-resourcemanager-appservice ${azure.resourcemanager.version} @@ -535,11 +522,21 @@ azure-resourcemanager-mysql ${azure-resourcemanager-mysql.version} + + com.azure.resourcemanager + azure-resourcemanager-redis + ${azure-resourcemanager-redis.version} + com.azure.resourcemanager azure-resourcemanager-appplatform ${azure.resourcemanager.version} + + com.azure.resourcemanager + azure-resourcemanager-compute + ${azure.resourcemanager.version} + com.azure azure-identity @@ -571,30 +568,10 @@ com.azure - azure-core-http-netty + azure-core-http-okhttp - - com.azure - azure-core-http-okhttp - ${azure.core.http.okhttp.version} - - - com.squareup.okhttp3 - okhttp - ${okhttp.version} - - - com.squareup.okhttp3 - logging-interceptor - ${okhttp.version} - - - com.squareup.okhttp3 - okhttp-urlconnection - ${okhttp.version} - org.apache.httpcomponents httpclient @@ -662,17 +639,6 @@ httpcore ${httpcore.version} - - com.google.errorprone - error_prone_core - ${error.prone.core.version} - - - com.google.guava - guava - - - com.microsoft.azure azure-arm-client-runtime @@ -698,6 +664,11 @@ java-semver ${semver.version} + + io.projectreactor.netty + reactor-netty + ${reactor.netty.version} + jakarta.xml.bind @@ -713,6 +684,16 @@ com.networknt json-schema-validator ${networknt.json-schema-validator.version} + + + com.sun.mail + mailapi + + + org.mozilla + rhino + + com.github.java-json-tools diff --git a/azure-webapp-maven-plugin/CHANGELOG.md b/azure-webapp-maven-plugin/CHANGELOG.md index 798e874c59..697f6d60ff 100644 --- a/azure-webapp-maven-plugin/CHANGELOG.md +++ b/azure-webapp-maven-plugin/CHANGELOG.md @@ -1,6 +1,7 @@ # Change Log All notable changes to the "Maven Plugin for Azure App Service" will be documented in this file. - [Change Log](#change-log) + - [2.2.0](#210) - [2.1.0](#210) - [2.0.0](#200) - [1.15.0](#1150) @@ -26,6 +27,14 @@ All notable changes to the "Maven Plugin for Azure App Service" will be document - [1.1.0](#110) - [1.0.0](#100) +## 2.2.0 +- Support default value for region/pricing tier/webContainer/javaVersion [#1755](https://github.com/microsoft/azure-maven-plugins/pull/1755) +- Support flag to skip create azure resources PR [#1762](https://github.com/microsoft/azure-maven-plugins/pull/1762), Issue [#1651](https://github.com/microsoft/azure-maven-plugins/issues/1651) +- Support username and password in proxy [#1677](https://github.com/microsoft/azure-maven-plugins/pull/1677) +- Check whether webapp name is available before creating webapp [#1728](https://github.com/microsoft/azure-maven-plugins/pull/1728) +- Remove unsupported JBoss 7.2 Runtime(use JBoss 7 instead) [#1751](https://github.com/microsoft/azure-maven-plugins/pull/1751) +- Fix warning message of `illegal reflective access from groovy` [#1763](https://github.com/microsoft/azure-maven-plugins/pull/1763) + ## 2.1.0 - Support static validation against json schema [#1647](https://github.com/microsoft/azure-maven-plugins/pull/1647) - Support custom values for pricingTier/region/webContainer/javaVersion [#1643](https://github.com/microsoft/azure-maven-plugins/pull/1643) @@ -138,4 +147,3 @@ All notable changes to the "Maven Plugin for Azure App Service" will be document ## 1.0.0 - Add the support for deploying Web App to an existing App Service Plan - diff --git a/azure-webapp-maven-plugin/README.md b/azure-webapp-maven-plugin/README.md index d9bee75d41..385104014a 100644 --- a/azure-webapp-maven-plugin/README.md +++ b/azure-webapp-maven-plugin/README.md @@ -25,7 +25,7 @@ Mavan plugins supports Azure Cli and some other auth methods, see [Authenticatio You can prepare your application for Azure Web App easily with one command: ```shell -mvn com.microsoft.azure:azure-webapp-maven-plugin:1.14.0:config +mvn com.microsoft.azure:azure-webapp-maven-plugin:2.2.0:config ``` This command adds a `azure-webapp-maven-plugin` plugin and related configuration by prompting you to select an existing Azure Web App or create a new one. Then you can deploy your Java app to Azure using the following command: @@ -39,7 +39,7 @@ Here is a typical configuration for Azure Web App Maven Plugin: com.microsoft.azure azure-webapp-maven-plugin - 2.1.0 + 2.2.0 111111-11111-11111-1111111 spring-boot-xxxxxxxxxx-rg @@ -72,12 +72,12 @@ Property | Required | Description `` | false | Specifies the target subscription.
    Use this setting when you have multiple subscriptions in your authentication file.| `` | true | Azure Resource Group for your Web App. | `` | true | The name of your Web App. | -``| false | The pricing tier for your Web App. The default value is **P1V2**.| -``| false | Specifies the region where your Web App will be hosted; the default value is **westeurope**. All valid regions at [Supported Regions](#region) section. | -``| false | Specifies the os, supported values are *Linux*, *Windows* and *Docker*. | -``| false | Specifies the runtime stack, values for Linux are: *Tomcat 8.5*, *Tomcat 9.0*, *Java SE*, *JbossEAP 7.2*| -``| false | Specifies the java version, values are: *Java 8* or *Java 11*| -``| false | Specifies the target file to be deployed | +``| false | The pricing tier for your Web App. The default value is **P1V2**(**P1v3** for JBoss).| +``| false | Specifies the region where your Web App will be hosted; the default value is **centralus**(or the first region if centralus is not available in your subscription). All valid regions at [Supported Regions](#region) section. | + ``| false | Specifies the os, supported values are *Linux*, *Windows* and *Docker*. The default value is **linux**| +``| false | Specifies the runtime stack, values for Linux are: *Tomcat 8.5*, *Tomcat 9.0*, *Java SE*, *JBossEAP 7*, The default value would be **Tomcat 8.5** or **Java SE** or **JBossEAP 7** according to your project type | +``| false | Specifies the java version, values are: *Java 8* or *Java 11*. The default value is your project compiler level| +``| false | Specifies the target file to be deployed. If it is not specified, a default webapp is created without any deployments. | ## Feedback and Questions To report bugs or request new features, file issues on [Issues](https://github.com/microsoft/azure-maven-plugins/issues). Or, ask questions on [Stack Overflow with tag azure-java-tools](https://stackoverflow.com/questions/tagged/azure-java-tools). diff --git a/azure-webapp-maven-plugin/pom.xml b/azure-webapp-maven-plugin/pom.xml index 63467a90c2..bcefd09af1 100644 --- a/azure-webapp-maven-plugin/pom.xml +++ b/azure-webapp-maven-plugin/pom.xml @@ -7,11 +7,11 @@ com.microsoft.azure azure-maven-plugins - 1.14.0-SNAPSHOT + 1.14.0 azure-webapp-maven-plugin - 2.2.0-SNAPSHOT + 2.2.0 maven-plugin Maven Plugin for Azure Web Apps Maven Plugin for Azure Web Apps @@ -215,6 +215,56 @@ + + + + com.nickwongdev + aspectj-maven-plugin + + false + 1.8 + 1.8 + ignore + 1.8 + UTF-8 + false + true + true + + + + com.microsoft.azure + azure-toolkit-common-lib + + + + + + compile-with-aspectj + process-classes + + + ${project.build.directory}/classes + + + + compile + + + + test-compile-with-aspectj + process-test-classes + + + ${project.build.directory}/test-classes + + + + test-compile + + + + org.apache.maven.plugins maven-plugin-plugin diff --git a/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/AbstractWebAppMojo.java b/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/AbstractWebAppMojo.java index be08a72270..3a1c6328c1 100644 --- a/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/AbstractWebAppMojo.java +++ b/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/AbstractWebAppMojo.java @@ -44,11 +44,10 @@ public abstract class AbstractWebAppMojo extends AbstractAppServiceMojo { public static final String DOCKER_IMAGE_TYPE_KEY = "dockerImageType"; public static final String DEPLOYMENT_TYPE_KEY = "deploymentType"; public static final String OS_KEY = "os"; - public static final String INVALID_CONFIG_KEY = "invalidConfiguration"; public static final String SCHEMA_VERSION_KEY = "schemaVersion"; public static final String DEPLOY_TO_SLOT_KEY = "isDeployToSlot"; - private static final String INVALID_PARAMETER_ERROR_MESSAGE = "Invalid values found in configuration, please correct the value with messages below:"; - + public static final String SKIP_CREATE_RESOURCE_KEY = "skipCreateResource"; + public static final String INVALID_PARAMETER_ERROR_MESSAGE = "Invalid values found in configuration, please correct the value with messages below:"; //region Properties /** @@ -89,6 +88,20 @@ public abstract class AbstractWebAppMojo extends AbstractAppServiceMojo { @Parameter(property = "webapp.skip", defaultValue = "false") protected boolean skip; + /** + * TODO(andxu): move this flag to AbstractAzureMojo + */ + @JsonIgnore + @Parameter(property = "azure.resource.create.skip", defaultValue = "false") + protected boolean skipAzureResourceCreate; + + /** + * TODO(andxu): move this flag to AbstractAzureMojo + */ + @JsonIgnore + @Parameter(property = "skipCreateAzureResource") + protected boolean skipCreateAzureResource; + /** * App Service region, which will only be used to create App Service at the first time. */ @@ -136,7 +149,8 @@ public abstract class AbstractWebAppMojo extends AbstractAppServiceMojo { private boolean isRuntimeInjected = false; @JsonIgnore - private WebAppConfig config; + @Getter + protected ConfigParser configParser = new ConfigParser(this); //endregion @@ -226,6 +240,8 @@ public Map getTelemetryProperties() { final boolean isDeployToSlot = Optional.ofNullable(getDeploymentSlotSetting()).map(DeploymentSlotSetting::getName) .map(StringUtils::isNotEmpty).orElse(false); map.put(DEPLOY_TO_SLOT_KEY, String.valueOf(isDeployToSlot)); + + map.put(SKIP_CREATE_RESOURCE_KEY, String.valueOf(skipAzureResourceCreate || skipCreateAzureResource)); return map; } @@ -238,13 +254,6 @@ protected void validateConfiguration(Consumer validationMessa } } - protected synchronized WebAppConfig getWebAppConfig() throws AzureExecutionException { - if (config == null) { - config = new ConfigParser(this).parse(); - } - return config; - } - @Override public String getSubscriptionId() { return appServiceClient == null ? this.subscriptionId : appServiceClient.getDefaultSubscription().getId(); diff --git a/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/ConfigMojo.java b/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/ConfigMojo.java index 5e914db580..d3a962ad38 100644 --- a/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/ConfigMojo.java +++ b/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/ConfigMojo.java @@ -14,15 +14,14 @@ import com.microsoft.azure.maven.webapp.configuration.SchemaVersion; import com.microsoft.azure.maven.webapp.handlers.WebAppPomHandler; import com.microsoft.azure.maven.webapp.models.WebAppOption; -import com.microsoft.azure.maven.webapp.parser.ConfigParser; import com.microsoft.azure.toolkit.lib.Azure; import com.microsoft.azure.toolkit.lib.appservice.AzureAppService; +import com.microsoft.azure.toolkit.lib.appservice.config.AppServiceConfig; import com.microsoft.azure.toolkit.lib.appservice.model.JavaVersion; import com.microsoft.azure.toolkit.lib.appservice.model.OperatingSystem; import com.microsoft.azure.toolkit.lib.appservice.model.PricingTier; import com.microsoft.azure.toolkit.lib.appservice.model.Runtime; import com.microsoft.azure.toolkit.lib.appservice.model.WebContainer; -import com.microsoft.azure.toolkit.lib.appservice.service.IAppServicePlan; import com.microsoft.azure.toolkit.lib.appservice.service.IWebApp; import com.microsoft.azure.toolkit.lib.auth.exception.AzureToolkitAuthenticationException; import com.microsoft.azure.toolkit.lib.common.exception.AzureExecutionException; @@ -52,11 +51,11 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import static com.microsoft.azure.maven.webapp.utils.Utils.findStringInCollectionIgnoreCase; +import static com.microsoft.azure.toolkit.lib.appservice.utils.AppServiceConfigUtils.fromAppService; /** * Init or edit the configuration of azure webapp maven plugin. @@ -84,8 +83,6 @@ public class ConfigMojo extends AbstractWebAppMojo { private static final String NO_JAVA_WEB_APPS = "There are no Java Web Apps in current subscription, please follow the following steps to create a new one."; private static final String LONG_LOADING_HINT = "It may take a few minutes to load all Java Web Apps, please be patient."; private static final String[] configTypes = {"Application", "Runtime", "DeploymentSlot"}; - private static final String SETTING_DOCKER_IMAGE = "DOCKER_CUSTOM_IMAGE_NAME"; - private static final String SETTING_REGISTRY_SERVER = "DOCKER_REGISTRY_SERVER_URL"; private static final String SETTING_REGISTRY_USERNAME = "DOCKER_REGISTRY_SERVER_USERNAME"; private static final String SERVER_ID_TEMPLATE = "Please add a server in Maven settings.xml related to username: %s and put the serverId here"; @@ -466,7 +463,7 @@ private boolean isJarProject() { private WebAppConfiguration getWebAppConfiguration() { validateConfiguration(message -> AzureMessager.getMessager().warning(message.getMessage()), false); - return new ConfigParser(this).getWebAppConfiguration(); + return getConfigParser().getWebAppConfiguration(); } private WebAppConfiguration chooseExistingWebappForConfiguration() @@ -501,17 +498,12 @@ private WebAppConfiguration chooseExistingWebappForConfiguration() } final IWebApp webapp = az.webapp(selectedApp.getId()); - final String serverPlanId = selectedApp.getServicePlanId(); - IAppServicePlan servicePlan = null; - if (StringUtils.isNotBlank(serverPlanId)) { - servicePlan = az.appServicePlan(serverPlanId); - } final WebAppConfiguration.WebAppConfigurationBuilder builder = WebAppConfiguration.builder(); if (!AppServiceUtils.isDockerAppService(webapp)) { builder.resources(Deployment.getDefaultDeploymentConfiguration(getProject().getPackaging()).getResources()); } - return getConfigurationFromExisting(webapp, servicePlan, builder); + return getConfigurationFromExisting(webapp, builder); } catch (AzureToolkitAuthenticationException ex) { // if is valid for config goal to have error in authentication getLog().warn(String.format("Cannot authenticate due to error: %s, select existing webapp is skipped.", ex.getMessage())); @@ -534,43 +526,30 @@ private static WebAppOption selectAzureWebApp(TextIO textIO, List .read(String.format("%s Web Apps in subscription %s:", webAppType, TextUtils.blue(targetSubscription.getName()))); } - private static WebAppConfiguration getConfigurationFromExisting(IWebApp webapp, IAppServicePlan servicePlan, + private WebAppConfiguration getConfigurationFromExisting(IWebApp webapp, WebAppConfiguration.WebAppConfigurationBuilder builder) { + final AppServiceConfig appServiceConfig = fromAppService(webapp, webapp.plan()); // common configuration - builder.appName(webapp.name()) - .resourceGroup(webapp.entity().getResourceGroup()) - .subscriptionId(Utils.getSubscriptionId(webapp.id())) - .region(webapp.entity().getRegion()); - + builder.appName(appServiceConfig.appName()) + .resourceGroup(appServiceConfig.resourceGroup()) + .subscriptionId(appServiceConfig.subscriptionId()) + .region(appServiceConfig.region()); + builder.os(appServiceConfig.runtime().os()); if (AppServiceUtils.isDockerAppService(webapp)) { - builder.os(OperatingSystem.DOCKER); final Map settings = webapp.entity().getAppSettings(); - - final String imageSetting = settings.get(SETTING_DOCKER_IMAGE); - if (StringUtils.isNotBlank(imageSetting)) { - builder.image(imageSetting); - } else { - builder.image(webapp.entity().getDockerImageName()); - } - final String registryServerSetting = settings.get(SETTING_REGISTRY_SERVER); - if (StringUtils.isNotBlank(registryServerSetting)) { - builder.registryUrl(registryServerSetting); - } - + builder.image(appServiceConfig.runtime().image()); + builder.registryUrl(appServiceConfig.runtime().registryUrl()); final String dockerUsernameSetting = settings.get(SETTING_REGISTRY_USERNAME); if (StringUtils.isNotBlank(dockerUsernameSetting)) { builder.serverId(String.format(SERVER_ID_TEMPLATE, dockerUsernameSetting)); } } else { - builder.os(webapp.getRuntime().getOperatingSystem()); - builder.webContainer(Objects.toString(webapp.getRuntime().getWebContainer())); - builder.javaVersion(Objects.toString(webapp.getRuntime().getJavaVersion())); - } - if (servicePlan != null && servicePlan.entity() != null) { - builder.pricingTier(Optional.ofNullable(servicePlan.entity().getPricingTier()).map(Object::toString).orElse(null)); - builder.servicePlanName(servicePlan.name()); - builder.servicePlanResourceGroup(servicePlan.entity().getResourceGroup()); + builder.webContainer(Objects.toString(appServiceConfig.runtime().webContainer())); + builder.javaVersion(Objects.toString(appServiceConfig.runtime().javaVersion())); } + builder.servicePlanName(appServiceConfig.servicePlanName()); + builder.servicePlanResourceGroup(appServiceConfig.servicePlanResourceGroup()); + builder.pricingTier(Objects.toString(appServiceConfig.pricingTier())); return builder.build(); } diff --git a/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/DeployMojo.java b/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/DeployMojo.java index ee4e3c6422..662a86a448 100644 --- a/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/DeployMojo.java +++ b/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/DeployMojo.java @@ -5,294 +5,138 @@ package com.microsoft.azure.maven.webapp; -import com.azure.core.management.exception.ManagementException; -import com.azure.resourcemanager.AzureResourceManager; -import com.azure.resourcemanager.resources.models.ResourceGroup; import com.microsoft.azure.maven.model.DeploymentResource; -import com.microsoft.azure.maven.webapp.utils.DeployUtils; -import com.microsoft.azure.maven.webapp.utils.Utils; -import com.microsoft.azure.maven.webapp.utils.WebAppUtils; -import com.microsoft.azure.toolkit.lib.appservice.model.DeployType; +import com.microsoft.azure.maven.webapp.configuration.DeploymentSlotConfig; +import com.microsoft.azure.maven.webapp.task.DeployExternalResourcesTask; +import com.microsoft.azure.toolkit.lib.Azure; +import com.microsoft.azure.toolkit.lib.appservice.AzureAppService; +import com.microsoft.azure.toolkit.lib.appservice.config.AppServiceConfig; +import com.microsoft.azure.toolkit.lib.appservice.model.JavaVersion; +import com.microsoft.azure.toolkit.lib.appservice.model.PricingTier; import com.microsoft.azure.toolkit.lib.appservice.model.WebAppArtifact; import com.microsoft.azure.toolkit.lib.appservice.model.WebContainer; -import com.microsoft.azure.toolkit.lib.appservice.service.IAppService; -import com.microsoft.azure.toolkit.lib.appservice.service.IAppServicePlan; import com.microsoft.azure.toolkit.lib.appservice.service.IWebApp; import com.microsoft.azure.toolkit.lib.appservice.service.IWebAppBase; import com.microsoft.azure.toolkit.lib.appservice.service.IWebAppDeploymentSlot; +import com.microsoft.azure.toolkit.lib.appservice.task.CreateOrUpdateWebAppTask; +import com.microsoft.azure.toolkit.lib.appservice.task.DeployWebAppTask; +import com.microsoft.azure.toolkit.lib.appservice.utils.AppServiceConfigUtils; +import com.microsoft.azure.toolkit.lib.common.bundle.AzureString; import com.microsoft.azure.toolkit.lib.common.exception.AzureExecutionException; import com.microsoft.azure.toolkit.lib.common.messager.AzureMessager; -import com.microsoft.azure.toolkit.lib.common.model.Region; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.io.FileUtils; +import com.microsoft.azure.toolkit.lib.common.operation.AzureOperation; +import com.microsoft.azure.toolkit.lib.common.utils.Utils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.maven.artifact.versioning.ComparableVersion; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; -import org.zeroturnaround.zip.ZipUtil; import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Objects; -import java.util.UUID; -import java.util.function.Predicate; -import java.util.stream.Collectors; + +import static com.microsoft.azure.toolkit.lib.appservice.utils.AppServiceConfigUtils.fromAppService; +import static com.microsoft.azure.toolkit.lib.appservice.utils.AppServiceConfigUtils.mergeAppServiceConfig; +import static com.microsoft.azure.toolkit.lib.appservice.utils.Utils.throwForbidCreateResourceWarning; /** * Deploy an Azure Web App, either Windows-based or Linux-based. */ @Mojo(name = "deploy", defaultPhase = LifecyclePhase.DEPLOY) public class DeployMojo extends AbstractWebAppMojo { - private static final String CREATE_WEBAPP = "Creating web app %s..."; - private static final String CREATE_WEB_APP_DONE = "Successfully created Web App %s."; - private static final String UPDATE_WEBAPP = "Updating target Web App %s..."; - private static final String UPDATE_WEBAPP_DONE = "Successfully updated Web App %s."; - private static final String CREATE_RESOURCE_GROUP = "Creating resource group %s in region %s..."; - private static final String CREATE_RESOURCE_GROUP_DONE = "Successfully created resource group %s."; - private static final String CREATE_APP_SERVICE_PLAN = "Creating app service plan..."; - private static final String CREATE_APP_SERVICE_DONE = "Successfully created app service plan %s."; private static final String WEBAPP_NOT_EXIST_FOR_SLOT = "The Web App specified in pom.xml does not exist. " + "Please make sure the Web App name is correct."; private static final String CREATE_DEPLOYMENT_SLOT = "Creating deployment slot %s in web app %s"; private static final String CREATE_DEPLOYMENT_SLOT_DONE = "Successfully created the Deployment Slot."; - private static final String DEPLOY_START = "Trying to deploy artifact to %s..."; - private static final String DEPLOY_FINISH = "Successfully deployed the artifact to https://%s"; - private static final String SKIP_DEPLOYMENT_FOR_DOCKER_APP_SERVICE = "Skip deployment for docker app service"; - private static final String NO_RUNTIME_CONFIG = "You need to specified in pom.xml for creating azure webapps."; - private static final String CREATE_NEW_APP_SERVICE_PLAN = "createNewAppServicePlan"; - private static final String CREATE_NEW_RESOURCE_GROUP = "createNewResourceGroup"; - private static final String CREATE_NEW_WEB_APP = "createNewWebApp"; private static final String CREATE_NEW_DEPLOYMENT_SLOT = "createNewDeploymentSlot"; @Override + @AzureOperation(name = "webapp|mojo.deploy", type = AzureOperation.Type.ACTION) protected void doExecute() throws AzureExecutionException { validateConfiguration(message -> AzureMessager.getMessager().error(message.getMessage()), true); // initialize library client az = getOrCreateAzureAppServiceClient(); - - final WebAppConfig config = getWebAppConfig(); - final IWebAppBase target = createOrUpdateResource(config); - deploy(target, config); - } - - private IWebAppBase createOrUpdateResource(final WebAppConfig config) throws AzureExecutionException { - if (StringUtils.isEmpty(config.getDeploymentSlotName())) { - final IWebApp webApp = getWebApp(config); - return webApp.exists() ? updateWebApp(webApp, config) : createWebApp(webApp, config); + final IWebAppBase target = createOrUpdateResource(); + deployExternalResources(target, getConfigParser().getExternalArtifacts()); + deploy(target, getConfigParser().getArtifacts()); + updateTelemetryProperties(); + } + + private IWebAppBase createOrUpdateResource() throws AzureExecutionException { + final boolean skipCreate = skipAzureResourceCreate || skipCreateAzureResource; + if (!isDeployToDeploymentSlot()) { + final AppServiceConfig appServiceConfig = getConfigParser().getAppServiceConfig(); + IWebApp app = Azure.az(AzureAppService.class).webapp(appServiceConfig.resourceGroup(), appServiceConfig.appName()); + final boolean newWebApp = !app.exists(); + AppServiceConfig defaultConfig = !newWebApp ? fromAppService(app, app.plan()) : buildDefaultConfig(appServiceConfig.subscriptionId(), + appServiceConfig.resourceGroup(), appServiceConfig.appName()); + mergeAppServiceConfig(appServiceConfig, defaultConfig); + if (appServiceConfig.pricingTier() == null) { + appServiceConfig.pricingTier(appServiceConfig.runtime().webContainer() == WebContainer.JBOSS_7 ? PricingTier.PREMIUM_P1V3 : PricingTier.PREMIUM_P1V2); + } + final CreateOrUpdateWebAppTask task = new CreateOrUpdateWebAppTask(appServiceConfig); + task.setSkipCreateAzureResource(skipCreate); + return task.execute(); } else { + // todo: New CreateOrUpdateDeploymentSlotTask + final DeploymentSlotConfig config = getConfigParser().getDeploymentSlotConfig(); final IWebAppDeploymentSlot slot = getDeploymentSlot(config); - return slot.exists() ? updateDeploymentSlot(slot, config) : createDeploymentSlot(slot, config); - } - } - - private IWebApp getWebApp(final WebAppConfig config) { - return az.webapp(config.getResourceGroup(), config.getAppName()); - } - - private IWebAppDeploymentSlot getDeploymentSlot(final WebAppConfig config) throws AzureExecutionException { - final IWebApp webApp = getWebApp(config); - if (!webApp.exists()) { - throw new AzureExecutionException(WEBAPP_NOT_EXIST_FOR_SLOT); - } - return webApp.deploymentSlot(config.getDeploymentSlotName()); - } - - private IWebApp createWebApp(final IWebApp webApp, final WebAppConfig webAppConfig) throws AzureExecutionException { - if (webAppConfig.getRuntime() == null) { - throw new AzureExecutionException(NO_RUNTIME_CONFIG); + final boolean slotExists = slot.exists(); + if (!slotExists && skipCreate) { + throwForbidCreateResourceWarning("Deployment slot", config.getName()); + } + return slotExists ? updateDeploymentSlot(slot, config) : createDeploymentSlot(slot, config); } - getTelemetryProxy().addDefaultProperty(CREATE_NEW_WEB_APP, String.valueOf(true)); - final ResourceGroup resourceGroup = getOrCreateResourceGroup(webAppConfig); - final IAppServicePlan appServicePlan = getOrCreateAppServicePlan(webAppConfig); - AzureMessager.getMessager().info(String.format(CREATE_WEBAPP, webAppConfig.getAppName())); - final IWebApp result = webApp.create().withName(webAppConfig.getAppName()) - .withResourceGroup(resourceGroup.name()) - .withPlan(appServicePlan.id()) - .withRuntime(webAppConfig.getRuntime()) - .withDockerConfiguration(webAppConfig.getDockerConfiguration()) - .withAppSettings(webAppConfig.getAppSettings()) - .commit(); - AzureMessager.getMessager().info(String.format(CREATE_WEB_APP_DONE, result.name())); - return result; } - private IWebApp updateWebApp(final IWebApp webApp, final WebAppConfig webAppConfig) { - // update app service plan - AzureMessager.getMessager().info(String.format(UPDATE_WEBAPP, webApp.name())); - final IAppServicePlan currentPlan = webApp.plan(); - IAppServicePlan targetServicePlan = StringUtils.isEmpty(webAppConfig.getServicePlanName()) ? currentPlan : - az.appServicePlan(getServicePlanResourceGroup(webAppConfig), webAppConfig.getServicePlanName()); - if (!targetServicePlan.exists()) { - targetServicePlan = getOrCreateAppServicePlan(webAppConfig); - } else { - if (region != null && !Objects.equals(Region.fromName(region), Region.fromName(targetServicePlan.entity().getRegion()))) { - AzureMessager.getMessager().warning(String.format("Skip region update for existing service plan '%s' since it is not allowed.", - targetServicePlan.name())); - } - - if (webAppConfig.getPricingTier() != null) { - targetServicePlan.update().withPricingTier(webAppConfig.getPricingTier()).commit(); + private AppServiceConfig buildDefaultConfig(String subscriptionId, String resourceGroup, String appName) { + ComparableVersion javaVersionForProject = null; + final String outputFileName = project.getBuild().getFinalName() + "." + project.getPackaging(); + File outputFile = new File(project.getBuild().getDirectory(), outputFileName); + if (outputFile.exists() && StringUtils.equalsIgnoreCase("jar", project.getPackaging())) { + try { + javaVersionForProject = new ComparableVersion(Utils.getArtifactCompileVersion(outputFile)); + } catch (Exception e) { + // it is acceptable that java version from jar file cannot be retrieved } } - final IWebApp result = webApp.update().withPlan(targetServicePlan.id()) - .withRuntime(webAppConfig.getRuntime()) - .withDockerConfiguration(webAppConfig.getDockerConfiguration()) - .withAppSettings(webAppConfig.getAppSettings()) - .commit(); - AzureMessager.getMessager().info(String.format(UPDATE_WEBAPP_DONE, webApp.name())); - return result; - } - - private ResourceGroup getOrCreateResourceGroup(final WebAppConfig webAppConfig) { - // todo: Extract resource group logic to library - final AzureResourceManager azureResourceManager = az.getAzureResourceManager(webAppConfig.getSubscriptionId()); - try { - return azureResourceManager.resourceGroups().getByName(webAppConfig.getResourceGroup()); - } catch (ManagementException e) { - AzureMessager.getMessager().info(String.format(CREATE_RESOURCE_GROUP, webAppConfig.getResourceGroup(), webAppConfig.getRegion().getName())); - getTelemetryProxy().addDefaultProperty(CREATE_NEW_RESOURCE_GROUP, String.valueOf(true)); - final ResourceGroup result = azureResourceManager.resourceGroups().define(webAppConfig.getResourceGroup()) - .withRegion(webAppConfig.getRegion().getName()).create(); - AzureMessager.getMessager().info(String.format(CREATE_RESOURCE_GROUP_DONE, webAppConfig.getResourceGroup())); - return result; - } + javaVersionForProject = ObjectUtils.firstNonNull(javaVersionForProject, new ComparableVersion(System.getProperty("java.version"))); + // get java version according to project java version + JavaVersion javaVersion = javaVersionForProject.compareTo(new ComparableVersion("9")) < 0 ? JavaVersion.JAVA_8 : JavaVersion.JAVA_11; + return AppServiceConfigUtils.buildDefaultWebAppConfig(subscriptionId, resourceGroup, appName, this.project.getPackaging(), javaVersion); } - private IAppServicePlan getOrCreateAppServicePlan(final WebAppConfig webAppConfig) { - final String servicePlanName = StringUtils.isEmpty(webAppConfig.getServicePlanName()) ? - getNewAppServicePlanName(webAppConfig) : webAppConfig.getServicePlanName(); - final String servicePlanGroup = getServicePlanResourceGroup(webAppConfig); - final IAppServicePlan appServicePlan = az.appServicePlan(servicePlanGroup, servicePlanName); - if (!appServicePlan.exists()) { - AzureMessager.getMessager().info(CREATE_APP_SERVICE_PLAN); - getTelemetryProxy().addDefaultProperty(CREATE_NEW_APP_SERVICE_PLAN, String.valueOf(true)); - appServicePlan.create() - .withName(servicePlanName) - .withResourceGroup(servicePlanGroup) - .withRegion(webAppConfig.getRegion()) - .withPricingTier(webAppConfig.getPricingTier()) - .withOperatingSystem(webAppConfig.getRuntime().getOperatingSystem()) - .commit(); - AzureMessager.getMessager().info(String.format(CREATE_APP_SERVICE_DONE, appServicePlan.name())); + private IWebAppDeploymentSlot getDeploymentSlot(final DeploymentSlotConfig config) throws AzureExecutionException { + final IWebApp webApp = az.webapp(config.getResourceGroup(), config.getAppName()); + if (!webApp.exists()) { + throw new AzureExecutionException(WEBAPP_NOT_EXIST_FOR_SLOT); } - return appServicePlan; - } - - private String getNewAppServicePlanName(final WebAppConfig webAppConfig) { - return StringUtils.isEmpty(webAppConfig.getServicePlanName()) ? String.format("asp-%s", webAppConfig.getAppName()) : - webAppConfig.getServicePlanName(); + return webApp.deploymentSlot(config.getName()); } - private String getServicePlanResourceGroup(final WebAppConfig webAppConfig) { - return StringUtils.isEmpty(webAppConfig.getServicePlanResourceGroup()) ? webAppConfig.getResourceGroup() : - webAppConfig.getServicePlanResourceGroup(); - } - - private IWebAppDeploymentSlot createDeploymentSlot(final IWebAppDeploymentSlot slot, final WebAppConfig webAppConfig) { - AzureMessager.getMessager().info(String.format(CREATE_DEPLOYMENT_SLOT, webAppConfig.getDeploymentSlotName(), webAppConfig.getAppName())); + private IWebAppDeploymentSlot createDeploymentSlot(final IWebAppDeploymentSlot slot, final DeploymentSlotConfig slotConfig) { + AzureMessager.getMessager().info(AzureString.format(CREATE_DEPLOYMENT_SLOT, slotConfig.getName(), slotConfig.getAppName())); getTelemetryProxy().addDefaultProperty(CREATE_NEW_DEPLOYMENT_SLOT, String.valueOf(true)); - final IWebAppDeploymentSlot result = slot.create().withName(webAppConfig.getDeploymentSlotName()) - .withConfigurationSource(webAppConfig.getDeploymentSlotConfigurationSource()) - .withAppSettings(webAppConfig.getAppSettings()) + final IWebAppDeploymentSlot result = slot.create().withName(slotConfig.getName()) + .withConfigurationSource(slotConfig.getConfigurationSource()) + .withAppSettings(slotConfig.getAppSettings()) .commit(); AzureMessager.getMessager().info(CREATE_DEPLOYMENT_SLOT_DONE); return result; } - private void deploy(IWebAppBase target, WebAppConfig config) throws AzureExecutionException { - if (target.getRuntime().isDocker()) { - AzureMessager.getMessager().info(SKIP_DEPLOYMENT_FOR_DOCKER_APP_SERVICE); - return; - } - try { - AzureMessager.getMessager().info(String.format(DEPLOY_START, config.getAppName())); - if (isStopAppDuringDeployment()) { - WebAppUtils.stopAppService(target); - } - deployArtifacts(target, config); - deployExternalResources(target); - AzureMessager.getMessager().info(String.format(DEPLOY_FINISH, target.hostName())); - } finally { - WebAppUtils.startAppService(target); - } - } - // update existing slot is not supported in current version, will implement it later - private IWebAppDeploymentSlot updateDeploymentSlot(final IWebAppDeploymentSlot slot, final WebAppConfig webAppConfig) { + private IWebAppDeploymentSlot updateDeploymentSlot(final IWebAppDeploymentSlot slot, final DeploymentSlotConfig slotConfig) { + AzureMessager.getMessager().warning("update existing slot is not supported in current version"); return slot; } - private void deployArtifacts(IWebAppBase target, WebAppConfig config) throws AzureExecutionException { - final List artifactsOneDeploy = config.getWebAppArtifacts().stream() - .filter(artifact -> artifact.getDeployType() != null) - .collect(Collectors.toList()); - artifactsOneDeploy.forEach(resource -> target.deploy(resource.getDeployType(), resource.getFile(), resource.getPath())); - - // This is the codes for one deploy API, for current release, will replace it with zip all files and deploy with zip deploy - final List artifacts = config.getWebAppArtifacts().stream() - .filter(artifact -> artifact.getDeployType() == null) - .collect(Collectors.toList()); - - if (CollectionUtils.isEmpty(artifacts)) { - return; - } - // call correspond deploy method when deploy artifact only - if (artifacts.size() == 1) { - final WebAppArtifact artifact = artifacts.get(0); - final DeployType deployType = DeployType.getDeployTypeFromFile(artifact.getFile()); - target.deploy(deployType, artifact.getFile(), artifact.getPath()); - return; - } - // Support deploy multi war to different paths - if (DeployUtils.isAllWarArtifacts(artifacts)) { - artifacts.forEach(resource -> target.deploy(DeployType.getDeployTypeFromFile(resource.getFile()), resource.getFile(), resource.getPath())); - return; - } - // package all resource and do zip deploy - // todo: migrate to use one deploy - deployArtifactsWithZipDeploy(target, artifacts); - } - - private void deployArtifactsWithZipDeploy(IWebAppBase target, List artifacts) throws AzureExecutionException { - final File stagingDirectory = prepareStagingDirectory(artifacts); - // Rename jar once java_se runtime - if (Objects.equals(target.getRuntime().getWebContainer(), WebContainer.JAVA_SE)) { - final List files = new ArrayList<>(FileUtils.listFiles(stagingDirectory, null, true)); - DeployUtils.prepareJavaSERuntimeJarArtifact(files, project.getBuild().getFinalName()); - } - final File zipFile = Utils.createTempFile(appName + UUID.randomUUID(), ".zip"); - ZipUtil.pack(stagingDirectory, zipFile); - // Deploy zip with zip deploy - target.deploy(DeployType.ZIP, zipFile); - } - - private static File prepareStagingDirectory(List webAppArtifacts) throws AzureExecutionException { - try { - final File stagingDirectory = Files.createTempDirectory("azure-functions").toFile(); - FileUtils.forceDeleteOnExit(stagingDirectory); - // Copy maven artifacts to staging folder - for (final WebAppArtifact webAppArtifact : webAppArtifacts) { - final File targetFolder = StringUtils.isEmpty(webAppArtifact.getPath()) ? stagingDirectory : - new File(stagingDirectory, webAppArtifact.getPath()); - FileUtils.copyFileToDirectory(webAppArtifact.getFile(), targetFolder); - } - return stagingDirectory; - } catch (IOException e) { - throw new AzureExecutionException("Failed to package resources", e); - } - } - - private void deployExternalResources(IAppService target) throws AzureExecutionException { - DeployUtils.deployResourcesWithFtp(target, filterResources(DeploymentResource::isExternalResource)); + private void deploy(IWebAppBase target, List artifacts) { + new DeployWebAppTask(target, artifacts, isStopAppDuringDeployment()).execute(); } - private List filterResources(Predicate predicate) { - final List resources = this.deployment == null ? Collections.emptyList() : this.deployment.getResources(); - return resources.stream() - .filter(predicate).collect(Collectors.toList()); + private void deployExternalResources(final IWebAppBase target, final List resources) { + new DeployExternalResourcesTask(target, resources).execute(); } } diff --git a/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/WebAppConfig.java b/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/WebAppConfig.java deleted file mode 100644 index e2a2ca6a41..0000000000 --- a/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/WebAppConfig.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - */ -package com.microsoft.azure.maven.webapp; - -import com.microsoft.azure.toolkit.lib.appservice.model.WebAppArtifact; -import com.microsoft.azure.toolkit.lib.appservice.model.DockerConfiguration; -import com.microsoft.azure.toolkit.lib.appservice.model.PricingTier; -import com.microsoft.azure.toolkit.lib.appservice.model.Runtime; -import com.microsoft.azure.toolkit.lib.common.model.Region; -import lombok.Getter; -import lombok.experimental.SuperBuilder; - -import java.util.List; -import java.util.Map; - -@Getter -@SuperBuilder(toBuilder = true) -public class WebAppConfig { - private String subscriptionId; - private String resourceGroup; - private String appName; - private String servicePlanName; - private String servicePlanResourceGroup; - private Region region; - private PricingTier pricingTier; - private Runtime runtime; - private DockerConfiguration dockerConfiguration; - private String deploymentSlotName; - private String deploymentSlotConfigurationSource; - private Map appSettings; - // resources - private List webAppArtifacts; -} diff --git a/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/WebAppConfiguration.java b/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/WebAppConfiguration.java index 26270d2833..7c30333126 100644 --- a/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/WebAppConfiguration.java +++ b/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/WebAppConfiguration.java @@ -29,7 +29,7 @@ @SuperBuilder(toBuilder = true) public class WebAppConfiguration { public static final PricingTier DEFAULT_JBOSS_PRICING_TIER = PricingTier.PREMIUM_P1V3; - public static final Region DEFAULT_REGION = Region.EUROPE_WEST; + public static final Region DEFAULT_REGION = Region.US_CENTRAL; public static final PricingTier DEFAULT_PRICINGTIER = PricingTier.PREMIUM_P1V2; public static final JavaVersion DEFAULT_JAVA_VERSION = JavaVersion.JAVA_8; public static final WebContainer DEFAULT_CONTAINER = WebContainer.TOMCAT_85; diff --git a/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/configuration/DeploymentSlotConfig.java b/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/configuration/DeploymentSlotConfig.java new file mode 100644 index 0000000000..fe5d19dc95 --- /dev/null +++ b/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/configuration/DeploymentSlotConfig.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ +package com.microsoft.azure.maven.webapp.configuration; + +import lombok.Builder; +import lombok.Getter; + +import java.util.Map; + +@Getter +@Builder +public class DeploymentSlotConfig { + private final String subscriptionId; + private final String resourceGroup; + private final String appName; + private final String name; + private final String configurationSource; + private final Map appSettings; +} diff --git a/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/parser/ConfigParser.java b/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/parser/ConfigParser.java index 07d8777963..01b47131bd 100644 --- a/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/parser/ConfigParser.java +++ b/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/parser/ConfigParser.java @@ -9,12 +9,13 @@ import com.microsoft.azure.maven.model.DeploymentResource; import com.microsoft.azure.maven.utils.MavenArtifactUtils; import com.microsoft.azure.maven.webapp.AbstractWebAppMojo; -import com.microsoft.azure.maven.webapp.WebAppConfig; import com.microsoft.azure.maven.webapp.WebAppConfiguration; import com.microsoft.azure.maven.webapp.configuration.Deployment; +import com.microsoft.azure.maven.webapp.configuration.DeploymentSlotConfig; import com.microsoft.azure.maven.webapp.configuration.MavenRuntimeConfig; +import com.microsoft.azure.toolkit.lib.appservice.config.AppServiceConfig; +import com.microsoft.azure.toolkit.lib.appservice.config.RuntimeConfig; import com.microsoft.azure.toolkit.lib.appservice.model.DeployType; -import com.microsoft.azure.toolkit.lib.appservice.model.DockerConfiguration; import com.microsoft.azure.toolkit.lib.appservice.model.JavaVersion; import com.microsoft.azure.toolkit.lib.appservice.model.OperatingSystem; import com.microsoft.azure.toolkit.lib.appservice.model.PricingTier; @@ -59,6 +60,87 @@ public ConfigParser(AbstractWebAppMojo mojo) { this.mojo = mojo; } + public AppServiceConfig getAppServiceConfig() throws AzureExecutionException { + return new AppServiceConfig() + .subscriptionId(getSubscriptionId()) + .resourceGroup(getResourceGroup()) + .appName(getAppName()) + .servicePlanName(getAppServicePlanName()) + .servicePlanResourceGroup(getAppServicePlanResourceGroup()) + .deploymentSlotName(getDeploymentSlotName()) + .deploymentSlotConfigurationSource(getDeploymentSlotConfigurationSource()) + .pricingTier(getPricingTier()) + .region(getRegion()) + .runtime(getRuntimeConfig()) + .appSettings(mojo.getAppSettings()); + } + + // todo: replace WebAppConfiguration with WebAppConfig + public WebAppConfiguration getWebAppConfiguration() { + WebAppConfiguration.WebAppConfigurationBuilder builder = WebAppConfiguration.builder(); + final Runtime runtime = getRuntime(); + final OperatingSystem os = Optional.ofNullable(runtime).map(Runtime::getOperatingSystem).orElse(null); + if (os == null) { + Log.debug("No runtime related config is specified. It will cause error if creating a new web app."); + } else { + switch (os) { + case WINDOWS: + case LINUX: + builder = builder.javaVersion(Objects.toString(runtime.getJavaVersion())).webContainer(Objects.toString(runtime.getWebContainer())); + break; + case DOCKER: + final MavenRuntimeConfig runtimeConfig = mojo.getRuntime(); + builder = builder.image(runtimeConfig.getImage()).serverId(runtimeConfig.getServerId()).registryUrl(runtimeConfig.getRegistryUrl()); + break; + default: + Log.debug("Invalid operating system from the configuration."); + } + } + return builder.appName(getAppName()) + .resourceGroup(getResourceGroup()) + .region(getRegion()) + .pricingTier(Optional.ofNullable(getPricingTier()).map(PricingTier::getSize).orElse(null)) + .servicePlanName(mojo.getAppServicePlanName()) + .servicePlanResourceGroup(mojo.getAppServicePlanResourceGroup()) + .deploymentSlotSetting(mojo.getDeploymentSlotSetting()) + .os(os) + .mavenSettings(mojo.getSettings()) + .resources(Optional.ofNullable(mojo.getDeployment()).map(Deployment::getResources).orElse(null)) + .stagingDirectoryPath(mojo.getDeploymentStagingDirectoryPath()) + .buildDirectoryAbsolutePath(mojo.getBuildDirectoryAbsolutePath()) + .project(mojo.getProject()) + .session(mojo.getSession()) + .filtering(mojo.getMavenResourcesFiltering()) + .schemaVersion("v2") + .build(); + } + + public DeploymentSlotConfig getDeploymentSlotConfig() { + return DeploymentSlotConfig.builder() + .subscriptionId(getSubscriptionId()) + .resourceGroup(getResourceGroup()) + .appName(getAppName()) + .name(getDeploymentSlotName()) + .configurationSource(getDeploymentSlotConfigurationSource()) + .appSettings(mojo.getAppSettings()) + .build(); + } + + public List getArtifacts() throws AzureExecutionException { + if (mojo.getDeployment() == null || mojo.getDeployment().getResources() == null) { + return Collections.emptyList(); + } + return convertResourceToArtifacts(mojo.getDeployment().getResources()); + } + + public List getExternalArtifacts() { + if (mojo.getDeployment() == null || mojo.getDeployment().getResources() == null) { + return Collections.emptyList(); + } + return mojo.getDeployment().getResources().stream() + .filter(DeploymentResource::isExternalResource).collect(Collectors.toList()); + } + public String getAppName() { return mojo.getAppName(); } @@ -76,6 +158,9 @@ public String getDeploymentSlotConfigurationSource() { } public PricingTier getPricingTier() { + if (StringUtils.isEmpty(mojo.getPricingTier())) { + return null; + } return parseExpandableParameter(input -> { if (StringUtils.contains(mojo.getPricingTier(), "_")) { final String[] pricingParams = mojo.getPricingTier().split("_"); @@ -99,31 +184,10 @@ public String getSubscriptionId() { } public Region getRegion() { - return parseExpandableParameter(Region::fromName, mojo.getRegion(), EXPANDABLE_REGION_WARNING); - } - - public DockerConfiguration getDockerConfiguration() throws AzureExecutionException { - final MavenRuntimeConfig runtime = mojo.getRuntime(); - if (runtime == null) { + if (StringUtils.isEmpty(mojo.getRegion())) { return null; } - final OperatingSystem os = getOs(runtime); - if (os != OperatingSystem.DOCKER) { - return null; - } - final MavenDockerCredentialProvider credentialProvider = getDockerCredential(runtime.getServerId()); - return DockerConfiguration.builder() - .registryUrl(runtime.getRegistryUrl()) - .image(runtime.getImage()) - .userName(credentialProvider.getUsername()) - .password(credentialProvider.getPassword()).build(); - } - - public List getMavenArtifacts() throws AzureExecutionException { - if (mojo.getDeployment() == null || mojo.getDeployment().getResources() == null) { - return Collections.emptyList(); - } - return convertResourceToArtifacts(mojo.getDeployment().getResources()); + return parseExpandableParameter(Region::fromName, mojo.getRegion(), EXPANDABLE_REGION_WARNING); } public Runtime getRuntime() { @@ -135,8 +199,10 @@ public Runtime getRuntime() { if (os == OperatingSystem.DOCKER) { return Runtime.DOCKER; } - final JavaVersion javaVersion = parseExpandableParameter(JavaVersion::fromString, runtime.getJavaVersion(), EXPANDABLE_JAVA_VERSION_WARNING); - final WebContainer webContainer = parseExpandableParameter(WebContainer::fromString, runtime.getWebContainer(), EXPANDABLE_WEB_CONTAINER_WARNING); + final JavaVersion javaVersion = StringUtils.isEmpty(runtime.getJavaVersion()) ? null : + parseExpandableParameter(JavaVersion::fromString, runtime.getJavaVersion(), EXPANDABLE_JAVA_VERSION_WARNING); + final WebContainer webContainer = StringUtils.isEmpty(runtime.getWebContainer()) ? null : + parseExpandableParameter(WebContainer::fromString, runtime.getWebContainer(), EXPANDABLE_WEB_CONTAINER_WARNING); return Runtime.getRuntime(os, webContainer, javaVersion); } @@ -144,62 +210,23 @@ private OperatingSystem getOs(final MavenRuntimeConfig runtime) { return OperatingSystem.fromString(runtime.getOs()); } - public WebAppConfig parse() throws AzureExecutionException { - return WebAppConfig.builder() - .subscriptionId(getSubscriptionId()) - .appName(getAppName()) - .resourceGroup(getResourceGroup()) - .servicePlanName(getAppServicePlanName()) - .servicePlanResourceGroup(getAppServicePlanResourceGroup()) - .pricingTier(getPricingTier()) - .region(getRegion()) - .runtime(getRuntime()) - .dockerConfiguration(getDockerConfiguration()) - .deploymentSlotName(getDeploymentSlotName()) - .deploymentSlotConfigurationSource(getDeploymentSlotConfigurationSource()) - .webAppArtifacts(getMavenArtifacts()) - .appSettings(this.mojo.getAppSettings()) - .build(); - } - - // todo: replace WebAppConfiguration with WebAppConfig - public WebAppConfiguration getWebAppConfiguration() { - WebAppConfiguration.WebAppConfigurationBuilder builder = WebAppConfiguration.builder(); - final Runtime runtime = getRuntime(); - final OperatingSystem os = Optional.ofNullable(runtime).map(Runtime::getOperatingSystem).orElse(null); - if (os == null) { - Log.debug("No runtime related config is specified. It will cause error if creating a new web app."); - } else { - switch (os) { - case WINDOWS: - case LINUX: - builder = builder.javaVersion(Objects.toString(runtime.getJavaVersion())).webContainer(Objects.toString(runtime.getWebContainer())); - break; - case DOCKER: - final MavenRuntimeConfig runtimeConfig = mojo.getRuntime(); - builder = builder.image(runtimeConfig.getImage()).serverId(runtimeConfig.getServerId()).registryUrl(runtimeConfig.getRegistryUrl()); - break; - default: - Log.debug("Invalid operating system from the configuration."); - } + private RuntimeConfig getRuntimeConfig() throws AzureExecutionException { + final MavenRuntimeConfig runtime = mojo.getRuntime(); + if (runtime == null || runtime.isEmpty()) { + return null; } - return builder.appName(getAppName()) - .resourceGroup(getResourceGroup()) - .region(getRegion()) - .pricingTier(getPricingTier().getSize()) - .servicePlanName(mojo.getAppServicePlanName()) - .servicePlanResourceGroup(mojo.getAppServicePlanResourceGroup()) - .deploymentSlotSetting(mojo.getDeploymentSlotSetting()) - .os(os) - .mavenSettings(mojo.getSettings()) - .resources(Optional.ofNullable(mojo.getDeployment()).map(Deployment::getResources).orElse(null)) - .stagingDirectoryPath(mojo.getDeploymentStagingDirectoryPath()) - .buildDirectoryAbsolutePath(mojo.getBuildDirectoryAbsolutePath()) - .project(mojo.getProject()) - .session(mojo.getSession()) - .filtering(mojo.getMavenResourcesFiltering()) - .schemaVersion("v2") - .build(); + final OperatingSystem os = getOs(runtime); + final JavaVersion javaVersion = StringUtils.isEmpty(runtime.getJavaVersion()) ? null : + parseExpandableParameter(JavaVersion::fromString, runtime.getJavaVersion(), EXPANDABLE_JAVA_VERSION_WARNING); + final WebContainer webContainer = StringUtils.isEmpty(runtime.getWebContainer()) ? null : + parseExpandableParameter(WebContainer::fromString, runtime.getWebContainer(), EXPANDABLE_WEB_CONTAINER_WARNING); + final RuntimeConfig result = new RuntimeConfig().os(os).javaVersion(javaVersion).webContainer(webContainer) + .image(runtime.getImage()).registryUrl(runtime.getRegistryUrl()); + if (StringUtils.isNotEmpty(runtime.getServerId())) { + final MavenDockerCredentialProvider credentialProvider = getDockerCredential(runtime.getServerId()); + result.username(credentialProvider.getUsername()).password(credentialProvider.getPassword()); + } + return result; } protected MavenDockerCredentialProvider getDockerCredential(String serverId) { @@ -253,12 +280,10 @@ private static List convertOneDeployResourceToArtifacts(Resource } if (artifacts.isEmpty()) { - Log.warn(String.format("Cannot find any files defined by resource(%s)", - StringUtils.firstNonBlank(resource.toString()))); + Log.warn(String.format("Cannot find any files defined by resource(%s)", StringUtils.firstNonBlank(resource.toString()))); } if (type.ignorePath() && StringUtils.isNotBlank(resource.getTargetPath())) { - throw new AzureToolkitRuntimeException(String.format("'' is not allowed for deployable type('%s').", - type)); + throw new AzureToolkitRuntimeException(String.format("'' is not allowed for deployable type('%s').", type)); } if (StringUtils.isNotBlank(type.getFileExt())) { final String expectFileExtension = type.getFileExt(); @@ -307,5 +332,4 @@ private static String normalizePath(String path) { } return StringUtils.removeEnd(path.replaceAll("([\\\\/])+", Matcher.quoteReplacement("/")), "/"); } - } diff --git a/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/task/DeployExternalResourcesTask.java b/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/task/DeployExternalResourcesTask.java new file mode 100644 index 0000000000..60efb20bc4 --- /dev/null +++ b/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/task/DeployExternalResourcesTask.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +package com.microsoft.azure.maven.webapp.task; + +import com.microsoft.azure.maven.model.DeploymentResource; +import com.microsoft.azure.maven.webapp.utils.FTPUtils; +import com.microsoft.azure.maven.webapp.utils.Utils; +import com.microsoft.azure.toolkit.lib.appservice.model.PublishingProfile; +import com.microsoft.azure.toolkit.lib.appservice.service.IAppService; +import com.microsoft.azure.toolkit.lib.appservice.service.IWebAppBase; +import com.microsoft.azure.toolkit.lib.common.bundle.AzureString; +import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException; +import com.microsoft.azure.toolkit.lib.common.messager.AzureMessager; +import com.microsoft.azure.toolkit.lib.common.task.AzureTask; +import org.apache.commons.net.ftp.FTPClient; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +@Deprecated +public class DeployExternalResourcesTask extends AzureTask> { + private static final String DEPLOY_START = "Trying to deploy external resources to %s..."; + private static final String DEPLOY_FINISH = "Successfully deployed the resources to %s"; + + final IWebAppBase target; + final List resources; + + public DeployExternalResourcesTask(final IWebAppBase target, final List resources) { + this.target = target; + this.resources = resources; + } + + @Override + public IWebAppBase execute() { + AzureMessager.getMessager().info(AzureString.format(DEPLOY_START, target.name())); + deployExternalResources(target, resources); + AzureMessager.getMessager().info(AzureString.format(DEPLOY_FINISH, target.name())); + return target; + } + + private void deployExternalResources(final IAppService target, final List resources) { + if (resources.isEmpty()) { + return; + } + AzureMessager.getMessager().info(AzureString.format("Uploading resources to %s", target.name())); + final PublishingProfile publishingProfile = target.getPublishingProfile(); + final String serverUrl = publishingProfile.getFtpUrl().split("/", 2)[0]; + try { + final FTPClient ftpClient = FTPUtils.getFTPClient(serverUrl, publishingProfile.getFtpUsername(), publishingProfile.getFtpPassword()); + for (final DeploymentResource externalResource : resources) { + uploadResource(externalResource, ftpClient); + } + } catch (IOException e) { + throw new AzureToolkitRuntimeException(e.getMessage(), e); + } + } + + private static void uploadResource(DeploymentResource resource, FTPClient ftpClient) throws IOException { + final List files = Utils.getArtifacts(resource); + final String target = resource.getAbsoluteTargetPath(); + for (final File file : files) { + FTPUtils.uploadFile(ftpClient, file.getPath(), target); + } + } +} diff --git a/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/utils/DeployUtils.java b/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/utils/DeployUtils.java deleted file mode 100644 index 99eb90f6a1..0000000000 --- a/azure-webapp-maven-plugin/src/main/java/com/microsoft/azure/maven/webapp/utils/DeployUtils.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - */ - -package com.microsoft.azure.maven.webapp.utils; - -import com.microsoft.azure.toolkit.lib.common.exception.AzureExecutionException; -import com.microsoft.azure.toolkit.lib.common.logging.Log; -import com.microsoft.azure.maven.model.DeploymentResource; -import com.microsoft.azure.toolkit.lib.appservice.model.WebAppArtifact; -import com.microsoft.azure.toolkit.lib.appservice.model.DeployType; -import com.microsoft.azure.toolkit.lib.appservice.model.PublishingProfile; -import com.microsoft.azure.toolkit.lib.appservice.service.IAppService; -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.net.ftp.FTPClient; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.List; -import java.util.Set; -import java.util.jar.JarInputStream; -import java.util.jar.Manifest; -import java.util.stream.Collectors; - -public class DeployUtils { - private static final String DEFAULT_APP_SERVICE_JAR_NAME = "app.jar"; - private static final String WEB_CONFIG = "web.config"; - private static final String RENAMING_MESSAGE = "Renaming %s to %s"; - private static final String RENAMING_FAILED_MESSAGE = "Failed to rename artifact to %s, which is required in Java SE environment, " + - "refer to https://docs.microsoft.com/en-us/azure/app-service/containers/configure-language-java#set-java-runtime-options for details."; - private static final String NO_EXECUTABLE_JAR = "No executable jar found in target folder according to resource filter in , " + - "please make sure the resource filter is correct and you have built the jar."; - private static final String MULTI_EXECUTABLE_JARS = "Multi executable jars found in , please check the configuration"; - - public static void deployResourcesWithFtp(IAppService appService, List externalResources) throws AzureExecutionException { - if (externalResources.isEmpty()) { - return; - } - final PublishingProfile publishingProfile = appService.getPublishingProfile(); - final String serverUrl = publishingProfile.getFtpUrl().split("/", 2)[0]; - try { - - final FTPClient ftpClient = FTPUtils.getFTPClient(serverUrl, publishingProfile.getFtpUsername(), publishingProfile.getFtpPassword()); - for (final DeploymentResource externalResource : externalResources) { - uploadResource(externalResource, ftpClient); - } - } catch (IOException e) { - throw new AzureExecutionException(e.getMessage(), e); - } - } - - public static boolean isAllWarArtifacts(List webAppArtifacts) { - final Set deployTypes = webAppArtifacts.stream().map(WebAppArtifact::getDeployType).collect(Collectors.toSet()); - return deployTypes.size() == 1 && deployTypes.iterator().next() == DeployType.WAR; - } - - private static void uploadResource(DeploymentResource resource, FTPClient ftpClient) throws IOException { - final List files = Utils.getArtifacts(resource); - final String target = resource.getAbsoluteTargetPath(); - for (final File file : files) { - FTPUtils.uploadFile(ftpClient, file.getPath(), target); - } - } - - /** - * Rename project jar to app.jar for java se app service - */ - public static void prepareJavaSERuntimeJarArtifact(final List artifacts, final String finalName) throws AzureExecutionException { - if (existsWebConfig(artifacts)) { - return; - } - final File artifact = getProjectJarArtifact(artifacts, finalName); - final File renamedArtifact = new File(artifact.getParent(), DEFAULT_APP_SERVICE_JAR_NAME); - if (!StringUtils.equals(artifact.getName(), DEFAULT_APP_SERVICE_JAR_NAME)) { - Log.info(String.format(RENAMING_MESSAGE, artifact.getAbsolutePath(), DEFAULT_APP_SERVICE_JAR_NAME)); - if (!artifact.renameTo(renamedArtifact)) { - throw new AzureExecutionException(String.format(RENAMING_FAILED_MESSAGE, DEFAULT_APP_SERVICE_JAR_NAME)); - } - } - } - - private static File getProjectJarArtifact(final List artifacts, final String finalName) throws AzureExecutionException { - final List executableArtifacts = artifacts.stream() - .filter(DeployUtils::isExecutableJar).collect(Collectors.toList()); - final File finalArtifact = executableArtifacts.stream() - .filter(file -> StringUtils.equals(finalName, file.getName())).findFirst().orElse(null); - if (executableArtifacts.size() == 0) { - throw new AzureExecutionException(NO_EXECUTABLE_JAR); - } else if (finalArtifact == null && executableArtifacts.size() > 1) { - throw new AzureExecutionException(MULTI_EXECUTABLE_JARS); - } - return finalArtifact == null ? executableArtifacts.get(0) : finalArtifact; - } - - private static boolean existsWebConfig(final List artifacts) { - return artifacts.stream().anyMatch(file -> StringUtils.equals(file.getName(), WEB_CONFIG)); - } - - private static boolean isExecutableJar(File file) { - if (!StringUtils.equalsIgnoreCase(FilenameUtils.getExtension(file.getName()), "jar")) { - return false; - } - try (final FileInputStream fileInputStream = new FileInputStream(file); - final JarInputStream jarInputStream = new JarInputStream(fileInputStream)) { - final Manifest manifest = jarInputStream.getManifest(); - return manifest != null && manifest.getMainAttributes().getValue("Main-Class") != null; - } catch (IOException e) { - return false; - } - } -} diff --git a/azure-webapp-maven-plugin/src/main/resources/schema/WebAppConfiguration.json b/azure-webapp-maven-plugin/src/main/resources/schema/WebAppConfiguration.json index 9cd35a427f..d08b331353 100644 --- a/azure-webapp-maven-plugin/src/main/resources/schema/WebAppConfiguration.json +++ b/azure-webapp-maven-plugin/src/main/resources/schema/WebAppConfiguration.json @@ -3,55 +3,8 @@ "title": "Configuration", "description": "Configuration for Maven plugin for Azure Web App", "properties": { - "subscriptionId": { - "$ref": "#/definitions/non-empty-string" - }, - "resourceGroup": { - "$ref": "#/definitions/azure-resource-group" - }, - "appName": { - "type": "string", - "pattern": "^[a-zA-Z0-9\\-]+$", - "minLength": 2, - "maxLength": 60 - }, - "appServicePlanName": { - "type": "string", - "pattern": "^[a-zA-Z0-9\\-]+$", - "minLength": 1, - "maxLength": 40 - }, - "appServicePlanResourceGroup": { - "$ref": "#/definitions/azure-resource-group" - }, - "auth": { - "$ref": "#/definitions/auth" - }, - "region": { - "$ref": "#/definitions/non-empty-string" - }, "pricingTier": { - "$ref": "#/definitions/non-empty-string" - }, - "runtime": { - "$ref": "#/definitions/runtime" - }, - "deployment": { - "$ref": "#/definitions/deployment" - }, - "deploymentSlot": { - "$ref": "#/definitions/deployment-slot" - }, - "appSettings": { - "type": "object" - }, - "allowTelemetry": { - "type": "boolean", - "default": true - }, - "failsOnError": { - "type": "boolean", - "default": true + "$ref": "classpath:///schema/common/NonEmptyString.json" }, "stopAppDuringDeployment": { "type": "boolean", @@ -61,208 +14,26 @@ "type": "boolean", "default": false }, - "authType": { - "$ref": "#/definitions/auth-type", - "deprecationMessage": "Please set auth related properties like type in " - } - }, - "required": [ - "appName", - "resourceGroup" - ], - "definitions": { - "non-empty-string": { - "type": "string", - "minLength": 1 - }, - "auth-type": { - "type": "string", - "pattern": "(?i)^(auto|service_principal|managed_identity|azure_cli|vscode|intellij|azure_auth_maven_plugin|device_code|oauth2|visual_studio)$" + "region": { + "$ref": "classpath:///schema/common/NonEmptyString.json" }, - "azure-resource-group": { + "schemaVersion": { "type": "string", - "pattern": "^[a-zA-Z0-9._\\-()]+$", - "minLength": 1, - "maxLength": 90 - }, - "deployment-slot": { - "title": "DeploymentSlotConfiguration", - "description": "Deployment slot configuration for Maven plugin for Azure Web App", - "type": "object", - "properties": { - "name": { - "type": "string", - "pattern": "^[A-Za-z0-9-]+$", - "minLength": 1, - "maxLength": 60 - }, - "configurationSource": { - "$ref": "#/definitions/non-empty-string" - } - }, - "required": [ - "name" - ] + "deprecationMessage": " has been deprecated, only v2 schema is supported now" }, "runtime": { - "title": "Runtime", - "description": "Runtime configuration for Maven plugin for Azure Web App", - "type": "object", - "properties": { - "os": { - "description": "The operating system for app service", - "type": "string", - "pattern": "(?i)^(windows|linux|docker)$" - }, - "webContainer": { - "$ref": "#/definitions/non-empty-string" - }, - "javaVersion": { - "$ref": "#/definitions/non-empty-string" - }, - "image": { - "$ref": "#/definitions/non-empty-string" - }, - "serverId": { - "$ref": "#/definitions/non-empty-string" - }, - "registryUrl": { - "type": "string", - "pattern": "^https.*" - } - }, - "dependencies": { - "serverId": [ - "image" - ], - "registryUrl": [ - "image" - ] - }, - "required": [ - "os" - ], - "allOf": [ - { - "if": { - "properties": { - "os": { - "pattern": "(?i)^(windows|linux)$" - } - }, - "required": [ - "os" - ] - }, - "then": { - "required": [ - "javaVersion", - "webContainer" - ] - } - }, - { - "if": { - "properties": { - "os": { - "pattern": "(?i)^(docker)$" - } - }, - "required": [ - "os" - ] - }, - "then": { - "required": [ - "image" - ] - } - } - ] - }, - "auth": { - "title": "AuthConfiguration", - "description": "The auth config for accessing azure resources", - "type": "object", - "properties": { - "type": { - "$ref": "#/definitions/auth-type" - }, - "serverId": { - "description": "The server id defined in maven settings", - "type": "string" - }, - "client": { - "description": "Client ID", - "type": "string" - }, - "tenant": { - "description": "Tenant ID", - "type": "string" - }, - "key": { - "description": "Password", - "type": "string" - }, - "certificate": { - "description": "The absolute path of your certificate", - "type": "string" - }, - "certificatePassword": { - "description": "The password for your certificate, if there is any", - "type": "string" - }, - "environment": { - "description": "The Azure cloud environment", - "type": "string", - "default": "AZURE", - "pattern": "(?i)^(AZURE|AZURE_CHINA|AZURE_GERMANY|AZURE_US_GOVERNMENT)$" - } - }, - "allOf": [ - { - "if": { - "properties": { - "type": { - "pattern": "(?i)^service_principal$" - } - }, - "required": [ - "type" - ] - }, - "then": { - "anyOf": [ - { - "required": [ - "client", - "tenant", - "key" - ] - }, - { - "required": [ - "client", - "tenant", - "certificate" - ] - } - ] - } - } - ], - "not": { - "required": [ - "key", - "certificate" - ] - }, - "dependencies": { - "certificatePassword": [ - "certificate" - ] - } + "$ref": "classpath:///schema/appservice/Runtime.json" }, + "deployment": { + "$ref": "#/definitions/deployment" + } + }, + "allOf": [ + { + "$ref": "classpath:///schema/maven/AzureAppServiceMavenPlugin.json" + } + ], + "definitions": { "deployment-resource": { "type": "object", "title": "Deployment Resource", @@ -273,21 +44,21 @@ "pattern": "(?i)^(war|jar|ear|lib|script|static|startup|zip)$" }, "directory": { - "$ref": "#/definitions/non-empty-string" + "$ref": "classpath:///schema/common/NonEmptyString.json" }, "targetPath": { - "$ref": "#/definitions/non-empty-string" + "$ref": "classpath:///schema/common/NonEmptyString.json" }, "includes": { "type": "array", "items": { - "$ref": "#/definitions/non-empty-string" + "$ref": "classpath:///schema/common/NonEmptyString.json" } }, "excludes": { "type": "array", "items": { - "$ref": "#/definitions/non-empty-string" + "$ref": "classpath:///schema/common/NonEmptyString.json" } } } diff --git a/azure-webapp-maven-plugin/src/test/java/com/microsoft/azure/maven/webapp/DeployMojoTest.java b/azure-webapp-maven-plugin/src/test/java/com/microsoft/azure/maven/webapp/DeployMojoTest.java index ac30a368c0..79b9afeffe 100644 --- a/azure-webapp-maven-plugin/src/test/java/com/microsoft/azure/maven/webapp/DeployMojoTest.java +++ b/azure-webapp-maven-plugin/src/test/java/com/microsoft/azure/maven/webapp/DeployMojoTest.java @@ -94,7 +94,7 @@ public void getTelemetryProperties() throws Exception { ReflectionUtils.setVariableValueInObject(spyMojo, "plugin", plugin); doReturn("azure-webapp-maven-plugin").when(plugin).getArtifactId(); final Map map = spyMojo.getTelemetryProperties(); - assertEquals(12, map.size()); + assertEquals(13, map.size()); assertTrue(map.containsKey(JAVA_VERSION_KEY)); assertTrue(map.containsKey(JAVA_WEB_CONTAINER_KEY)); assertTrue(map.containsKey(DOCKER_IMAGE_TYPE_KEY)); diff --git a/azure-webapp-maven-plugin/src/test/java/com/microsoft/azure/maven/webapp/parser/ConfigParserTest.java b/azure-webapp-maven-plugin/src/test/java/com/microsoft/azure/maven/webapp/parser/ConfigParserTest.java index 3f9ce1fa03..784fab635d 100644 --- a/azure-webapp-maven-plugin/src/test/java/com/microsoft/azure/maven/webapp/parser/ConfigParserTest.java +++ b/azure-webapp-maven-plugin/src/test/java/com/microsoft/azure/maven/webapp/parser/ConfigParserTest.java @@ -106,8 +106,8 @@ public void getWindowsRuntime() { public void getLinuxRuntime() { doReturn("linux").when(runtimeSetting).getOs(); doReturn("Java 8").when(runtimeSetting).getJavaVersion(); - doReturn("JBosseap 7.2").when(runtimeSetting).getWebContainer(); - assertEquals(parser.getRuntime(), Runtime.LINUX_JAVA8_JBOSS72); + doReturn("JBosseap 7").when(runtimeSetting).getWebContainer(); + assertEquals(parser.getRuntime(), Runtime.LINUX_JAVA8_JBOSS7); doReturn("linux").when(runtimeSetting).getOs(); doReturn("Java 11").when(runtimeSetting).getJavaVersion(); diff --git a/build-tools/pom.xml b/build-tools/pom.xml index b2cfe02c71..c7f1b31307 100644 --- a/build-tools/pom.xml +++ b/build-tools/pom.xml @@ -7,7 +7,7 @@ com.microsoft.azure azure-maven-plugins - 1.14.0-SNAPSHOT + 1.14.0 maven-plugins-build-tools diff --git a/build-tools/src/main/resources/checkstyle/checkstyle-suppressions.xml b/build-tools/src/main/resources/checkstyle/checkstyle-suppressions.xml index 628f7bbf21..6303c32f8a 100644 --- a/build-tools/src/main/resources/checkstyle/checkstyle-suppressions.xml +++ b/build-tools/src/main/resources/checkstyle/checkstyle-suppressions.xml @@ -4,5 +4,6 @@ "https://checkstyle.org/dtds/suppressions_1_1.dtd"> - + + diff --git a/build-tools/src/main/resources/checkstyle/checkstyle.xml b/build-tools/src/main/resources/checkstyle/checkstyle.xml index a944eae803..6fd52915b5 100644 --- a/build-tools/src/main/resources/checkstyle/checkstyle.xml +++ b/build-tools/src/main/resources/checkstyle/checkstyle.xml @@ -10,7 +10,7 @@ page at https://checkstyle.org/config.html --> - + @@ -26,17 +26,17 @@ page at https://checkstyle.org/config.html --> - - - + + + - - - - - + + + + + + + @@ -51,19 +51,19 @@ page at https://checkstyle.org/config.html --> - - - - - + + + + + + + + - + - + - - - - - + + + + + - - - - - - - - - - - --> - + @@ -212,7 +200,7 @@ page at https://checkstyle.org/config.html --> --> - + @@ -234,7 +222,7 @@ page at https://checkstyle.org/config.html --> --> - + @@ -268,7 +256,7 @@ page at https://checkstyle.org/config.html --> + value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/> @@ -301,14 +289,15 @@ page at https://checkstyle.org/config.html --> - + + - + - - - + + + - + - - - - - + + + + + @@ -414,17 +403,47 @@ page at https://checkstyle.org/config.html --> - + - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index d59ad831ad..594b11929e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.microsoft.azure azure-maven-plugins - 1.14.0-SNAPSHOT + 1.14.0 pom Maven Plugins for Azure Maven plugins for Microsoft Azure services @@ -80,8 +80,8 @@ 3.1.0 2.9.1 3.1.2 - - 1.14.0-SNAPSHOT + 1.12.6 + 1.14.0 0.8.7 1.0.56 @@ -92,7 +92,7 @@ com.microsoft.azure azure-toolkit-libs - 0.11.0 + 0.12.1 pom import @@ -191,6 +191,11 @@ + + com.nickwongdev + aspectj-maven-plugin + ${maven.aspectj-plugin.version} + maven-clean-plugin ${maven.clean-plugin.version} @@ -253,7 +258,7 @@ com.microsoft.azure maven-plugins-build-tools - 1.14.0-SNAPSHOT + 1.14.0 com.puppycrawl.tools