Function Maven Plugin prototype (#13)

1. support function deployment using FTP;
2. implement function.json generation;
This commit is contained in:
Kevin Zhao 2017-07-31 10:49:43 +08:00 коммит произвёл GitHub
Родитель ef1e164d9c
Коммит 28ece551c9
33 изменённых файлов: 1213 добавлений и 135 удалений

Просмотреть файл

@ -1,7 +1,7 @@
language: java
jdk:
- oraclejdk7
- oraclejdk8
branches:
only:

Просмотреть файл

@ -7,6 +7,7 @@
This repository contains all Maven plugins for Microsoft Azure services. Complete list of all plugins are shown as below.
### [Azure Web Apps Maven Plugin](./webapp-maven-plugin/README.md)
### [Azure Functions Maven Plugin](./function-maven-plugin/README.md)
### Contributing

Просмотреть файл

@ -3,7 +3,7 @@ branches:
- master
environment:
JAVA_HOME: "C:\\Program Files\\Java\\jdk1.7.0"
JAVA_HOME: "C:\\Program Files\\Java\\jdk1.8.0"
PYTHON: "C:\\Python35"
CLIENT_ID:
secure: rSMXqU3uFoLjHUvut92UhV/DxNcC5csNr/BSw0N2UryRVhSge2tVYvz5PAHXSJla

Просмотреть файл

@ -5,4 +5,6 @@
<Class name="com.microsoft.azure.maven.function.HelpMojo"/>
<Class name="com.microsoft.azure.maven.telemetry.GetHashMac"/>
<Bug pattern="REC_CATCH_EXCEPTION"/>
<Bug pattern="DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED"/>
<Bug pattern="RV_RETURN_VALUE_IGNORED_BAD_PRACTICE"/>
</FindBugsFilter>

Просмотреть файл

@ -6,7 +6,7 @@
<groupId>com.microsoft.azure</groupId>
<artifactId>function-maven-plugin</artifactId>
<version>0.1.0</version>
<version>0.1.1</version>
<packaging>maven-plugin</packaging>
<name>Azure Functions Maven Plugin</name>
<description>Maven Plugin for Azure Functions</description>
@ -21,6 +21,9 @@
<codehaus.plexus-utils.version>3.0.20</codehaus.plexus-utils.version>
<azure.version>1.1.2</azure.version>
<azure.ai.version>1.0.8</azure.ai.version>
<azure.maven-lib.version>0.1.1</azure.maven-lib.version>
<azure.function.version>1.0.0</azure.function.version>
<reflections.version>0.9.11</reflections.version>
<junit.version>4.12</junit.version>
<mockito.version>2.4.0</mockito.version>
<spring-test.version>4.1.6.RELEASE</spring-test.version>
@ -83,6 +86,15 @@
</profile>
</profiles>
<!-- Use local repo to host private JAR before the official package is released -->
<repositories>
<repository>
<id>local-repo</id>
<name>local-repo</name>
<url>file://${project.basedir}/repo</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
@ -129,7 +141,17 @@
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>maven-plugin-lib</artifactId>
<version>0.1.0</version>
<version>${azure.maven-lib.version}</version>
</dependency>
<dependency>
<groupId>com.microsoft.azure.serverless</groupId>
<artifactId>azure-functions-java-core</artifactId>
<version>${azure.function.version}</version>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>${reflections.version}</version>
</dependency>
<!-- TEST -->
@ -208,8 +230,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
@ -224,7 +246,6 @@
<check/>
<instrumentation>
<excludes>
<exclude>**/configuration/*.class</exclude>
<exclude>**/HelpMojo.class</exclude>
</excludes>
</instrumentation>

Просмотреть файл

@ -6,13 +6,23 @@
package com.microsoft.azure.maven.function;
import com.microsoft.azure.management.appservice.FunctionApp;
import com.microsoft.azure.maven.AbstractAzureMojo;
import org.apache.maven.model.Resource;
import org.apache.maven.plugins.annotations.Parameter;
import java.util.List;
import java.io.File;
import java.nio.file.Paths;
public abstract class AbstractFunctionMojo extends AbstractAzureMojo {
@Parameter(defaultValue = "${project.build.finalName}", readonly = true, required = true)
protected String finalName;
@Parameter(defaultValue = "${project.build.directory}", readonly = true, required = true)
protected File buildDirectory;
@Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true, required = true)
protected File outputDirectory;
@Parameter(property = "resourceGroup", required = true)
protected String resourceGroup;
@ -22,11 +32,13 @@ public abstract class AbstractFunctionMojo extends AbstractAzureMojo {
@Parameter(property = "region", defaultValue = "westus")
protected String region;
@Parameter(property = "functionName", required = true)
protected String functionName;
public String getFinalName() {
return finalName;
}
@Parameter(property = "resources")
protected List<Resource> resources;
public String getBuildDirectoryAbsolutePath() {
return buildDirectory.getAbsolutePath();
}
public String getResourceGroup() {
return resourceGroup;
@ -40,11 +52,19 @@ public abstract class AbstractFunctionMojo extends AbstractAzureMojo {
return region;
}
public String getFunctionName() {
return functionName;
public String getDeploymentStageDirectory() {
return Paths.get(getBuildDirectoryAbsolutePath(),
"azure-functions",
getAppName()).toString();
}
public List<Resource> getResources() {
return resources;
public FunctionApp getFunctionApp() {
FunctionApp app = null;
try {
app = getAzureClient().appServices().functionApps().getByResourceGroup(getResourceGroup(), getAppName());
} catch (Exception e) {
// Swallow exception
}
return app;
}
}

Просмотреть файл

@ -0,0 +1,86 @@
/**
* 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.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.azure.maven.function.handlers.AnnotationHandler;
import com.microsoft.azure.maven.function.handlers.AnnotationHandlerImpl;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Set;
/**
* Goal which searches functions in target/classes directory and generates function.json files.
*/
@Mojo(name = "build", defaultPhase = LifecyclePhase.PACKAGE)
public class BuildMojo extends AbstractFunctionMojo {
@Override
protected void doExecute() throws Exception {
final AnnotationHandler handler = getAnnotationHandler();
getLog().info("Searching for Azure Function entry points...");
final Set<Method> functions = handler.findFunctions(getClassUrl());
getLog().info(functions.size() + " Azure Function entry point(s) found.");
getLog().info("Generating Azure Function configurations...");
final Map<String, FunctionConfiguration> configMap = handler.generateConfigurations(functions);
final String scriptFilePath = getScriptFilePath();
configMap.values().forEach(config -> config.setScriptFile(scriptFilePath));
getLog().info("Generation done.");
getLog().info("Validating generated configurations...");
configMap.values().forEach(config -> config.validate());
getLog().info("Validation done.");
getLog().info("Saving configurations to function.json...");
outputJsonFile(configMap);
getLog().info("Saved successfully.");
getLog().info("function.json generation completed successfully.");
}
protected AnnotationHandler getAnnotationHandler() throws Exception {
return new AnnotationHandlerImpl(getLog());
}
protected URL getClassUrl() throws Exception {
return outputDirectory.toURI().toURL();
}
protected String getScriptFilePath() {
return new StringBuilder()
.append("..\\")
.append(getFinalName())
.append(".jar")
.toString();
}
protected void outputJsonFile(final Map<String, FunctionConfiguration> configMap) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
for (final Map.Entry<String, FunctionConfiguration> config : configMap.entrySet()) {
final File file = getFunctionJsonFile(config.getKey());
mapper.writerWithDefaultPrettyPrinter().writeValue(file, config.getValue());
}
}
protected File getFunctionJsonFile(final String functionName) throws IOException {
final Path functionDirPath = Paths.get(getDeploymentStageDirectory(), functionName);
functionDirPath.toFile().mkdirs();
final File functionJsonFile = Paths.get(functionDirPath.toString(), "function.json").toFile();
functionJsonFile.createNewFile();
return functionJsonFile;
}
}

Просмотреть файл

@ -6,18 +6,66 @@
package com.microsoft.azure.maven.function;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import com.microsoft.azure.management.appservice.FunctionApp;
import com.microsoft.azure.maven.function.handlers.ArtifactHandler;
import com.microsoft.azure.maven.function.handlers.FTPArtifactHandlerImpl;
import org.apache.maven.model.Resource;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Goal which deploy function to Azure.
*/
@Mojo(name = "deploy", defaultPhase = LifecyclePhase.DEPLOY)
public class DeployMojo extends AbstractFunctionMojo {
public static final String FUNCTION_DEPLOY_START = "Starting deploying to Function App ";
public static final String FUNCTION_DEPLOY_SUCCESS = "Successfully deployed to Function App ";
public static final String FUNCTION_APP_CREATE_START = "Specified Function App does not exist. " +
"Creating a new Function App ...";
public static final String FUNCTION_APP_CREATED = "Successfully created Function App ";
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
super.execute();
protected void doExecute() throws Exception {
getLog().info(FUNCTION_DEPLOY_START + getAppName() + "...");
createFunctionAppIfNotExist();
getArtifactHandler().publish(getResources());
getFunctionApp().syncTriggers();
getLog().info(FUNCTION_DEPLOY_SUCCESS + getAppName());
}
protected void createFunctionAppIfNotExist() {
final FunctionApp app = getFunctionApp();
if (app == null) {
getLog().info(FUNCTION_APP_CREATE_START);
getAzureClient().appServices().functionApps()
.define(getAppName())
.withRegion(getRegion())
.withNewResourceGroup(getResourceGroup())
.create();
getLog().info(FUNCTION_APP_CREATED + getAppName());
}
}
protected List<Resource> getResources() {
final Resource resource = new Resource();
resource.setDirectory(getBuildDirectoryAbsolutePath());
resource.setTargetPath("/");
resource.setFiltering(false);
resource.setIncludes(Arrays.asList("*.jar"));
final ArrayList<Resource> resources = new ArrayList<>();
resources.add(resource);
return resources;
}
protected ArtifactHandler getArtifactHandler() {
return new FTPArtifactHandlerImpl(this);
}
}

Просмотреть файл

@ -0,0 +1,88 @@
/**
* 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.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.microsoft.azure.maven.function.bindings.BaseBinding;
import java.util.ArrayList;
import java.util.List;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class FunctionConfiguration {
public static final String MULTIPLE_TRIGGER = "Only one trigger is allowed for each Azure Function. " +
"Multiple triggers found on method: ";
public static final String HTTP_OUTPUT_NOT_ALLOWED = "HttpOutput binding is only allowed to use with " +
"HttpTrigger binding. HttpOutput binding found on method: ";
private String scriptFile;
private String entryPoint;
private List<BaseBinding> bindings = new ArrayList<>();
private boolean disabled = false;
private boolean excluded;
@JsonGetter("scriptFile")
public String getScriptFile() {
return scriptFile;
}
@JsonGetter("entryPoint")
public String getEntryPoint() {
return entryPoint;
}
@JsonGetter("bindings")
public List<BaseBinding> getBindings() {
return bindings;
}
@JsonGetter("disabled")
public boolean isDisabled() {
return disabled;
}
@JsonGetter("excluded")
public boolean isExcluded() {
return excluded;
}
public void setDisabled(boolean disabled) {
this.disabled = disabled;
}
public void setExcluded(boolean excluded) {
this.excluded = excluded;
}
public void setScriptFile(String scriptFile) {
this.scriptFile = scriptFile;
}
public void setEntryPoint(String entryPoint) {
this.entryPoint = entryPoint;
}
public void setBindings(List<BaseBinding> bindings) {
this.bindings = bindings;
}
public void validate() {
if (getBindings().stream().filter(b -> b.getType().endsWith("Trigger")).count() > 1) {
throw new RuntimeException(MULTIPLE_TRIGGER + getEntryPoint());
}
if (getBindings().stream().noneMatch(b -> b.getType().equalsIgnoreCase("httpTrigger")) &&
getBindings().stream().anyMatch(b -> b.getType().equalsIgnoreCase("http"))) {
throw new RuntimeException(HTTP_OUTPUT_NOT_ALLOWED + getEntryPoint());
}
}
}

Просмотреть файл

@ -0,0 +1,58 @@
/**
* 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.bindings;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class BaseBinding {
protected String type = "";
protected String name = "";
protected String direction = "";
@JsonGetter("type")
public String getType() {
return type;
}
@JsonGetter("name")
public String getName() {
return name;
}
@JsonGetter("direction")
public String getDirection() {
return direction;
}
public void setType(String type) {
this.type = type;
}
public void setName(String name) {
this.name = name;
}
public void setDirection(String direction) {
this.direction = direction;
}
@Override
public String toString() {
return new StringBuilder().append("[ name: ")
.append(getName())
.append(", type: ")
.append(getType())
.append(", direction: ")
.append(getDirection())
.append(" ]")
.toString();
}
}

Просмотреть файл

@ -0,0 +1,68 @@
/**
* 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.bindings;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.microsoft.azure.serverless.functions.annotation.HttpOutput;
import com.microsoft.azure.serverless.functions.annotation.HttpTrigger;
import java.util.Locale;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class HttpBinding extends BaseBinding {
private String route = "";
private String webHookType = "";
private String authLevel = "";
private String[] methods = {};
public HttpBinding(final HttpTrigger httpTrigger) {
setDirection("in");
setType("httpTrigger");
setName(httpTrigger.name());
route = httpTrigger.route();
authLevel = httpTrigger.authLevel().toString().toLowerCase(Locale.ENGLISH);
methods = httpTrigger.methods();
webHookType = httpTrigger.webHookType();
}
public HttpBinding(final HttpOutput httpOutput) {
setDirection("out");
setType("http");
setName(httpOutput.name());
}
public HttpBinding() {
setDirection("out");
setType("http");
setName("$return");
}
@JsonGetter("route")
public String getRoute() {
return route;
}
@JsonGetter("webHookType")
public String getWebHookType() {
return webHookType;
}
@JsonGetter("authLevel")
public String getAuthLevel() {
return authLevel;
}
@JsonGetter("methods")
public String[] getMethods() {
return methods.clone();
}
}

Просмотреть файл

@ -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.maven.function.bindings;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.microsoft.azure.serverless.functions.annotation.QueueOutput;
import com.microsoft.azure.serverless.functions.annotation.QueueTrigger;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class QueueBinding extends BaseBinding {
private String queueName = "";
private String connection = "";
public QueueBinding(final QueueTrigger queueTrigger) {
setDirection("in");
setType("queueTrigger");
setName(queueTrigger.name());
queueName = queueTrigger.queueName();
connection = queueTrigger.connection();
}
public QueueBinding(final QueueOutput queueOutput) {
setDirection("out");
setType("queue");
setName(queueOutput.name());
queueName = queueOutput.queueName();
connection = queueOutput.connection();
}
@JsonGetter("queueName")
public String getQueueName() {
return queueName;
}
@JsonGetter("connection")
public String getConnection() {
return connection;
}
}

Просмотреть файл

@ -0,0 +1,45 @@
/**
* 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.bindings;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.microsoft.azure.serverless.functions.annotation.TimerTrigger;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class TimerBinding extends BaseBinding {
private String schedule;
private boolean runOnStartup;
private boolean useMonitor;
public TimerBinding(final TimerTrigger timerTrigger) {
setDirection("in");
setType("timerTrigger");
setName(timerTrigger.name());
schedule = timerTrigger.schedule();
runOnStartup = timerTrigger.runOnStartup();
useMonitor = timerTrigger.useMonitor();
}
@JsonGetter("schedule")
public String getSchedule() {
return schedule;
}
@JsonGetter("runOnStartUp")
public boolean isRunOnStartup() {
return runOnStartup;
}
@JsonGetter("useMonitor")
public boolean isUseMonitor() {
return useMonitor;
}
}

Просмотреть файл

@ -0,0 +1,23 @@
/**
* 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.handlers;
import com.microsoft.azure.maven.function.FunctionConfiguration;
import org.reflections.Configuration;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Map;
import java.util.Set;
public interface AnnotationHandler {
Set<Method> findFunctions(final URL url);
Map<String, FunctionConfiguration> generateConfigurations(final Set<Method> methods) throws Exception;
FunctionConfiguration generateConfiguration(final Method method) throws Exception;
}

Просмотреть файл

@ -0,0 +1,130 @@
/**
* 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.handlers;
import com.microsoft.azure.maven.function.FunctionConfiguration;
import com.microsoft.azure.maven.function.bindings.BaseBinding;
import com.microsoft.azure.maven.function.bindings.HttpBinding;
import com.microsoft.azure.maven.function.bindings.QueueBinding;
import com.microsoft.azure.maven.function.bindings.TimerBinding;
import com.microsoft.azure.serverless.functions.annotation.*;
import org.apache.maven.plugin.logging.Log;
import org.reflections.Reflections;
import org.reflections.scanners.MethodAnnotationsScanner;
import org.reflections.util.ConfigurationBuilder;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
public class AnnotationHandlerImpl implements AnnotationHandler {
protected Log log;
public AnnotationHandlerImpl(final Log log) {
this.log = log;
}
@Override
public Set<Method> findFunctions(final URL url) {
return new Reflections(
new ConfigurationBuilder()
.addUrls(url)
.addScanners(new MethodAnnotationsScanner())
.addClassLoader(getClassLoader(url)))
.getMethodsAnnotatedWith(FunctionName.class);
}
protected ClassLoader getClassLoader(final URL url) {
return new URLClassLoader(new URL[]{url}, this.getClass().getClassLoader());
}
@Override
public Map<String, FunctionConfiguration> generateConfigurations(final Set<Method> methods) throws Exception {
final Map<String, FunctionConfiguration> configMap = new HashMap<>();
for (final Method method : methods) {
final FunctionName functionAnnotation = method.getAnnotation(FunctionName.class);
log.debug("Starting processing function : " + functionAnnotation.value());
configMap.put(functionAnnotation.value(), generateConfiguration(method));
}
return configMap;
}
@Override
public FunctionConfiguration generateConfiguration(final Method method) {
final FunctionConfiguration config = new FunctionConfiguration();
final List<BaseBinding> bindings = config.getBindings();
for (final Parameter param : method.getParameters()) {
bindings.addAll(parseAnnotations(param::getAnnotations, this::parseParameterAnnotation));
}
if (!method.getReturnType().equals(Void.TYPE)) {
bindings.addAll(parseAnnotations(method::getAnnotations, this::parseMethodAnnotation));
if (bindings.stream().anyMatch(b -> b.getType().equalsIgnoreCase(HttpTrigger.class.getSimpleName())) &&
bindings.stream().noneMatch(b -> b.getName().equalsIgnoreCase("$return"))) {
bindings.add(new HttpBinding());
}
}
config.setEntryPoint(method.getDeclaringClass().getCanonicalName() + "." + method.getName());
return config;
}
protected List<BaseBinding> parseAnnotations(Supplier<Annotation[]> annotationProvider,
Function<Annotation, BaseBinding> annotationParser) {
final List<BaseBinding> bindings = new ArrayList<>();
for (final Annotation annotation : annotationProvider.get()) {
final BaseBinding binding = annotationParser.apply(annotation);
if (binding != null) {
log.debug("Adding binding: " + binding.toString());
bindings.add(binding);
}
}
return bindings;
}
protected BaseBinding parseParameterAnnotation(final Annotation annotation) {
if (annotation instanceof HttpTrigger) {
return new HttpBinding((HttpTrigger) annotation);
}
if (annotation instanceof HttpOutput) {
return new HttpBinding((HttpOutput) annotation);
}
if (annotation instanceof QueueTrigger) {
return new QueueBinding((QueueTrigger) annotation);
}
if (annotation instanceof QueueOutput) {
return new QueueBinding((QueueOutput) annotation);
}
if (annotation instanceof TimerTrigger) {
return new TimerBinding((TimerTrigger) annotation);
}
return null;
}
protected BaseBinding parseMethodAnnotation(final Annotation annotation) {
BaseBinding ret = null;
if (annotation instanceof HttpOutput) {
ret = new HttpBinding((HttpOutput) annotation);
} else if (annotation instanceof QueueOutput) {
ret = new QueueBinding((QueueOutput) annotation);
}
if (ret != null) {
ret.setName("$return");
}
return ret;
}
}

Просмотреть файл

@ -0,0 +1,15 @@
/**
* 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.handlers;
import org.apache.maven.model.Resource;
import java.util.List;
public interface ArtifactHandler {
void publish(final List<Resource> resources) throws Exception;
}

Просмотреть файл

@ -0,0 +1,63 @@
/**
* 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.handlers;
import com.microsoft.azure.management.appservice.FunctionApp;
import com.microsoft.azure.management.appservice.PublishingProfile;
import com.microsoft.azure.maven.FTPUploader;
import com.microsoft.azure.maven.Utils;
import com.microsoft.azure.maven.function.AbstractFunctionMojo;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.MojoExecutionException;
import java.io.IOException;
import java.util.List;
public class FTPArtifactHandlerImpl implements ArtifactHandler {
public static final String DEFAULT_FUNCTION_ROOT = "/site/wwwroot/";
public static final int DEFAULT_MAX_RETRY_TIMES = 3;
private AbstractFunctionMojo mojo;
public FTPArtifactHandlerImpl(final AbstractFunctionMojo mojo) {
this.mojo = mojo;
}
@Override
public void publish(final List<Resource> resources) throws Exception {
copyResourcesToStageDirectory(resources);
uploadDirectoryToFTP();
}
protected void copyResourcesToStageDirectory(final List<Resource> resources) throws IOException {
Utils.copyResources(
mojo.getProject(),
mojo.getSession(),
mojo.getMavenResourcesFiltering(),
resources,
mojo.getDeploymentStageDirectory());
}
protected void uploadDirectoryToFTP() throws MojoExecutionException {
final FTPUploader uploader = getUploader();
final FunctionApp app = mojo.getFunctionApp();
final PublishingProfile profile = app.getPublishingProfile();
final String serverUrl = profile.ftpUrl().split("/", 2)[0];
uploader.uploadDirectoryWithRetries(
serverUrl,
profile.ftpUsername(),
profile.ftpPassword(),
mojo.getDeploymentStageDirectory(),
DEFAULT_FUNCTION_ROOT,
DEFAULT_MAX_RETRY_TIMES);
}
protected FTPUploader getUploader() {
return new FTPUploader(mojo.getLog());
}
}

Просмотреть файл

@ -0,0 +1,66 @@
/**
* 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.function.handlers.AnnotationHandler;
import com.microsoft.azure.maven.function.handlers.AnnotationHandlerImpl;
import org.apache.maven.plugin.testing.MojoRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.reflections.util.ClasspathHelper;
import java.io.File;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class BuildMojoTest {
@Rule
public MojoRule rule = new MojoRule() {
@Override
protected void before() throws Throwable {
}
@Override
protected void after() {
}
};
@Test
public void doExecute() throws Exception {
final BuildMojo mojo = getMojoFromPom("/pom.xml");
assertNotNull(mojo);
final BuildMojo mojoSpy = spy(mojo);
final AnnotationHandler handler = mock(AnnotationHandler.class);
doReturn(handler).when(mojoSpy).getAnnotationHandler();
doReturn(ClasspathHelper.forPackage("com.microsoft.azure.maven.function.handlers").toArray()[0])
.when(mojoSpy)
.getClassUrl();
doReturn("..\\\\test.jar").when(mojoSpy).getScriptFilePath();
mojoSpy.doExecute();
}
@Test
public void getAnnotationHandler() throws Exception {
final BuildMojo mojo = getMojoFromPom("/pom.xml");
assertNotNull(mojo);
final AnnotationHandler handler = mojo.getAnnotationHandler();
assertNotNull(handler);
assertTrue(handler instanceof AnnotationHandlerImpl);
}
private BuildMojo getMojoFromPom(String filename) throws Exception {
final File pom = new File(BuildMojoTest.class.getResource(filename).toURI());
return (BuildMojo) rule.lookupMojo("build", pom);
}
}

Просмотреть файл

@ -6,6 +6,11 @@
package com.microsoft.azure.maven.function;
import com.microsoft.azure.management.appservice.FunctionApp;
import com.microsoft.azure.maven.function.handlers.ArtifactHandler;
import com.microsoft.azure.maven.function.handlers.FTPArtifactHandlerImpl;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugin.testing.MojoRule;
import org.junit.Rule;
import org.junit.Test;
@ -13,9 +18,12 @@ import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.File;
import java.util.ArrayList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class DeployMojoTest {
@ -39,13 +47,40 @@ public class DeployMojoTest {
assertEquals("appName", mojo.getAppName());
assertEquals("a-function", mojo.getFunctionName());
assertEquals("westeurope", mojo.getRegion());
}
assertEquals("function-maven-plugin", mojo.getPluginName());
@Test
public void doExecute() throws Exception {
final DeployMojo mojo = getMojoFromPom("/pom.xml");
assertNotNull(mojo);
assertEquals(1, mojo.getResources().size());
final DeployMojo mojoSpy = spy(mojo);
doCallRealMethod().when(mojoSpy).getLog();
final ArtifactHandler handler = mock(ArtifactHandler.class);
doReturn(handler).when(mojoSpy).getArtifactHandler();
doCallRealMethod().when(mojoSpy).createFunctionAppIfNotExist();
doCallRealMethod().when(mojoSpy).getAppName();
doReturn("~/target").when(mojoSpy).getBuildDirectoryAbsolutePath();
doCallRealMethod().when(mojoSpy).getResources();
final FunctionApp app = mock(FunctionApp.class);
doReturn(app).when(mojoSpy).getFunctionApp();
mojoSpy.doExecute();
verify(mojoSpy, times(1)).createFunctionAppIfNotExist();
verify(mojoSpy, times(1)).doExecute();
verify(handler, times(1)).publish(anyList());
verifyNoMoreInteractions(handler);
}
@Test
public void getArtifactHandler() throws Exception {
final DeployMojo mojo = getMojoFromPom("/pom.xml");
assertNotNull(mojo);
final ArtifactHandler handler = mojo.getArtifactHandler();
assertNotNull(handler);
assertTrue(handler instanceof FTPArtifactHandlerImpl);
}
private DeployMojo getMojoFromPom(String filename) throws Exception {

Просмотреть файл

@ -0,0 +1,114 @@
/**
* 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.handlers;
import com.microsoft.azure.maven.function.FunctionConfiguration;
import com.microsoft.azure.serverless.functions.annotation.*;
import org.apache.maven.plugin.logging.Log;
import org.junit.Test;
import org.reflections.util.ClasspathHelper;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
public class AnnotationHandlerImplTest {
public static final String HTTP_TRIGGER_FUNCTION = "HttpTriggerFunction";
public static final String HTTP_TRIGGER_METHOD = "httpTriggerMethod";
public static final String QUEUE_TRIGGER_FUNCTION = "QueueTriggerFunction";
public static final String QUEUE_TRIGGER_METHOD = "queueTriggerMethod";
public static final String TIMER_TRIGGER_FUNCTION = "TimerTriggerFunction";
public static final String TIMER_TRIGGER_METHOD = "timerTriggerMethod";
public static final String MULTI_OUTPUT_FUNCTION = "MultiOutputFunction";
public static final String MULTI_OUTPUT_METHOD = "multipleOutput";
public class FunctionEntryPoints {
@FunctionName(HTTP_TRIGGER_FUNCTION)
public String httpTriggerMethod(@HttpTrigger(name = "req") String req) {
return "Hello!";
}
@FunctionName(MULTI_OUTPUT_FUNCTION)
@HttpOutput(name = "$return")
@QueueOutput(name = "$return", queueName = "qOut", connection = "conn")
public String multipleOutput(@HttpTrigger(name = "req") String req) {
return "Hello!";
}
@FunctionName(QUEUE_TRIGGER_FUNCTION)
public void queueTriggerMethod(@QueueTrigger(name = "in", queueName = "qIn", connection = "conn") String in,
@QueueOutput(name = "out", queueName = "qOut", connection = "conn") String out) {
}
@FunctionName(TIMER_TRIGGER_FUNCTION)
public void timerTriggerMethod(@TimerTrigger(name = "timer", schedule = "", useMonitor = false) String timer) {
}
}
@Test
public void findFunctions() throws Exception {
final Log log = mock(Log.class);
final AnnotationHandler handler = new AnnotationHandlerImpl(log);
final Set<Method> functions = handler.findFunctions(getClassUrl());
assertEquals(4, functions.size());
final List<String> methodNames = functions.stream().map(f -> f.getName()).collect(Collectors.toList());
assertTrue(methodNames.contains(HTTP_TRIGGER_METHOD));
assertTrue(methodNames.contains(QUEUE_TRIGGER_METHOD));
assertTrue(methodNames.contains(TIMER_TRIGGER_METHOD));
assertTrue(methodNames.contains(MULTI_OUTPUT_METHOD));
}
@Test
public void generateConfigurations() throws Exception {
final Log log = mock(Log.class);
final AnnotationHandler handler = new AnnotationHandlerImpl(log);
final Set<Method> functions = handler.findFunctions(getClassUrl());
final Map<String, FunctionConfiguration> configMap = handler.generateConfigurations(functions);
configMap.values().forEach(config -> config.validate());
assertEquals(4, configMap.size());
assertTrue(configMap.containsKey(HTTP_TRIGGER_FUNCTION));
final FunctionConfiguration httpTriggerFunctionConfig = configMap.get(HTTP_TRIGGER_FUNCTION);
assertEquals(getFullyQualifiedMethodName(HTTP_TRIGGER_METHOD), httpTriggerFunctionConfig.getEntryPoint());
assertFalse(httpTriggerFunctionConfig.isDisabled());
assertEquals(2, httpTriggerFunctionConfig.getBindings().size());
assertTrue(configMap.containsKey(QUEUE_TRIGGER_FUNCTION));
final FunctionConfiguration queueTriggerFunctionConfig = configMap.get(QUEUE_TRIGGER_FUNCTION);
assertEquals(getFullyQualifiedMethodName(QUEUE_TRIGGER_METHOD), queueTriggerFunctionConfig.getEntryPoint());
assertEquals(2, queueTriggerFunctionConfig.getBindings().size());
assertTrue(configMap.containsKey(TIMER_TRIGGER_FUNCTION));
final FunctionConfiguration timerTriggerFunctionConfig = configMap.get(TIMER_TRIGGER_FUNCTION);
assertEquals(getFullyQualifiedMethodName(TIMER_TRIGGER_METHOD), timerTriggerFunctionConfig.getEntryPoint());
assertEquals(1, timerTriggerFunctionConfig.getBindings().size());
assertTrue(configMap.containsKey(MULTI_OUTPUT_FUNCTION));
final FunctionConfiguration multiOutputFunctionConfig = configMap.get(MULTI_OUTPUT_FUNCTION);
assertEquals(getFullyQualifiedMethodName(MULTI_OUTPUT_METHOD), multiOutputFunctionConfig.getEntryPoint());
assertFalse(multiOutputFunctionConfig.isDisabled());
assertEquals(3, multiOutputFunctionConfig.getBindings().size());
}
private URL getClassUrl() {
return ClasspathHelper.forPackage("com.microsoft.azure.maven.function.handlers")
.iterator()
.next();
}
private String getFullyQualifiedMethodName(final String methodName) {
return FunctionEntryPoints.class.getCanonicalName() + "." + methodName;
}
}

Просмотреть файл

@ -0,0 +1,61 @@
/**
* 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.handlers;
import com.microsoft.azure.management.appservice.FunctionApp;
import com.microsoft.azure.management.appservice.PublishingProfile;
import com.microsoft.azure.maven.FTPUploader;
import com.microsoft.azure.maven.function.DeployMojo;
import org.junit.Test;
import static org.mockito.Mockito.*;
public class FTPArtifactHandlerImplTest {
@Test
public void publish() throws Exception {
final DeployMojo mojo = mock(DeployMojo.class);
final FTPArtifactHandlerImpl handler = new FTPArtifactHandlerImpl(mojo);
final FTPArtifactHandlerImpl handlerSpy = spy(handler);
doNothing().when(handlerSpy).copyResourcesToStageDirectory(null);
doNothing().when(handlerSpy).uploadDirectoryToFTP();
handlerSpy.publish(null);
verify(handlerSpy, times(1)).copyResourcesToStageDirectory(isNull());
verify(handlerSpy, times(1)).uploadDirectoryToFTP();
verify(handlerSpy, times(1)).publish(isNull());
verifyNoMoreInteractions(handlerSpy);
}
@Test
public void uploadDirectoryToFTP() throws Exception {
final String ftpUrl = "ftp.azurewebsites.net/site/wwwroot";
final PublishingProfile profile = mock(PublishingProfile.class);
when(profile.ftpUrl()).thenReturn(ftpUrl);
final FunctionApp app = mock(FunctionApp.class);
when(app.getPublishingProfile()).thenReturn(profile);
final DeployMojo mojo = mock(DeployMojo.class);
when(mojo.getFunctionApp()).thenReturn(app);
final FTPUploader uploader = mock(FTPUploader.class);
final FTPArtifactHandlerImpl handler = new FTPArtifactHandlerImpl(mojo);
final FTPArtifactHandlerImpl handlerSpy = spy(handler);
doReturn(uploader).when(handlerSpy).getUploader();
handlerSpy.uploadDirectoryToFTP();
verify(mojo, times(1)).getFunctionApp();
verify(mojo, times(1)).getDeploymentStageDirectory();
verifyNoMoreInteractions(mojo);
verify(app, times(1)).getPublishingProfile();
verifyNoMoreInteractions(app);
verify(profile, times(1)).ftpUrl();
verify(profile, times(1)).ftpUsername();
verify(profile, times(1)).ftpPassword();
verifyNoMoreInteractions(profile);
verify(uploader, times(1))
.uploadDirectoryWithRetries(anyString(), isNull(), isNull(), isNull(), anyString(), anyInt());
verifyNoMoreInteractions(uploader);
}
}

Просмотреть файл

@ -15,17 +15,7 @@
</authentication>
<resourceGroup>resourceGroupName</resourceGroup>
<appName>appName</appName>
<functionName>a-function</functionName>
<region>westeurope</region>
<resources>
<resource>
<directory>${baseDir}/target</directory>
<targetPath>/</targetPath>
<includes>
<include>*.jar</include>
</includes>
</resource>
</resources>
</configuration>
</plugin>
</plugins>

Просмотреть файл

@ -6,7 +6,7 @@
<groupId>com.microsoft.azure</groupId>
<artifactId>maven-plugin-lib</artifactId>
<version>0.1.0</version>
<version>0.1.1</version>
<packaging>jar</packaging>
<name>Azure Maven plugin library</name>
<description>Common library for Azure Maven Plugins</description>
@ -162,6 +162,7 @@
<check/>
<instrumentation>
<excludes>
<exclude>**/AuthenticationSetting.class</exclude>
<exclude>**/telemetry/**.class</exclude>
</excludes>
</instrumentation>

Просмотреть файл

@ -14,12 +14,15 @@ import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.Settings;
import org.apache.maven.shared.filtering.MavenResourcesFiltering;
import org.codehaus.plexus.util.StringUtils;
import java.util.HashMap;
import java.util.UUID;
import static com.microsoft.azure.maven.telemetry.AppInsightsProxy.*;
@ -30,6 +33,7 @@ import static com.microsoft.azure.maven.telemetry.AppInsightsProxy.*;
public abstract class AbstractAzureMojo extends AbstractMojo
implements TelemetryConfiguration, AuthConfiguration {
public static final String AZURE_INIT_FAIL = "Failed to initialize Azure client object.";
public static final String FAILURE_REASON = "failureReason";
@Parameter(defaultValue = "${project}", readonly = true, required = true)
protected MavenProject project;
@ -37,6 +41,9 @@ public abstract class AbstractAzureMojo extends AbstractMojo
@Parameter(defaultValue = "${session}", readonly = true, required = true)
protected MavenSession session;
@Parameter( defaultValue = "${plugin}", readonly = true, required = true )
private PluginDescriptor plugin;
/**
* The system settings for Maven. This is the instance resulting from
* merging global and user-level settings files.
@ -67,10 +74,6 @@ public abstract class AbstractAzureMojo extends AbstractMojo
private String installationId = GetHashMac.getHashMac();
private String pluginName = Utils.getValueFromPluginDescriptor("artifactId");
private String pluginVersion = Utils.getValueFromPluginDescriptor("version");
public MavenProject getProject() {
return project;
}
@ -112,11 +115,11 @@ public abstract class AbstractAzureMojo extends AbstractMojo
}
public String getPluginName() {
return pluginName;
return plugin.getArtifactId();
}
public String getPluginVersion() {
return pluginVersion;
return plugin.getVersion();
}
public String getUserAgent() {
@ -154,12 +157,58 @@ public abstract class AbstractAzureMojo extends AbstractMojo
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (getAzureClient() == null) {
getTelemetryProxy().trackEvent(TelemetryEvent.INIT_FAILURE);
throw new MojoExecutionException(AZURE_INIT_FAIL);
try {
if (getAzureClient() == null) {
getTelemetryProxy().trackEvent(TelemetryEvent.INIT_FAILURE);
throw new MojoExecutionException(AZURE_INIT_FAIL);
} else {
// Repopulate subscriptionId in case it is not configured.
getTelemetryProxy().addDefaultProperty(SUBSCRIPTION_ID_KEY, getAzureClient().subscriptionId());
}
trackMojoStart();
doExecute();
trackMojoSuccess();
} catch (Exception e) {
processException(e);
}
}
/**
* Sub-class should implement this method to do real work.
*
* @throws Exception
*/
protected abstract void doExecute() throws Exception;
protected void trackMojoStart() {
getTelemetryProxy().trackEvent(this.getClass().getSimpleName() + ".start");
}
protected void trackMojoSuccess() {
getTelemetryProxy().trackEvent(this.getClass().getSimpleName() + ".success");
}
protected void trackMojoFailure(final String message) {
final HashMap<String, String> failureReason = new HashMap<>();
failureReason.put(FAILURE_REASON, message);
getTelemetryProxy().trackEvent(this.getClass().getSimpleName() + ".failure");
}
protected void processException(final Exception exception) throws MojoExecutionException {
final String message = exception.getMessage();
if (StringUtils.isEmpty(message)) {
trackMojoFailure(exception.toString());
} else {
// Repopulate subscriptionId in case it is not configured.
getTelemetryProxy().addDefaultProperty(SUBSCRIPTION_ID_KEY, azure.subscriptionId());
trackMojoFailure(message);
}
if (isFailingOnError()) {
throw new MojoExecutionException(message, exception);
} else {
getLog().error(message);
}
}
}

Просмотреть файл

@ -70,24 +70,6 @@ public final class Utils {
return node.getValue();
}
/**
* Get string value from plugin descriptor file, namely /META-INF/maven/pugin.xml .
*
* @param tagName Valid tagName in /META-INF/maven/plugin.xml, such as "artifactId", "version" etc..
* @return String value of target property.
*/
public static String getValueFromPluginDescriptor(final String tagName) {
try (final InputStream is = Utils.class.getResourceAsStream("/META-INF/maven/plugin.xml")) {
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
final DocumentBuilder builder = factory.newDocumentBuilder();
final Document doc = builder.parse(is);
doc.getDocumentElement().normalize();
return doc.getElementsByTagName(tagName).item(0).getTextContent();
} catch (Exception e) {
}
return null;
}
/**
* Copy resources to target directory using Maven resource filtering so that we don't have to handle
* recursive directory listing and pattern matching.

Просмотреть файл

@ -7,7 +7,10 @@
package com.microsoft.azure.maven;
import com.microsoft.azure.management.Azure;
import com.microsoft.azure.maven.telemetry.TelemetryProxy;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.settings.Settings;
import org.codehaus.plexus.util.StringUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -18,42 +21,83 @@ import org.mockito.junit.MockitoJUnitRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class AbstractAzureMojoTest {
public static final String PLUGIN_NAME = "maven-plugin-lib";
public static final String PLUGIN_VERSION = "0.1.0-SNAPSHOT";
@Mock
Settings settings;
@Mock
PluginDescriptor plugin;
@Mock
Azure azure;
@Mock
TelemetryProxy telemetryProxy;
@InjectMocks
private AbstractAzureMojo mojo = new AbstractAzureMojo() {
public String getPluginName() {
return "maven-plugin-lib";
public boolean isFailingOnError() {
return true;
}
public String getPluginVersion() {
return "0.1.0-SNAPSHOT";
@Override
protected void doExecute() throws Exception {
}
};
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(plugin.getArtifactId()).thenReturn(PLUGIN_NAME);
when(plugin.getVersion()).thenReturn(PLUGIN_VERSION);
}
@Test
public void testGetAzureClient() throws Exception {
public void getAzureClient() throws Exception {
assertEquals(azure, mojo.getAzureClient());
}
@Test
public void testGetMavenSettings() {
public void getMavenSettings() {
assertEquals(settings, mojo.getSettings());
}
@Test
public void testInitTelemetry() {
assertNotNull(mojo.getTelemetryProxy());
public void getTelemetryProxy() {
assertEquals(telemetryProxy, mojo.getTelemetryProxy());
}
@Test
public void getUserAgent() {
final String userAgent = mojo.getUserAgent();
assertTrue(StringUtils.contains(userAgent, PLUGIN_NAME));
assertTrue(StringUtils.contains(userAgent, PLUGIN_VERSION));
assertTrue(StringUtils.contains(userAgent, mojo.getInstallationId()));
assertTrue(StringUtils.contains(userAgent, mojo.getSessionId()));
}
@Test
public void execute() throws Exception {
when(azure.subscriptionId()).thenReturn("fake-subscription-id");
mojo.execute();
}
@Test
public void processException() throws Exception {
final String message = "test exception message";
String actualMessage = null;
try {
mojo.processException(new Exception(message));
} catch (Exception e) {
actualMessage = e.getMessage();
}
assertEquals(message, actualMessage);
}
}

Просмотреть файл

@ -4,7 +4,7 @@
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-maven-plugins</artifactId>
<version>0.1.0</version>
<version>0.1.1</version>
<packaging>pom</packaging>
<name>Azure Maven Plugins</name>
<description>Maven plugins for Microsoft Azure services</description>

Просмотреть файл

@ -6,7 +6,7 @@
<groupId>com.microsoft.azure</groupId>
<artifactId>webapp-maven-plugin</artifactId>
<version>0.1.0</version>
<version>0.1.1</version>
<packaging>maven-plugin</packaging>
<name>Azure Web Apps Maven Plugin</name>
<description>Maven Plugin for Azure Web Apps</description>
@ -21,6 +21,7 @@
<codehaus.plexus-utils.version>3.0.20</codehaus.plexus-utils.version>
<azure.version>1.1.2</azure.version>
<azure.ai.version>1.0.8</azure.ai.version>
<azure.maven-lib.version>0.1.1</azure.maven-lib.version>
<junit.version>4.12</junit.version>
<mockito.version>2.4.0</mockito.version>
<spring-test.version>4.1.6.RELEASE</spring-test.version>
@ -129,7 +130,7 @@
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>maven-plugin-lib</artifactId>
<version>0.1.0</version>
<version>${azure.maven-lib.version}</version>
</dependency>
<!-- TEST -->

Просмотреть файл

@ -24,27 +24,19 @@ public class DeployMojo extends AbstractWebAppMojo {
public static final String WEBAPP_DEPLOY_START = "Start deploying to Web App ";
public static final String WEBAPP_DEPLOY_SUCCESS = "Successfully deployed to Web App ";
public static final String WEBAPP_DEPLOY_FAILURE = "Failed to deploy to Web App ";
public static final String FAILURE_REASON = "failureReason";
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
super.execute();
protected void doExecute() throws Exception {
getLog().info(WEBAPP_DEPLOY_START + getAppName() + APOSTROPHE);
try {
logDeployStart();
final DeployFacade facade = getDeployFacade();
facade.setupRuntime()
.applySettings()
.commitChanges();
final DeployFacade facade = getDeployFacade();
facade.setupRuntime()
.applySettings()
.commitChanges();
facade.deployArtifacts();
facade.deployArtifacts();
logDeploySuccess();
} catch (Exception e) {
processException(e);
}
getLog().info(WEBAPP_DEPLOY_SUCCESS + getAppName());
}
protected DeployFacade getDeployFacade() {
@ -52,36 +44,4 @@ public class DeployMojo extends AbstractWebAppMojo {
new DeployFacadeImplWithCreate(this) :
new DeployFacadeImplWithUpdate(this);
}
private void processException(final Exception exception) throws MojoExecutionException {
String message = exception.getMessage();
if (StringUtils.isEmpty(message)) {
message = exception.toString();
}
logDeployFailure(message);
if (isFailingOnError()) {
throw new MojoExecutionException(message, exception);
} else {
getLog().error(message);
}
}
private void logDeployStart() {
getTelemetryProxy().trackEvent(TelemetryEvent.DEPLOY_START);
getLog().info(WEBAPP_DEPLOY_START + getAppName() + APOSTROPHE);
}
private void logDeploySuccess() {
getTelemetryProxy().trackEvent(TelemetryEvent.DEPLOY_SUCCESS);
getLog().info(WEBAPP_DEPLOY_SUCCESS + getAppName());
}
private void logDeployFailure(final String message) {
final HashMap<String, String> failureReason = new HashMap<>();
failureReason.put(FAILURE_REASON, message);
getTelemetryProxy().trackEvent(TelemetryEvent.DEPLOY_FAILURE, failureReason);
getLog().error(WEBAPP_DEPLOY_FAILURE + getAppName());
}
}

Просмотреть файл

@ -39,12 +39,20 @@ public class FTPArtifactHandlerImpl implements ArtifactHandler {
}
protected void uploadDirectoryToFTP() throws MojoExecutionException {
final FTPUploader uploader = new FTPUploader(mojo.getLog());
final FTPUploader uploader = getUploader();
final WebApp app = mojo.getWebApp();
final PublishingProfile profile = app.getPublishingProfile();
final String serverUrl = profile.ftpUrl().split("/", 2)[0];
uploader.uploadDirectoryWithRetries(serverUrl, profile.ftpUsername(), profile.ftpPassword(),
mojo.getDeploymentStageDirectory(), DEFAULT_WEBAPP_ROOT, DEFAULT_MAX_RETRY_TIMES);
uploader.uploadDirectoryWithRetries(serverUrl,
profile.ftpUsername(),
profile.ftpPassword(),
mojo.getDeploymentStageDirectory(),
DEFAULT_WEBAPP_ROOT,
DEFAULT_MAX_RETRY_TIMES);
}
protected FTPUploader getUploader() {
return new FTPUploader(mojo.getLog());
}
}

Просмотреть файл

@ -11,7 +11,9 @@ import com.microsoft.azure.management.appservice.JavaVersion;
import com.microsoft.azure.management.appservice.PricingTier;
import com.microsoft.azure.management.appservice.WebApp;
import com.microsoft.azure.management.appservice.WebContainer;
import com.microsoft.azure.maven.telemetry.TelemetryProxy;
import com.microsoft.azure.maven.webapp.configuration.DeploymentType;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.testing.MojoRule;
import org.junit.Before;
import org.junit.Rule;
@ -57,8 +59,6 @@ public class DeployMojoTest {
assertEquals(PricingTier.STANDARD_S1, mojo.getPricingTier());
assertEquals("webapp-maven-plugin", mojo.getPluginName());
assertEquals(null, mojo.getJavaVersion());
assertEquals(null, mojo.getJavaWebContainer());
@ -109,22 +109,44 @@ public class DeployMojoTest {
}
@Test
public void testExecute() throws Exception {
public void testDoExecute() throws Exception {
final DeployMojo mojo = getMojoFromPom("/pom-linux.xml");
assertNotNull(mojo);
final DeployMojo mojoSpy = spy(mojo);
final DeployFacade facade = mock(DeployFacade.class);
final DeployFacade facade = getDeployFacade();
doReturn(facade).when(mojoSpy).getDeployFacade();
final Azure azure = mock(Azure.class);
when(azure.subscriptionId()).thenReturn("subscriptionId");
ReflectionTestUtils.setField(mojoSpy, "azure", azure);
doCallRealMethod().when(mojoSpy).getLog();
mojoSpy.execute();
mojoSpy.doExecute();
}
private DeployMojo getMojoFromPom(String filename) throws Exception {
final File pom = new File(DeployMojoTest.class.getResource(filename).toURI());
return (DeployMojo) rule.lookupMojo("deploy", pom);
}
private DeployFacade getDeployFacade() {
return new DeployFacade() {
@Override
public DeployFacade setupRuntime() throws MojoExecutionException {
return this;
}
@Override
public DeployFacade applySettings() throws MojoExecutionException {
return this;
}
@Override
public DeployFacade commitChanges() throws MojoExecutionException {
return this;
}
@Override
public DeployFacade deployArtifacts() throws Exception {
return this;
}
};
}
}

Просмотреть файл

@ -6,7 +6,11 @@
package com.microsoft.azure.maven.webapp.handlers;
import com.microsoft.azure.management.appservice.PublishingProfile;
import com.microsoft.azure.management.appservice.WebApp;
import com.microsoft.azure.maven.FTPUploader;
import com.microsoft.azure.maven.webapp.AbstractWebAppMojo;
import com.microsoft.azure.maven.webapp.DeployMojo;
import org.apache.maven.model.Resource;
import org.junit.Before;
import org.junit.Test;
@ -51,5 +55,31 @@ public class FTPArtifactHandlerImplTest {
@Test
public void uploadDirectoryToFTP() throws Exception {
final String FTP_URL = "ftp.azurewebsites.net/site/wwwroot";
final PublishingProfile profile = mock(PublishingProfile.class);
when(profile.ftpUrl()).thenReturn(FTP_URL);
final WebApp app = mock(WebApp.class);
when(app.getPublishingProfile()).thenReturn(profile);
final DeployMojo mojo = mock(DeployMojo.class);
when(mojo.getWebApp()).thenReturn(app);
final FTPUploader uploader = mock(FTPUploader.class);
final FTPArtifactHandlerImpl handler = new FTPArtifactHandlerImpl(mojo);
final FTPArtifactHandlerImpl handlerSpy = spy(handler);
doReturn(uploader).when(handlerSpy).getUploader();
handlerSpy.uploadDirectoryToFTP();
verify(mojo, times(1)).getWebApp();
verify(mojo, times(1)).getDeploymentStageDirectory();
verifyNoMoreInteractions(mojo);
verify(app, times(1)).getPublishingProfile();
verifyNoMoreInteractions(app);
verify(profile, times(1)).ftpUrl();
verify(profile, times(1)).ftpUsername();
verify(profile, times(1)).ftpPassword();
verifyNoMoreInteractions(profile);
verify(uploader, times(1))
.uploadDirectoryWithRetries(anyString(), (String) isNull(), (String) isNull(), (String) isNull(),
anyString(), anyInt());
verifyNoMoreInteractions(uploader);
}
}