Azure Function with Spring Boot Native and GraalVM

Starting point for building Spring Native application hosted in Azure Function.

Azure Storage ContainerAzure App Service
package com.pulumi.example.infra;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.asset.FileArchive;
import com.pulumi.azurenative.resources.ResourceGroup;
import com.pulumi.azurenative.storage.Blob;
import com.pulumi.azurenative.storage.BlobArgs;
import com.pulumi.azurenative.storage.BlobContainer;
import com.pulumi.azurenative.storage.BlobContainerArgs;
import com.pulumi.azurenative.storage.StorageAccount;
import com.pulumi.azurenative.storage.StorageAccountArgs;
import com.pulumi.azurenative.storage.StorageFunctions;
import com.pulumi.azurenative.storage.enums.HttpProtocol;
import com.pulumi.azurenative.storage.enums.Kind;
import com.pulumi.azurenative.storage.enums.Permissions;
import com.pulumi.azurenative.storage.enums.SignedResource;
import com.pulumi.azurenative.storage.enums.SkuName;
import com.pulumi.azurenative.storage.inputs.ListStorageAccountKeysArgs;
import com.pulumi.azurenative.storage.inputs.ListStorageAccountServiceSASArgs;
import com.pulumi.azurenative.storage.inputs.SkuArgs;
import com.pulumi.azurenative.web.AppServicePlan;
import com.pulumi.azurenative.web.AppServicePlanArgs;
import com.pulumi.azurenative.web.WebApp;
import com.pulumi.azurenative.web.WebAppArgs;
import com.pulumi.azurenative.web.enums.FtpsState;
import com.pulumi.azurenative.web.enums.SupportedTlsVersions;
import com.pulumi.azurenative.web.inputs.NameValuePairArgs;
import com.pulumi.azurenative.web.inputs.SiteConfigArgs;
import com.pulumi.azurenative.web.inputs.SkuDescriptionArgs;
import com.pulumi.core.Output;
import java.io.File;

public class Main {

    public static void main(String[] args) {
        Pulumi.run(Main::stack);
    }

    private static void stack(Context ctx) {
        // Create a separate resource group for this example.
        var resourceGroup = new ResourceGroup("linux-fn-rg");

        // Storage account is required by Function App.
        // Also, we will upload the function code to the same storage account.
        var storageAccount = new StorageAccount("linux-fn-sa", StorageAccountArgs.builder()
                .resourceGroupName(resourceGroup.name())
                .kind(Kind.StorageV2)
                .sku(SkuArgs.builder()
                        .name(SkuName.Standard_LRS)
                        .build())
                .build());

        // Function code archives will be stored in this container.
        var codeContainer = new BlobContainer("zips", BlobContainerArgs.builder()
                .resourceGroupName(resourceGroup.name())
                .accountName(storageAccount.name())
                .build()
        );

        // Upload Azure Function's code as a zip archive to the storage account.
        var codeBlob = new Blob("zip", BlobArgs.builder()
                .resourceGroupName(resourceGroup.name())
                .accountName(storageAccount.name())
                .containerName(codeContainer.name())
                .source(new FileArchive(findAppArchive().getAbsolutePath()))
                .build()
        );

        // Define a Consumption Plan for the Function App.
        // You can change the SKU to Premium or App Service Plan if needed.
        var plan = new AppServicePlan("plan", AppServicePlanArgs.builder()
                .resourceGroupName(resourceGroup.name())
                .kind("Linux")
                .reserved(true) // required for Linux
                .sku(SkuDescriptionArgs.builder()
                        .tier("Dynamic")
                        .name("Y1")
                        .capacity(1)
                        .build())
                .build());

        // Build the connection string and zip archive's SAS URL. They will go to Function App's settings.
        var storageConnectionString = getConnectionString(resourceGroup.name(), storageAccount.name());
        var codeBlobUrl = signedBlobReadUrl(codeBlob, codeContainer, storageAccount, resourceGroup);

        var app = storageConnectionString.applyValue(
                conn -> new WebApp("function", WebAppArgs.builder()
                        .resourceGroupName(resourceGroup.name())
                        .serverFarmId(plan.getId())
                        .kind("functionapp,linux,container")
                        .httpsOnly(true)
                        .siteConfig(SiteConfigArgs.builder()
                                .numberOfWorkers(1)
                                .minTlsVersion(SupportedTlsVersions._1_2)
                                .ftpsState(FtpsState.Disabled)
                                .appSettings(
                                        NameValuePairArgs.builder().name("AzureWebJobsStorage").value(conn).build(),
                                        NameValuePairArgs.builder().name("WEBSITE_RUN_FROM_PACKAGE").value(codeBlobUrl).build(),
                                        NameValuePairArgs.builder().name("FUNCTIONS_EXTENSION_VERSION").value("~3").build(),
                                        NameValuePairArgs.builder().name("FUNCTIONS_WORKER_RUNTIME").value("powershell").build()
                                ).build())
                        .build())
        );
        ctx.export("functionName", app.apply(WebApp::name));
        ctx.export("endpoint", Output.format(
                "https://%s/api/hello", // 'hello' corresponds to app/src/main/function/hello
                app.apply(WebApp::defaultHostName)
        ));
    }

    static Output<String> getConnectionString(Output<String> resourceGroupName, Output<String> accountName) {
        // Retrieve the primary storage account key.
        var primaryStorageKey = StorageFunctions.listStorageAccountKeys(ListStorageAccountKeysArgs.builder()
                .resourceGroupName(resourceGroupName)
                .accountName(accountName)
                .build())
            .applyValue(r -> r.keys().get(0).value());

        // Build the connection string to the storage account.
        return Output.format(
                "DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s",
                accountName, primaryStorageKey
        );
    }

    static Output<String> signedBlobReadUrl(
            Blob blob, BlobContainer container, StorageAccount account, ResourceGroup resourceGroup) {
        var blobSASServiceSasToken = StorageFunctions.listStorageAccountServiceSAS(ListStorageAccountServiceSASArgs.builder()
                    .resourceGroupName(resourceGroup.name())
                    .accountName(account.name())
                    .protocols(HttpProtocol.Https)
                    .sharedAccessExpiryTime("2030-01-01")
                    .sharedAccessStartTime("2021-01-01")
                    .resource(SignedResource.C)
                    .permissions(Permissions.R)
                    .canonicalizedResource(String.format("/blob/%s/%s", account.name(), container.name()))
                    .contentType("application/json")
                    .cacheControl("max-age=5")
                    .contentDisposition("inline")
                    .contentEncoding("deflate")
                    .build())
                .applyValue(sas -> sas.serviceSasToken());
        return Output.format(
                "https://%s.blob.core.windows.net/%s/%s?$%s",
                account.name(), container.name(), blob.name(), blobSASServiceSasToken
        );
    }

    private static File findAppArchive() {
        var files = new File("../app/build/dist")
            .listFiles((dir, name) -> name.endsWith("-app.zip"));
        if (files == null || files.length == 0) {
            throw new IllegalStateException("Could not find app archive in `./app/build/dist/*-app.zip`;"+
                                            " did you run `gradle app:packageDistribution`?");
        }
        if (files.length > 1) {
            throw new IllegalStateException("Found more than one app archive `./app/build/dist/*-app.zip`;"+
                                            " confused which one to use.");
        }
        return files[0];
    }
}

Download

git clone https://github.com/pulumi/examples
cd examples/azure-java-function-graal-spring

Pattern repository

View on GitHub

Last updated on 26 Dec 2024

Edit this page