|
| 1 | +--- |
| 2 | +title: Bring dependencies and third-party libraries to Azure Functions |
| 3 | +description: Learn how to bring files or third party library |
| 4 | +ms.date: 4/6/2020 |
| 5 | +ms.topic: article |
| 6 | +zone_pivot_groups: "bring-third-party-dependency-programming-functions" |
| 7 | +--- |
| 8 | + |
| 9 | +# Bring dependencies or third party library to Azure Functions |
| 10 | + |
| 11 | +In this article, you learn to bring in third-party dependencies into your functions apps. Examples of third-party dependencies are json files, binary files and machine learning models. |
| 12 | + |
| 13 | +In this article, you learn how to: |
| 14 | +> [!div class="checklist"] |
| 15 | +> * Bring in dependencies via Functions Code project |
| 16 | +::: zone pivot="programming-language-python" |
| 17 | +> [!div class="checklist"] |
| 18 | +> * Bring in dependencies via mounting Azure Fileshare |
| 19 | +::: zone-end |
| 20 | + |
| 21 | +## Bring in dependencies from the project directory |
| 22 | +::: zone pivot="programming-language-python" |
| 23 | +One of the simplest ways to bring in dependencies is to put the files/artifact together with the functions app code in Functions project directory structure. Here's an example of the directory samples in a Python functions project: |
| 24 | +``` |
| 25 | +<project_root>/ |
| 26 | + | - my_first_function/ |
| 27 | + | | - __init__.py |
| 28 | + | | - function.json |
| 29 | + | | - example.py |
| 30 | + | - dependencies/ |
| 31 | + | | - dependency1 |
| 32 | + | - .funcignore |
| 33 | + | - host.json |
| 34 | + | - local.settings.json |
| 35 | +``` |
| 36 | +By putting the dependencies in a folder inside functions app project directory, the dependencies folder will get deployed together with the code. As a result, your function code can access the dependencies in the cloud via file system api. |
| 37 | + |
| 38 | +### Accessing the dependencies in your code |
| 39 | + |
| 40 | +Here's an example to access and execute ```ffmpeg``` dependency that is put into ```<project_root>/ffmpeg_lib``` directory. |
| 41 | + |
| 42 | + |
| 43 | +```python |
| 44 | +import logging |
| 45 | + |
| 46 | +import azure.functions as func |
| 47 | +import subprocess |
| 48 | + |
| 49 | +FFMPEG_RELATIVE_PATH = "../ffmpeg_lib/ffmpeg" |
| 50 | + |
| 51 | +def main(req: func.HttpRequest, |
| 52 | + context: func.Context) -> func.HttpResponse: |
| 53 | + logging.info('Python HTTP trigger function processed a request.') |
| 54 | + |
| 55 | + command = req.params.get('command') |
| 56 | + # If no command specified, set the command to help |
| 57 | + if not command: |
| 58 | + command = "-h" |
| 59 | + |
| 60 | + # context.function_directory returns the current directory in which functions is executed |
| 61 | + ffmpeg_path = "/".join([str(context.function_directory), FFMPEG_RELATIVE_PATH]) |
| 62 | + |
| 63 | + try: |
| 64 | + byte_output = subprocess.check_output([ffmpeg_path, command]) |
| 65 | + return func.HttpResponse(byte_output.decode('UTF-8').rstrip(),status_code=200) |
| 66 | + except Exception as e: |
| 67 | + return func.HttpResponse("Unexpected exception happened when executing ffmpeg. Error message:" + str(e),status_code=200) |
| 68 | +``` |
| 69 | +>[!NOTE] |
| 70 | +> You may need to use `chmod` to provide `Execute` rights to the ffmpeg binary in a Linux environment |
| 71 | +::: zone-end |
| 72 | + |
| 73 | +::: zone pivot="programming-language-java" |
| 74 | +One of the simplest ways to bring in dependencies is to put the files/artifact together with the functions app code in functions project directory structure. Here's an example of the directory samples in a Java functions project: |
| 75 | +``` |
| 76 | +<project_root>/ |
| 77 | + | - src/ |
| 78 | + | | - main/java/com/function |
| 79 | + | | | - Function.java |
| 80 | + | | - test/java/com/function |
| 81 | + | - artifacts/ |
| 82 | + | | - dependency1 |
| 83 | + | - host.json |
| 84 | + | - local.settings.json |
| 85 | + | - pom.xml |
| 86 | +``` |
| 87 | +For java specifically, you need to specifically include the artifacts into the build/target folder when copying resources. Here's an example on how to do it in Maven: |
| 88 | + |
| 89 | +```xml |
| 90 | +... |
| 91 | +<execution> |
| 92 | + <id>copy-resources</id> |
| 93 | + <phase>package</phase> |
| 94 | + <goals> |
| 95 | + <goal>copy-resources</goal> |
| 96 | + </goals> |
| 97 | + <configuration> |
| 98 | + <overwrite>true</overwrite> |
| 99 | + <outputDirectory>${stagingDirectory}</outputDirectory> |
| 100 | + <resources> |
| 101 | + <resource> |
| 102 | + <directory>${project.basedir}</directory> |
| 103 | + <includes> |
| 104 | + <include>host.json</include> |
| 105 | + <include>local.settings.json</include> |
| 106 | + <include>artifacts/**</include> |
| 107 | + </includes> |
| 108 | + </resource> |
| 109 | + </resources> |
| 110 | + </configuration> |
| 111 | +</execution> |
| 112 | +... |
| 113 | +``` |
| 114 | +By putting the dependencies in a folder inside functions app project directory, the dependencies folder will get deployed together with the code. As a result, your function code can access the dependencies in the cloud via file system api. |
| 115 | + |
| 116 | +### Accessing the dependencies in your code |
| 117 | + |
| 118 | +Here's an example to access and execute ```ffmpeg``` dependency that is put into ```<project_root>/ffmpeg_lib``` directory. |
| 119 | + |
| 120 | + |
| 121 | +```java |
| 122 | +public class Function { |
| 123 | + final static String BASE_PATH = "BASE_PATH"; |
| 124 | + final static String FFMPEG_PATH = "/artifacts/ffmpeg/ffmpeg.exe"; |
| 125 | + final static String HELP_FLAG = "-h"; |
| 126 | + final static String COMMAND_QUERY = "command"; |
| 127 | + |
| 128 | + @FunctionName("HttpExample") |
| 129 | + public HttpResponseMessage run( |
| 130 | + @HttpTrigger( |
| 131 | + name = "req", |
| 132 | + methods = {HttpMethod.GET, HttpMethod.POST}, |
| 133 | + authLevel = AuthorizationLevel.ANONYMOUS) |
| 134 | + HttpRequestMessage<Optional<String>> request, |
| 135 | + final ExecutionContext context) throws IOException{ |
| 136 | + context.getLogger().info("Java HTTP trigger processed a request."); |
| 137 | + |
| 138 | + // Parse query parameter |
| 139 | + String flags = request.getQueryParameters().get(COMMAND_QUERY); |
| 140 | + |
| 141 | + if (flags == null || flags.isBlank()) { |
| 142 | + flags = HELP_FLAG; |
| 143 | + } |
| 144 | + |
| 145 | + Runtime rt = Runtime.getRuntime(); |
| 146 | + String[] commands = { System.getenv(BASE_PATH) + FFMPEG_PATH, flags}; |
| 147 | + Process proc = rt.exec(commands); |
| 148 | + |
| 149 | + BufferedReader stdInput = new BufferedReader(new |
| 150 | + InputStreamReader(proc.getInputStream())); |
| 151 | + |
| 152 | + String out = stdInput.lines().collect(Collectors.joining("\n")); |
| 153 | + if(out.isEmpty()) { |
| 154 | + BufferedReader stdError = new BufferedReader(new |
| 155 | + InputStreamReader(proc.getErrorStream())); |
| 156 | + out = stdError.lines().collect(Collectors.joining("\n")); |
| 157 | + } |
| 158 | + return request.createResponseBuilder(HttpStatus.OK).body(out).build(); |
| 159 | + |
| 160 | + } |
| 161 | +``` |
| 162 | +>[!NOTE] |
| 163 | +> To get this snippet of code to work in Azure, you need to specify a custom application setting of "BASE_PATH" with value of "/home/site/wwwroot" |
| 164 | +::: zone-end |
| 165 | + |
| 166 | + |
| 167 | +::: zone pivot="programming-language-python" |
| 168 | +## Bring dependencies by mounting a file share |
| 169 | + |
| 170 | +When running your function app on Linux, there's another way to bring in third-party dependencies. Functions lets you mount a file share hosted in Azure Files. Consider this approach when you want to decouple dependencies or artifacts from your application code. |
| 171 | +
|
| 172 | +First, you need to create an Azure Storage Account. In the account, you also need to create file share in Azure files. To create these resources, follow this [guide](../storage/files/storage-how-to-use-files-portal.md) |
| 173 | +
|
| 174 | +After you created the storage account and file share, use the [az webapp config storage-account add](/cli/azure/webapp/config/storage-account#az_webapp_config_storage_account_add) command to attach the file share to your functions app, as shown in the following example. |
| 175 | +
|
| 176 | +```console |
| 177 | +az webapp config storage-account add \ |
| 178 | + --name < Function-App-Name > \ |
| 179 | + --resource-group < Resource-Group > \ |
| 180 | + --subscription < Subscription-Id > \ |
| 181 | + --custom-id < Unique-Custom-Id > \ |
| 182 | + --storage-type AzureFiles \ |
| 183 | + --account-name < Storage-Account-Name > \ |
| 184 | + --share-name < File-Share-Name > \ |
| 185 | + --access-key < Storage-Account-AccessKey > \ |
| 186 | + --mount-path </path/to/mount> |
| 187 | +``` |
| 188 | +
|
| 189 | +
|
| 190 | +
|
| 191 | +| Flag | Value | |
| 192 | +| ------------- | ---------------------------------- | |
| 193 | +| custom-id | Any unique string | |
| 194 | +| storage-type | Only AzureFiles is supported currently | |
| 195 | +| share-name | Pre-existing share | |
| 196 | +| mount-path | Path at which the share will be accessible inside the container. Value has to be of the format `/dir-name` and it can't start with `/home` | |
| 197 | + |
| 198 | +More commands to modify/delete the file share configuration can be found [here](/cli/azure/webapp/config/storage-account#az-webapp-config-storage-account-update) |
| 199 | + |
| 200 | + |
| 201 | +### Uploading the dependencies to Azure Files |
| 202 | + |
| 203 | +One option to upload your dependency into Azure Files is through Azure portal. Refer to this [guide](../storage/files/storage-how-to-use-files-portal.md#upload-a-file) for instruction to upload dependencies using portal. Other options to upload your dependencies into Azure Files are through [Azure CLI](../storage/files/storage-how-to-use-files-cli.md#upload-a-file) and [PowerShell](../storage/files/storage-how-to-use-files-powershell.md#upload-a-file). |
| 204 | + |
| 205 | + |
| 206 | +### Accessing the dependencies in your code |
| 207 | + |
| 208 | +After your dependencies are uploaded in the file share, you can access the dependencies from your code. The mounted share is available at the specified *mount-path*, such as ```/path/to/mount```. You can access the target directory by using file system APIs. |
| 209 | + |
| 210 | +The following example shows HTTP trigger code that accesses the `ffmpeg` library, which is stored in a mounted file share. |
| 211 | + |
| 212 | +```python |
| 213 | +import logging |
| 214 | + |
| 215 | +import azure.functions as func |
| 216 | +import subprocess |
| 217 | + |
| 218 | +FILE_SHARE_MOUNT_PATH = os.environ['FILE_SHARE_MOUNT_PATH'] |
| 219 | +FFMPEG = "ffmpeg" |
| 220 | + |
| 221 | +def main(req: func.HttpRequest) -> func.HttpResponse: |
| 222 | + logging.info('Python HTTP trigger function processed a request.') |
| 223 | + |
| 224 | + command = req.params.get('command') |
| 225 | + # If no command specified, set the command to help |
| 226 | + if not command: |
| 227 | + command = "-h" |
| 228 | + |
| 229 | + try: |
| 230 | + byte_output = subprocess.check_output(["/".join(FILE_SHARE_MOUNT_PATH, FFMPEG), command]) |
| 231 | + return func.HttpResponse(byte_output.decode('UTF-8').rstrip(),status_code=200) |
| 232 | + except Exception as e: |
| 233 | + return func.HttpResponse("Unexpected exception happened when executing ffmpeg. Error message:" + str(e),status_code=200) |
| 234 | +``` |
| 235 | + |
| 236 | +When you deploy this code to a function app in Azure, you need to [create an app setting](functions-how-to-use-azure-function-app-settings.md#settings) with a key name of `FILE_SHARE_MOUNT_PATH` and value of the mounted file share path, which for this example is `/azure-files-share`. To do local debugging, you need to populate the `FILE_SHARE_MOUNT_PATH` with the file path where your dependencies are stored in your local machine. Here's an example to set `FILE_SHARE_MOUNT_PATH` using `local.settings.json`: |
| 237 | +
|
| 238 | +```json |
| 239 | +{ |
| 240 | + "IsEncrypted": false, |
| 241 | + "Values": { |
| 242 | + "AzureWebJobsStorage": "", |
| 243 | + "FUNCTIONS_WORKER_RUNTIME": "python", |
| 244 | + "FILE_SHARE_MOUNT_PATH" : "PATH_TO_LOCAL_FFMPEG_DIR" |
| 245 | + } |
| 246 | +} |
| 247 | +
|
| 248 | +``` |
| 249 | +::: zone-end |
| 250 | +
|
| 251 | +
|
| 252 | +
|
| 253 | +## Next steps |
| 254 | +
|
| 255 | ++ [Azure Functions Python developer guide](functions-reference-python.md) |
| 256 | ++ [Azure Functions Java developer guide](functions-reference-java.md) |
| 257 | ++ [Azure Functions developer reference](functions-reference.md) |
0 commit comments