diff --git a/azure-functions-maven-plugin/README.md b/azure-functions-maven-plugin/README.md index 74e142a0ce..1f0e4f7f3a 100644 --- a/azure-functions-maven-plugin/README.md +++ b/azure-functions-maven-plugin/README.md @@ -28,10 +28,6 @@ Maven | 3.0 and above ## Goals -#### `azure-functions:add` -- Create new Java function and add to current project. -- You will be prompted to choose template and enter parameters. - #### `azure-functions:package` - Scan the output directory (default is `${project.basedir}/target/classes`) and generating `function.json` for each function (method annotated with `FunctionName`) in the staging directory. - Copy JAR files from the build directory (default is `${project.basedir}/target/`) to the staging directory. @@ -39,6 +35,14 @@ Maven | 3.0 and above >NOTE: >Default staging directory is `${project.basedir}/target/azure-functions/${function-app-name}/` +#### `azure-functions:add` +- Create new Java function and add to current project. +- You will be prompted to choose template and enter parameters. Templates for below triggers are supported as of now: + - HTTP Trigger + - Azure Storage Blob Trigger + - Azure Storage Queue Trigger + - Timer Trigger + #### `azure-functions:run` - Invoke Azure Functions Local Emulator to run all functions. Default working directory is the staging directory. - Use property `-Dfunctions.target=myFunction` to run a single function named `myFunction` @@ -150,8 +154,8 @@ You don't have to provide all properties on command line. Missing properties wil ### Generate `function.json` from current project Follow below instructions, you don't need to handwrite `function.json` any more. -1. Use annotations from package `com.microsoft.azure.serverless:azure-functions-java-core` to decorate your functions. -2. Run `mvn package azure-functions:package`; then `function.json` files will be automatically generated for all functions in your project. +1. Use annotations from package `com.microsoft.azure:azure-functions-java-core` to decorate your functions. +2. Run `mvn clean package azure-functions:package`; then `function.json` files will be automatically generated for all functions in your project. ### Run Azure Functions locally diff --git a/azure-functions-maven-plugin/pom.xml b/azure-functions-maven-plugin/pom.xml index df1e0185e9..59edb65963 100644 --- a/azure-functions-maven-plugin/pom.xml +++ b/azure-functions-maven-plugin/pom.xml @@ -15,6 +15,7 @@ UTF-8 UTF-8 + yyyyMMddHHmmssSSS 1.8 1.8 3.3.3 @@ -108,8 +109,15 @@ invoker.properties clean - function:deploy + package + azure-functions:deploy + + ${maven.build.timestamp} + + + ${maven.build.timestamp} + diff --git a/azure-functions-maven-plugin/src/it/http-trigger/.gitignore b/azure-functions-maven-plugin/src/it/http-trigger/.gitignore new file mode 100644 index 0000000000..3e94b7478c --- /dev/null +++ b/azure-functions-maven-plugin/src/it/http-trigger/.gitignore @@ -0,0 +1,30 @@ +# Build output +target/ +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# IDE +.idea/ +*.iml + +# macOS +.DS_Store diff --git a/azure-functions-maven-plugin/src/it/http-trigger/cleanup.groovy b/azure-functions-maven-plugin/src/it/http-trigger/cleanup.groovy new file mode 100644 index 0000000000..d8ca99c3c4 --- /dev/null +++ b/azure-functions-maven-plugin/src/it/http-trigger/cleanup.groovy @@ -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. + */ + +// Verify Azure Functions +def url = "https://maven-functions-it-${timestamp}.azurewebsites.net/api/hello?name=Azure".toURL() +try { + url.getText() // warm up +} catch (Exception e) { + // ignore warm-up exception +} +def response = url.getText() +assert response == "Hello, Azure" + +// Clean up resources created in test +def clientId = System.getenv("CLIENT_ID") +def tenantId = System.getenv("TENANT_ID") +def key = System.getenv("KEY") +def command = """ + az login --service-principal -u ${clientId} -p ${key} --tenant ${tenantId} + az group delete -y -n maven-functions-it-rg-1 --no-wait + az logout +""" +def process = ["bash", "-c", command].execute() +println process.text diff --git a/azure-functions-maven-plugin/src/it/http-trigger/host.json b/azure-functions-maven-plugin/src/it/http-trigger/host.json new file mode 100644 index 0000000000..2c63c08510 --- /dev/null +++ b/azure-functions-maven-plugin/src/it/http-trigger/host.json @@ -0,0 +1,2 @@ +{ +} diff --git a/azure-functions-maven-plugin/src/it/http-trigger/local.settings.json b/azure-functions-maven-plugin/src/it/http-trigger/local.settings.json new file mode 100644 index 0000000000..1d9e4b7806 --- /dev/null +++ b/azure-functions-maven-plugin/src/it/http-trigger/local.settings.json @@ -0,0 +1,7 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "", + "AzureWebJobsDashboard": "" + } +} diff --git a/azure-functions-maven-plugin/src/it/http-trigger/pom.xml b/azure-functions-maven-plugin/src/it/http-trigger/pom.xml new file mode 100644 index 0000000000..b2ed82b84b --- /dev/null +++ b/azure-functions-maven-plugin/src/it/http-trigger/pom.xml @@ -0,0 +1,110 @@ + + + 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} + westus + + + + + com.microsoft.azure + azure-functions-java-core + 1.0.0-beta-1 + + + + + junit + junit + 4.12 + test + + + + + + + + maven-resources-plugin + 3.0.2 + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + + + + + @project.groupId@ + @project.artifactId@ + + + azure-auth + + maven-functions-it-rg-1 + ${functionAppName} + ${functionAppRegion} + + + FUNCTIONS_EXTENSION_VERSION + beta + + + + + + package-functions + + package + + + + + + maven-resources-plugin + + + copy-resources + package + + copy-resources + + + true + ${project.build.directory}/azure-functions/${functionAppName} + + + + ${project.basedir} + + host.json + local.settings.json + + + + + + + + + + + + diff --git a/azure-functions-maven-plugin/src/it/http-trigger/setup.groovy b/azure-functions-maven-plugin/src/it/http-trigger/setup.groovy new file mode 100644 index 0000000000..434d7005cb --- /dev/null +++ b/azure-functions-maven-plugin/src/it/http-trigger/setup.groovy @@ -0,0 +1,17 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for + * license information. + */ + +// Delete resources before test +def clientId = System.getenv("CLIENT_ID") +def tenantId = System.getenv("TENANT_ID") +def key = System.getenv("KEY") +def command = """ + az login --service-principal -u ${clientId} -p ${key} --tenant ${tenantId} + az group delete -y -n maven-functions-it-rg-1 + az logout +""" +def process = ["bash", "-c", command].execute() +println process.text diff --git a/azure-functions-maven-plugin/src/it/http-trigger/src/main/java/com/microsoft/azure/Function.java b/azure-functions-maven-plugin/src/it/http-trigger/src/main/java/com/microsoft/azure/Function.java new file mode 100644 index 0000000000..6546d1b891 --- /dev/null +++ b/azure-functions-maven-plugin/src/it/http-trigger/src/main/java/com/microsoft/azure/Function.java @@ -0,0 +1,40 @@ +/** + * 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; + +import com.microsoft.azure.serverless.functions.annotation.*; +import com.microsoft.azure.serverless.functions.*; + +/** + * Azure Functions with HTTP Trigger. + */ +public class Function { + @FunctionName("hello") + public HttpResponseMessage httpHandler( + @HttpTrigger(name = "req", methods = {"get", "post"}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage request, + final ExecutionContext context + ) { + context.getLogger().info("Java HTTP trigger processed a HTTP request."); + + // Parse query parameter + String name = request.getQueryParameters().get("name"); + + if (name == null) { + // Get request body + Object body = request.getBody(); + if (body != null) { + name = body.toString(); + } + } + + if (name == null) { + return request.createResponse(400, "Please pass a name on the query string or in the request body"); + } else { + return request.createResponse(200, "Hello, " + name); + } + } +} diff --git a/azure-functions-maven-plugin/src/it/settings.xml b/azure-functions-maven-plugin/src/it/settings.xml new file mode 100644 index 0000000000..e997981bdb --- /dev/null +++ b/azure-functions-maven-plugin/src/it/settings.xml @@ -0,0 +1,45 @@ + + + + azure-auth + + ${env.CLIENT_ID} + ${env.TENANT_ID} + ${env.KEY} + AZURE + + + + + + it-repo + + true + + + + local.central + @localRepositoryUrl@ + + true + + + true + + + + + + local.central + @localRepositoryUrl@ + + true + + + true + + + + + + diff --git a/azure-functions-maven-plugin/src/main/resources/templates.json b/azure-functions-maven-plugin/src/main/resources/templates.json index 6b66b433c1..3d9563a6a9 100644 --- a/azure-functions-maven-plugin/src/main/resources/templates.json +++ b/azure-functions-maven-plugin/src/main/resources/templates.json @@ -9,7 +9,7 @@ "userPrompt": [] }, "files": { - "function.java": "package $packageName$;\n\nimport com.microsoft.azure.serverless.functions.ExecutionContext;\nimport com.microsoft.azure.serverless.functions.annotation.*;\n\npublic class $functionName$ {\n @FunctionName(\"$functionName$\")\n public void functionHandler(@HttpTrigger(name = \"req\", authLevel = AuthorizationLevel.ANONYMOUS) String req, final ExecutionContext executionContext) {\n System.out.println(\"Http trigger input: \" + req);\n }\n}\n" + "function.java": "package $packageName$;\n\nimport com.microsoft.azure.serverless.functions.annotation.*;\nimport com.microsoft.azure.serverless.functions.*;\n\n/**\n * Azure Functions with HTTP trigger.\n */\npublic class $functionName$ {\n /**\n * This function will listen at HTTP endpoint \"/api/$functionName$\". Two approaches to invoke it using \"curl\" command in bash:\n * 1. curl -d \"Http Body\" {your host}/api/$functionName$\n * 2. curl {your host}/api/$functionName$?name=HTTP%20Query\n */\n @FunctionName(\"$functionName$\")\n public HttpResponseMessage httpHandler(\n @HttpTrigger(name = \"req\", methods = { \"get\", \"post\" }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage request,\n final ExecutionContext context\n ) {\n context.getLogger().info(\"Java HTTP trigger processed a request.\");\n\n // Parse query parameters\n String name = request.getQueryParameters().get(\"name\");\n\n if (name == null) {\n // Get request body\n Object body = request.getBody();\n if (body != null) {\n name = body.toString();\n }\n }\n\n if (name == null) {\n return request.createResponse(400, \"Please pass a name on the query string or in the request body\");\n } else {\n return request.createResponse(200, \"Hello, \" + name);\n }\n }\n}\n" } }, { @@ -25,7 +25,7 @@ ] }, "files": { - "function.java": "package $packageName$;\n\nimport com.microsoft.azure.serverless.functions.ExecutionContext;\nimport com.microsoft.azure.serverless.functions.annotation.*;\n\npublic class $functionName$ {\n @FunctionName(\"$functionName$\")\n public void functionHandler(@BlobTrigger(name = \"myBlob\", path = \"$path$\", connection = \"$connection$\") String myBlob, final ExecutionContext executionContext) {\n System.out.println(\"Blob trigger input: \" + myBlob);\n }\n}\n" + "function.java": "package $packageName$;\n\nimport com.microsoft.azure.serverless.functions.annotation.*;\nimport com.microsoft.azure.serverless.functions.*;\n\n/**\n * Azure Functions with Azure Blob trigger.\n */\npublic class $functionName$ {\n /**\n * This function will be invoked when a new or updated blob is detected at the specified path. The blob contents are provided as input to this function.\n */\n @FunctionName(\"$functionName$\")\n public void blobHandler(\n @BlobTrigger(name = \"content\", path = \"$path$\", connection = \"$connection$\") String content,\n final ExecutionContext context\n ) {\n context.getLogger().info(\"Java Blob trigger function processed a blob. Blob=\" + content);\n }\n}\n" } }, { @@ -41,57 +41,7 @@ ] }, "files": { - "function.java": "package $packageName$;\n\nimport com.microsoft.azure.serverless.functions.ExecutionContext;\nimport com.microsoft.azure.serverless.functions.annotation.*;\n\npublic class $functionName$ {\n @FunctionName(\"$functionName$\")\n public void functionHandler(@QueueTrigger(name = \"myQueueItem\", queueName = \"$queueName$\", connection = \"$connection$\") String myQueueItem, final ExecutionContext executionContext) {\n System.out.println(\"Queue trigger input: \" + myQueueItem);\n }\n}\n" - } - }, - { - "id": "ServiceBusQueueTrigger-Java", - "metadata": { - "name": "ServiceBusQueueTrigger", - "description": "$ServiceBusQueueTriggerJava_description", - "defaultFunctionName": "serviceBusQueueTriggerJava", - "language": "Java", - "userPrompt": [ - "connection", - "queueName" - ] - }, - "files": { - "function.java": "package $packageName$;\n\nimport com.microsoft.azure.serverless.functions.ExecutionContext;\nimport com.microsoft.azure.serverless.functions.annotation.*;\n\npublic class $functionName$ {\n @FunctionName(\"$functionName$\")\n public void functionHandler(@ServiceBusQueueTrigger(name = \"myQueueItem\", queueName = \"$queueName$\", connection = \"$connection$\") String myQueueItem, final ExecutionContext executionContext) {\n System.out.println(\"Service Bus Queue trigger input: \" + myQueueItem);\n }\n}\n" - } - }, - { - "id": "ServiceBusTopicTrigger-Java", - "metadata": { - "name": "ServiceBusTopicTrigger", - "description": "$ServiceBusTopicTriggerJava_description", - "defaultFunctionName": "serviceBusTopicTriggerJava", - "language": "Java", - "userPrompt": [ - "connection", - "topicName", - "subscriptionName" - ] - }, - "files": { - "function.java": "package $packageName$;\n\nimport com.microsoft.azure.serverless.functions.ExecutionContext;\nimport com.microsoft.azure.serverless.functions.annotation.*;\n\npublic class $functionName$ {\n @FunctionName(\"$functionName$\")\n public void functionHandler(@ServiceBusTopicTrigger(name = \"myTopicItem\", topicName = \"$topicName$\", subscriptionName = \"$subscriptionName$\", connection = \"$connection$\") String myTopicItem, final ExecutionContext executionContext) {\n System.out.println(\"Service Bus Topic trigger input: \" + myTopicItem);\n }\n}\n" - } - }, - { - "id": "EventHubTrigger-Java", - "metadata": { - "name": "EventHubTrigger", - "description": "$EventHubTriggerJava_description", - "defaultFunctionName": "eventHubTriggerJava", - "language": "Java", - "userPrompt": [ - "connection", - "consumerGroup", - "eventHubName" - ] - }, - "files": { - "function.java": "package $packageName$;\n\nimport com.microsoft.azure.serverless.functions.ExecutionContext;\nimport com.microsoft.azure.serverless.functions.annotation.*;\n\npublic class $functionName$ {\n @FunctionName(\"$functionName$\")\n public void functionHandler(@EventHubTrigger(name = \"myEvent\", eventHubName = \"$eventHubName$\", consumerGroup = \"$consumerGroup$\", connection = \"$connection$\") String myEvent, final ExecutionContext executionContext) {\n System.out.println(\"EventHub trigger input: \" + myEvent);\n }\n}\n" + "function.java": "package $packageName$;\n\nimport com.microsoft.azure.serverless.functions.annotation.*;\nimport com.microsoft.azure.serverless.functions.*;\n\n/**\n * Azure Functions with Azure Storage Queue trigger.\n */\npublic class $functionName$ {\n /**\n * This function will be invoked when a new message is received at the specified path. The message contents are provided as input to this function.\n */\n @FunctionName(\"$functionName$\")\n public void queueHandler(\n @QueueTrigger(name = \"message\", queueName = \"$queueName$\", connection = \"$connection$\") String message,\n final ExecutionContext context\n ) {\n context.getLogger().info(\"Java Queue trigger function processed a message: \" + message);\n }\n}\n" } }, { @@ -106,7 +56,7 @@ ] }, "files": { - "function.java": "package $packageName$;\n\nimport com.microsoft.azure.serverless.functions.ExecutionContext;\nimport com.microsoft.azure.serverless.functions.annotation.*;\n\npublic class $functionName$ {\n @FunctionName(\"$functionName$\")\n public void functionHandler(@TimerTrigger(name = \"timerInfo\", schedule = \"$schedule$\") String timerInfo, final ExecutionContext executionContext) {\n System.out.println(\"Timer trigger input: \" + timerInfo);\n }\n}\n" + "function.java": "package $packageName$;\n\nimport java.time.*;\nimport com.microsoft.azure.serverless.functions.annotation.*;\nimport com.microsoft.azure.serverless.functions.*;\n\n/**\n * Azure Functions with Timer trigger.\n */\npublic class $functionName$ {\n /**\n * This function will be invoked periodically according to the specified schedule.\n */\n @FunctionName(\"$functionName$\")\n public void timerHandler(\n @TimerTrigger(name = \"timerInfo\", schedule = \"$schedule$\") String timerInfo,\n final ExecutionContext context\n ) {\n context.getLogger().info(\"Java Timer trigger function executed at: \" + LocalDateTime.now());\n }\n}\n" } } ]