- Author: Etienne P Jacquot (04/29/2021)
Following the helpful Microsoft Azure Development example --> here
- You need az cli, docker, and R on your localhost (I am running on MacOS):
az login # <-- confirm you azure cli access
docker login # <-- confirm your Docker ID access
/Library/Frameworks/R.framework/Resources/bin/R --version # <-- to confirm local R version
Local Azure Function R development to cloud Linux container
- Azure Func Directory --> LocalFunctionsProject
func init LocalFunctionsProject --worker-runtime custom --docker
cd LocalFunctionsProject
func new --name HttpExample --template "HTTP trigger"
- Run & add from the script which is described here.
--> YOU MUST GO TO THAT AZURE SITE TO COPY THE HANDLER R SCRIPT
touch handler.R
vim handler.R
- Add the script for your R HTTPUV simple web app (more info here) *
+ library(httpuv)
+ ...
+ cat(paste0("Server listening on :", PORT, "...\n"))
+ runServer("0.0.0.0", PORT, app)
Edit your host.json to add the following for a custom header
- We use the absolute path to my Rscript locally, remember to change this for your local host / rebuild before pushing to Docker
"customHandler": {
"description": {
- "defaultExecutablePath": "Rscript",
+ "defaultExecutablePath": "/Library/Frameworks/R.framework/Resources/bin/Rscript",
+ "arguments": [
+ "handler.R"
+ ]
Test local R function on MacOS, I don't have this bin in my path so local dev edit the host.json:
- Run the function locally to confirm this works as expected:
/Library/Frameworks/R.framework/Resources/bin/R -e "install.packages('httpuv', repos='http://cran.rstudio.com/')"
func start
- This will show in browser http://localhost:7071/api/HttpExample
Significantly, to demonstrate how the Function api works you can append &name=ETIENNEJ
to the url for a custom hello message.
- This would be http://localhost:7071/api/HttpExample?name=ETIENNEJ
With our R function working locally, proceed with containerizing
- Edit the Dockerfile) with lines provided:s
+FROM mcr.microsoft.com/azure-functions/dotnet:3.0-appservice
+ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
+ AzureFunctionsJobHost__Logging__Console__IsEnabled=true
+RUN apt update && \
+ apt install -y r-base && \
+ R -e "install.packages('httpuv', repos='http://cran.rstudio.com/')"
+COPY . /home/site/wwwroot
- PLEASE REMEMBER: Make sure to edit your host.json for the RScript line before building so this is not absolute path!!
Build your R function azure container locally:
- My dockerID is
atnjqt
--> https://hub.docker.com/u/atnjqt
docker ps # <-- make sure Docker Daemon is on!
docker build --tag atnjqt/azurefunctionsimage:v1.0.0 .
Run your R function azure container locally:
- Make sure to use the correct version number
docker run -p 8080:80 -it atnjqt/azurefunctionsimage:v1.0.0
If you open http://localhost:8080 you will now notice the Azure Function default blue screen...
- You can try and navigate to http://localhost:8080/api/HttpExample but this will fail! This is a security feature that can be limiting in dev for azure funcs... You need to change
authlevel
to confirm this is working before pushing to the cloud!
Edit your HttpExample/function.json
- You need to stop the local container, edit the following and restart docker daemon, then rebuild w/ a new tag version to run with anonymous authlevel:
"bindings": [
{
- "authLevel": "function",
+ "authLevel": "anonymous",
"type": "httpTrigger",
Please navigate to your API local endpoint, this should work locally now at http://localhost:8080/api/HttpExample
- Confirm the API function works w/ http://localhost:8080/api/HttpExample?name=ETIENNEJ-CONTAINER in
anonymous
authlevel enabled...
YOU MUST SWITCH BACK TO AUTHELEVEL Function
FOR YOUR FUNCTION APP BEFORE DOCKER PUSH!
- i.e. Rebuild w/ a new tag, no need to rerun though because we know it works...
You can run the following, make sure to use the specific tag version number...
- initial push may take a while, but subsequent pushes are quick!
docker login
docker push atnjqt/azurefunctionsimage:v1.4.0
Navigate to docker hub to confirm your image is pushed to your account:
Run the following examples to reproduce a public R http function endpoint.
- My organization already had various of these resources provisioned for my account / I was able to use already existing resources. I include the below steps just for reference, please consider associated costs w/ the premium EP1 linux instance...
- Run the following with specific values for
--name
values
az login # <-- confirm your login to Azure
# Resource Group
az group create --name AzureFunctionsContainers-rg --location eastus2
az storage account create --name <storage_name> --location eastus2 --resource-group AzureFunctionsContainers-rg --sku Standard_LRS
I already have the following so we just run:
- resource groups (
asc-etienne-rg
) - storage groups (
etiennejapp
)
--> NOTE, THIS IS A PREMIUM PLAN SO PLEASE DELETE WHEN COMPLETED!
az functionapp plan create --resource-group asc-etienne-rg --name myPremiumPlan-dev --location eastus2 --number-of-workers 1 --sku EP1 --is-linux
Your premium App Service Plan is now created!
- *Please note we tried to find this on the GUI but couldn't find
EP1
... *
For our Aure Function we have the following configurations:
r-helloworld-dev
azure func nameetiennejapp
storage account nameasc-etienne-rg
resource group namemyPremiumPlan-dev
plan created in steps aboveatnjqt/azurefunctionsimage:latest
as custom container (more info here)
We tried going through Azure GUI but unsure how to associate w/ a custom linux container
- Run the following for our
r-helloworld-dev
:
az functionapp create --name r-helloworld-dev --storage-account etiennejapp --resource-group asc-etienne-rg --plan myPremiumPlan-dev --runtime custom --deployment-container-image-name atnjqt/azurefunctionsimage:v1.1.2
- Run the following for storage account
etiennejapp
query string:
storageConnectionString=$(az storage account show-connection-string --resource-group asc-etienne-rg --name etiennejapp --query connectionString --output tsv)
az functionapp config appsettings set --name r-helloworld-dev --resource-group asc-etienne-rg --settings AzureWebJobsStorage=$storageConnectionString
- Great! Your Azure Function is now associated with the Storage Account, and thus it should be live.
Navigate to https://portal.azure.com to find your Azure Function App
-
https://r-helloworld-dev.azurewebsites.net (to confirm the app is live)
-
https://r-helloworld-dev.azurewebsites.net/api/HttpExample (our end point is public, but cannot be reached without Function level auth token)
-
https://r-helloworld-dev.azurewebsites.net/api/HttpExample?code=Vbl5n5NBSqFWObzJIbXgSKrro0RpeiEipEsIVaGgsADNvV84NtcI5w== (our end point is public and accessible w/ the function key included in HTTP query)
You can then pass query parameters, such as name, etc...
-
for an API call testing locally this works nicely, I think it's hard-coded in the example that it needs immediately after the URL
/api/HttpExample?name=YourNameHere&code=...
Run the following to enable CI and get your webhook
az functionapp deployment container config --enable-cd --query CI_CD_URL --output tsv --name r-helloworld-dev --resource-group asc-etienne-rg
- Navigate to Docker hub and associate this as a webhook for your repository. https://hub.docker.com/repository/docker/atnjqt/azurefunctionsimage/webhooks
With the webhook set, Azure Functions redeploys your image whenever you update it in Docker Hub.
Go ahead and rebuild your image
- how does tags work w/ CI? I think I specific earlier on ...
The idea I think is to pass a query string to the API that contains all the necessary BCRA columns...
- More info on integration w/ Qualtrics API Common Use Cases here: https://www.qualtrics.com/support/integrations/api-integration/common-api-use-cases/