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 94323dab0..3ed0a8a71 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 @@ -61,6 +61,8 @@ public abstract class AbstractFunctionMojo extends AbstractAppServiceMojo { private static final String FUNCTION_REGION_KEY = "region"; private static final String FUNCTION_PRICING_KEY = "pricingTier"; private static final String FUNCTION_DEPLOY_TO_SLOT_KEY = "isDeployToFunctionSlot"; + private static final String FUNCTION_NATIVE_EXECUTABLE_PATH = "nativeExecutablePath"; + private static final String FUNCTION_IS_NATIVE = "isNativeFunction"; //region Properties @Parameter(defaultValue = "${project.build.finalName}", readonly = true, required = true) @@ -214,6 +216,24 @@ public abstract class AbstractFunctionMojo extends AbstractAppServiceMojo { @Parameter protected String storageAccountResourceGroup; + /** + * Boolean flag to run function with custom runtime instead of java + * + * @since 1.27.0 + */ + @Getter + @Parameter(property = "functions.nativeExecutablePath") + protected String nativeExecutablePath; + + /** + * Args to be used by custom handler. + * + * @since 1.27.0 + */ + @Getter + @Parameter(property = "functions.customHandlerArgs", defaultValue = "") + protected String customHandlerArgs; + @Getter protected final ConfigParser parser = new ConfigParser(this); @@ -271,6 +291,10 @@ protected File getArtifact() throws AzureToolkitRuntimeException { return result; } + protected boolean isNativeExecutable() { + return StringUtils.isNotEmpty(getNativeExecutablePath()); + } + protected File getHostJsonFile() { final Path path = Paths.get(getHostJson()); return path.isAbsolute() ? path.toFile() : @@ -355,6 +379,7 @@ public Map getTelemetryProperties() { result.put(DISABLE_APP_INSIGHTS_KEY, String.valueOf(isDisableAppInsights())); final boolean isDeployToFunctionSlot = getDeploymentSlotSetting() != null && StringUtils.isNotEmpty(getDeploymentSlotSetting().getName()); result.put(FUNCTION_DEPLOY_TO_SLOT_KEY, String.valueOf(isDeployToFunctionSlot)); + result.put(FUNCTION_IS_NATIVE, String.valueOf(isNativeExecutable())); return result; } //endregion 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 index a8324cf20..0592d04aa 100644 --- 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 @@ -38,6 +38,8 @@ public FunctionAppConfig parseConfig() throws AzureExecutionException { .appInsightsInstance(mojo.getAppInsightsInstance()) .storageAccountName(mojo.getStorageAccountName()) .storageAccountResourceGroup(StringUtils.firstNonBlank(mojo.getStorageAccountResourceGroup(), mojo.getResourceGroup())) + .nativeExecutablePath(mojo.getNativeExecutablePath()) + .customHandlerArgs(mojo.getCustomHandlerArgs()) .subscriptionId(mojo.getSubscriptionId()) .resourceGroup(mojo.getResourceGroup()) .appName(mojo.getAppName()) diff --git a/azure-functions-maven-plugin/src/main/java/com/microsoft/azure/maven/function/PackageMojo.java b/azure-functions-maven-plugin/src/main/java/com/microsoft/azure/maven/function/PackageMojo.java index c048eb856..63a0a4bcb 100644 --- a/azure-functions-maven-plugin/src/main/java/com/microsoft/azure/maven/function/PackageMojo.java +++ b/azure-functions-maven-plugin/src/main/java/com/microsoft/azure/maven/function/PackageMojo.java @@ -29,6 +29,7 @@ import com.microsoft.azure.toolkit.lib.legacy.function.handlers.CommandHandlerImpl; import com.microsoft.azure.toolkit.lib.legacy.function.handlers.FunctionCoreToolsHandler; import com.microsoft.azure.toolkit.lib.legacy.function.handlers.FunctionCoreToolsHandlerImpl; + import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.SetUtils; import org.apache.commons.io.FileUtils; @@ -61,6 +62,7 @@ import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Paths; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -95,6 +97,7 @@ public class PackageMojo extends AbstractFunctionMojo { public static final String SAVE_FUNCTION_JSON = "Starting processing function: "; public static final String SAVE_SUCCESS = "Successfully saved to "; public static final String COPY_JARS = "Step 7 of 8: Copying JARs to staging directory "; + public static final String COPY_BINARY = "Step 7 of 8: Copying Binary to staging directory "; public static final String COPY_SUCCESS = "Copied successfully."; public static final String INSTALL_EXTENSIONS = "Step 8 of 8: Installing function extensions if needed"; public static final String SKIP_INSTALL_EXTENSIONS_HTTP = "Skip install Function extension for HTTP Trigger Functions"; @@ -104,10 +107,12 @@ public class PackageMojo extends AbstractFunctionMojo { public static final String FUNCTION_JSON = "function.json"; public static final String EXTENSION_BUNDLE = "extensionBundle"; private static final String AZURE_FUNCTIONS_JAVA_CORE_LIBRARY = "azure-functions-java-core-library"; - private static final String DEFAULT_LOCAL_SETTINGS_JSON = "{ \"IsEncrypted\": false, \"Values\": " + - "{ \"FUNCTIONS_WORKER_RUNTIME\": \"java\" } }"; + private static final String DEFAULT_LOCAL_SETTINGS_JSON = "'{' \"IsEncrypted\": false, \"Values\": " + + "'{' \"FUNCTIONS_WORKER_RUNTIME\": \"{0}\" '}' '}'"; private static final String DEFAULT_HOST_JSON = "{\"version\":\"2.0\",\"extensionBundle\":" + "{\"id\":\"Microsoft.Azure.Functions.ExtensionBundle\",\"version\":\"[4.*, 5.0.0)\"}}\n"; + private static final String DEFAULT_CUSTOM_HOST_JSON = "'{'\"version\":\"2.0\",\"extensionBundle\":" + + "'{'\"id\":\"Microsoft.Azure.Functions.ExtensionBundle\",\"version\":\"[4.*, 5.0.0)\"'}',\"customHandler\": '{'\"description\": '{'\"defaultExecutablePath\": \"{0}\",\"workingDirectory\": \"\",\"arguments\": [\"{1}\"]'}',\"enableForwardingHttpRequest\": true'}}'\n"; private static final BindingEnum[] FUNCTION_WITHOUT_FUNCTION_EXTENSION = {BindingEnum.HttpOutput, BindingEnum.HttpTrigger}; private static final String EXTENSION_BUNDLE_ID = "Microsoft.Azure.Functions.ExtensionBundle"; @@ -170,7 +175,12 @@ protected void doExecute() throws AzureExecutionException { writeFunctionJsonFiles(objectWriter, configMap); - copyJarsToStageDirectory(); + if (isNativeExecutable()) { + copyBinaryToStageDirectory(); + } else { + copyJarsToStageDirectory(); + } + } catch (IOException | MojoExecutionException e) { throw new AzureExecutionException("Cannot perform IO operations due to error:" + e.getMessage(), e); } @@ -184,6 +194,21 @@ protected void doExecute() throws AzureExecutionException { log.info(BUILD_SUCCESS); } + protected void copyBinaryToStageDirectory() throws IOException, MojoExecutionException { + final File stagingDirectory = new File(getDeploymentStagingDirectoryPath()); + log.info(""); + log.info(COPY_BINARY + stagingDirectory.getAbsolutePath()); + final File originalArtifact = new File(getArtifact().getParentFile(), nativeExecutablePath); + + File destFile = new File(stagingDirectory, nativeExecutablePath); + log.info("src: {}", originalArtifact.getAbsolutePath()); + log.info("dest: {}", destFile.getAbsolutePath()); + + FileUtils.copyFile(originalArtifact, destFile); + + log.info(COPY_SUCCESS); + } + public static void buildArtifactWithDependencies(@Nonnull final File artifactFile, @Nullable final Set dependencies, final File target) { AzureMessager.getMessager().info("Building artifact with dependencies..."); final Shader shader = new DefaultShader(); @@ -316,6 +341,7 @@ protected void writeFunctionJsonFiles(final ObjectWriter objectWriter, final Map configMap) throws IOException { log.info(""); log.info(SAVE_FUNCTION_JSONS); + log.info("Native: "+isNativeExecutable()); if (configMap.size() == 0) { log.info(SAVE_SKIP); } else { @@ -330,6 +356,11 @@ protected void writeFunctionJsonFile(final ObjectWriter objectWriter, final Stri log.info(SAVE_FUNCTION_JSON + functionName); final File functionJsonFile = Paths.get(getDeploymentStagingDirectoryPath(), functionName, FUNCTION_JSON).toFile(); + + if (isNativeExecutable()) { + config.setEntryPoint(null); + config.setScriptFile(null); + } writeObjectToFile(objectWriter, config, functionJsonFile); log.info(SAVE_SUCCESS + functionJsonFile.getAbsolutePath()); } @@ -339,7 +370,14 @@ protected void copyHostJson() throws IOException { log.info(SAVING_HOST_JSON); final File sourceHostJsonFile = getHostJsonFile(); final File destHostJsonFile = Paths.get(getDeploymentStagingDirectoryPath(), HOST_JSON).toFile(); - copyFilesWithDefaultContent(sourceHostJsonFile, destHostJsonFile, DEFAULT_HOST_JSON); + if (isNativeExecutable()) { + File nativeFile = new File(getNativeExecutablePath()); + String newDefaultContent = MessageFormat.format(DEFAULT_CUSTOM_HOST_JSON, nativeFile.getName(), getCustomHandlerArgs()); + copyFilesWithDefaultContent(sourceHostJsonFile, destHostJsonFile, newDefaultContent); + } else { + copyFilesWithDefaultContent(sourceHostJsonFile, destHostJsonFile, DEFAULT_HOST_JSON); + } + log.info(SAVE_SUCCESS + destHostJsonFile.getAbsolutePath()); } @@ -348,10 +386,15 @@ protected void copyLocalSettingsJson() throws IOException { log.info(SAVING_LOCAL_SETTINGS_JSON); final File sourceLocalSettingsJsonFile = getLocalSettingsJsonFile(); final File destLocalSettingsJsonFile = Paths.get(getDeploymentStagingDirectoryPath(), LOCAL_SETTINGS_JSON).toFile(); - copyFilesWithDefaultContent(sourceLocalSettingsJsonFile, destLocalSettingsJsonFile, DEFAULT_LOCAL_SETTINGS_JSON); + String defaultContent = MessageFormat.format(DEFAULT_LOCAL_SETTINGS_JSON, getWorkerRuntime()); + copyFilesWithDefaultContent(sourceLocalSettingsJsonFile, destLocalSettingsJsonFile, defaultContent); log.info(SAVE_SUCCESS + destLocalSettingsJsonFile.getAbsolutePath()); } + private String getWorkerRuntime() { + return isNativeExecutable()? "custom": "java"; + } + private static void copyFilesWithDefaultContent(File source, File dest, String defaultContent) throws IOException { if (source != null && source.exists()) { 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 index 7c5dd67a7..9eb6574dd 100644 --- 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 @@ -19,6 +19,8 @@ public class FunctionAppConfig extends AppServiceConfig { private boolean disableAppInsights; private String storageAccountName; private String storageAccountResourceGroup; + private String nativeExecutablePath; + private String customHandlerArgs; private LogAnalyticsWorkspaceConfig workspaceConfig; private FlexConsumptionConfiguration flexConsumptionConfiguration; }