How to automatically install specific Site Extensions for Azure Web Apps - azure-web-app-service

I'd ideally like to declare in my applications code or .deployment file for particular Site Extensions to be installed or updated when my code is pushed to the scm Kudu site. Is this possible?
Specifically I'd like the Microsoft.ApplicationInsights.AzureWebSites site extension to be automatically installed as part of all the sites I deploy without having to manually browse to the scm site and install it from the gallery.

You cannot install site extensions as part of Kudu git deployment, but you can do it as part of an ARM template. You can find a complete sample here.
This is basically the same as for any other site configurations. e.g. setting App Settings, app service tier, turning on logging, WebSockets, ... All those things sit outside of Kudu deployments, but can be achieved using an ARM template which has all your desired site state.

Another would be to make a WebJob that can create the SiteExtension folder and then copy your SiteExtension files in to that folder. As part of your deployment you would just include the webjob.
We do something similar with how Stackify APM is installed from a site extension in to Azure Web Apps. Site extension creates a WebJob and the WebJob actually updates the site extension applicationHost transform based on some conditional items.

It can be done using powershell, but it's a bit hacky (example using staging slot named staging):
Write-Host "Setting appsettings for Stackify on $AzureWebSite"
$hash = #{}
$hash['Stackify.ApiKey'] = "$licenceKey"
$hash['Stackify.Environment'] = "$environment"
$hash['Stackify.AppName'] = "$BaseWebAppName"
if ($loadCertificates -eq 'True')
{
$hash['WEBSITE_LOAD_CERTIFICATES'] = "*"
}
Set-AzureWebsite -Name $AzureWebSite -Slot staging -AppSettings $hash
### Install Extension for Azure App###
Write-Host "Installing Stackify on $AzureWebSite"
$Kudu = "https://" + $AzureWebSite + "-staging.scm.azurewebsites.net/api/extensionfeed" # Here you can get a list for all Extensions available.
$InstallNRURI = "https://" + $AzureWebSite + "-staging.scm.azurewebsites.net/api/siteextensions" # Install API EndPoint
$slot = Get-AzureWebsite $AzureWebSite -Slot staging
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $slot.PublishingUsername,$slot.PublishingPassword)))
$invoke = Invoke-RestMethod -Uri $Kudu -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method get ###-InFile $filePath -ContentType "multipart/form-data"
$id = ($invoke | ? {$_.id -match "stackify*"}).id ### Searching for Stackify ID Extension
try {
$InstallStackify = Invoke-RestMethod -Uri "$InstallNRURI/$id" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method Put
$Status = ($InstallStackify.provisioningState).ToString() + "|" + ($InstallStackify.installed_date_time).ToString() ### Status
Write-Output "Stackify Installation Status : $Status"
}
catch{$_}
Restart-AzureWebsite $AzureWebSite -Slot staging

Related

Azure Devops Release Pipeline - Deploy single file to Azure App Service

I have a single file that I need to deploy to an existing App Service (that already contains an application, deployed previously through another pipeline).
I would like to avoid using the FTP task for that.
Is there a way to deploy a single file to a specific folder in an App Service via a DevOps Pipeline?
Actually FTP seems to be the easiest way to deploy single file. However if you don't want to use it you can use KUDU API. What you need is to wrap it in powershell script:
$username = '$(username)'
$password = '$(password)'
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
$apiUrl = "https://$(siteName).scm.azurewebsites.net/api/vfs/site/your-file"
$filePath = "$(System.DefaultWorkingDirectory)/your-file"
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Authorization", ("Basic {0}" -f $base64AuthInfo))
$headers.Add("If-Match", "*")
Invoke-RestMethod -Uri $apiUrl -Headers $headers -Method PUT -InFile $filePath -ContentType "multipart/form-data"
Here you find info how to get credentials.
But if this is one time task you may use drag & drop from KUDU panel as it is mentioned here

How to Copy a Folder from a TEST App Service to a LIVE App Service on Azure

In my Azure DevOps Pipeline I would like to copy a folder e.g. Media from 1 environment/app service, say TEST to another environment/app service say Live. The Media folder in TEST may get updated AFTER the Ci/cd build has been deployed to the TEST environment - just to exclude answers that might suggest putting it in Git and including it as a Build artifact.
EDIT - Clarification on using the accepted answer.
My repo contains the given powershell script in the accepted answer as :
azure/Copy-Media-Test-To-Live.ps1
I then add the azure folder as an artifact in the build pipeline i.e.
Edit azure-pipelines.yml and Add:
- task: PublishPipelineArtifact#1
inputs:
path: $(System.DefaultWorkingDirectory)/azure/
artifact: azure
In the release pipeline - reference the script to perform the copy:
steps:
- task: AzurePowerShell#4
displayName: 'Azure PowerShell script: FilePath'
inputs:
azureSubscription: 'Your subscription '
ScriptPath: '$(System.DefaultWorkingDirectory)/_your-artifact-path/azure/Copy-Media-Test-To-Live.ps1'
azurePowerShellVersion: LatestVersion
Any application running in an App Service Environment can be managed via Kudu. Kudu has an API for downloading a ZIP compressed archive of any folder currently deployed to an application. It can be accessed with a GET request to:
https://{{YOUR-APP-NAME}}.scm.azurewebsites.net/api/zip/site/{{FOLDER}}
You can use the Invoke-WebRequest cmdlet in PowerShell to pull this content to local storage.
You do need to authenticate to use the Kudu API, which is easy in a browser, but when automating is a little more involved. Please see the following article which details how to retrieve and present a Basic Authorization header, as well as demonstrating how to use the command API to extract a ZIP file using the Invoke-RestMethod cmdlet. Your service principal will need at least contributor access to your applications to get deployment credentials to use in the API calls.
https://blogs.msdn.microsoft.com/waws/2018/06/26/powershell-script-to-execute-commands-in-scm-website-on-all-instances/
EDIT (Include worked example script):
If you have multiple subscriptions and the context has not been set properly in the deployment runtime environment, you may need to use Set-AzContext -Subscription "<SubsciptionName>" to set the context for getting the WebApp
$srcResGroupName = "Test"
$srcWebAppName = "tstest12"
$srcDirectory = "/site/wwwroot/myFolder/"
$dstResGroupName = "Test"
$dstWebAppName = "tstest123"
$dstDirectory = "/site/wwwroot/myFolder/"
# Get publishing profile for SOURCE application
$srcWebApp = Get-AzWebApp -Name $srcWebAppName -ResourceGroupName $srcResGroupName
[xml]$publishingProfile = Get-AzWebAppPublishingProfile -WebApp $srcWebApp
# Create Base64 authorization header
$username = $publishingProfile.publishData.publishProfile[0].userName
$password = $publishingProfile.publishData.publishProfile[0].userPWD
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
$apiBaseUrl = "https://$($srcWebApp.Name).scm.azurewebsites.net/api"
# Download the ZIP file to ./tmp.zip
Invoke-RestMethod -Uri "$apiBaseUrl/zip$($srcDirectory)" `
-Headers #{UserAgent="powershell/1.0"; `
Authorization=("Basic {0}" -f $base64AuthInfo)} `
-Method GET `
-OutFile ./tmp.zip
# Get publishing profile for DESTINATION application
$dstWebApp = Get-AzWebApp -Name $dstWebAppName -ResourceGroupName $dstResGroupName
[xml]$publishingProfile = Get-AzWebAppPublishingProfile -WebApp $dstWebApp
# Create Base64 authorization header
$username = $publishingProfile.publishData.publishProfile[0].userName
$password = $publishingProfile.publishData.publishProfile[0].userPWD
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
$apiBaseUrl = "https://$($dstWebApp.Name).scm.azurewebsites.net/api"
# Upload and extract the ZIP file
Invoke-RestMethod -Uri "$apiBaseUrl/zip$($dstDirectory)" `
-Headers #{UserAgent="powershell/1.0"; `
Authorization=("Basic {0}" -f $base64AuthInfo)} `
-Method PUT `
-InFile ./tmp.zip `
-ContentType "multipart/form-data"

Deploy Code from GitLab Repository to Azure Web App using PowerShell and Azure CLI

I would like to setup continuous deployment from a GitLab repository to an Azure App using a PowerShell script and the Azure CLI. There is already an answer for doing this using the Azure RM module and Windows PowerShell, but as these are now deprecated, I am looking specifically for a solution that uses the new Az module and PowerShelll Core.
The solution should give a a PowerShell (Core) script to setup a Continuous Deployment directly from GitLab to Azure. It must make use of the Az module. Once the setup script is run, each subsequent commit/merge to the GitLab repository should then automatically be deployed to Azure. Preferably, this PowerShell script should work for both public and private repositories that are hosted on GitLab, but I'm willing to accept solutions that only work on public repositories.
I was playing around with gitlab and kudu rest APIs, and figured out how to automate manual solution you mentioned. The only extra step is to add gitlab api token to your code, but you just do it once for all projects. You can get it from your gitlab account settings under "Access Tokens". Some other notes:
To interact with kudu api the script is using autogenerated
deployment credentials. But you can create a separate user for
deployment and use it in all other projects (skipping that step). You
can do it in azure CLI:
az webapp deployment user set --user-name someUser --password somepassword
GitLab API is using project ID, not the project name. The script is
trying to retrieve project id automatically from repo URL, but you
might copy/paste it from the project general setting on gitlab to be
safe.
This solution works with private repos too. The only thing you'll see
some error while creating a resource (because ssh key is not set up
yet). But after script is completed it should be fine, so ignore the
error. For public repos you can skip that key set up stuff at all
Here is the script:
function log {param($memo); Write-Host "[$((get-date).ToString("HH:mm:ss"))]: $memo" -ForegroundColor Green}
# =============== App and GitLab settings ==============
$webapp="geekscodeStackOverflow"
$resgroup = $webapp + "Group"
$plan = $webapp + "Plan"
$location="centralus"
$gitToken = "yourGitLabTokenHere"
$repoUrl = "https://gitlab.com/MagicAndi/geekscode.net"
# $projID = "99..."
# ============== DEPLOYMENT SCRIPT ==========================#
log "Setting up the app on azure"
New-AzResourceGroup -Name $resgroup -Location $location
New-AzAppServicePlan -Name $plan -Location $location -ResourceGroupName $resgroup -Tier Free
New-AzWebApp -Name $webapp -Location $location -AppServicePlan $plan -ResourceGroupName $resgroup
$appInfo = Get-AzWebApp -Name $webapp
$appRef = #{Name=$appInfo.Name; ResourceGroupName = $appInfo.ResourceGroup}
if(!$appInfo){Write-Host "app deployment failed" -ForegroundColor Red; return} else {Write-Host "App created:" -ForegroundColor Green}
# ================= linking web app to gitlab =========================
# you can do this manually: app dashboard / Deployment Centrer / External / App Service Kudu / git
log "setting up deployment "
$deployment = #{
PropertyObject = #{ repoUrl = $repoUrl; branch = "master"; isMercurial= $false; isManualIntegration = $true }
ResourceGroupName = $appInfo.ResourceGroup
ResourceType = "Microsoft.Web/sites/sourcecontrols"
ResourceName = $appInfo.Name + "/web"
ApiVersion = "2018-02-01"
}
# you'll get error on this step for private repos because the key is not set up yet. You can ignore that error
Set-AzResource #deployment -Force
log "Extracting Deployment credentials"
# you can also create a user credentials in AZ CLI and skip this or manually get it in App's deployment center
$prof = Get-AzWebAppPublishingProfile #appRef | Select-Xml -XPath "//*[#publishMethod='MSDeploy']"
$deployCreds = $prof.node.userName + ":" + $prof.node.userPWD
log "Extracting Deployment key"
# Can skip for public repors
$keyUrl = "https://$webapp.scm.azurewebsites.net/api/sshkey?ensurePublicKey=1"
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($deployCreds))
$head = #{Authorization=("Basic {0}" -f $base64AuthInfo)}
$deployKey = Invoke-RestMethod -Uri $keyUrl -Headers $head -Method Get
#============== Setting Up GIT LAB ================ #
$gitApi = "https://gitlab.com/api/v4"
$gitHead = #{'PRIVATE-TOKEN'= $gitToken; 'Content-Type'='application/json'}
# looking up project id by user/repo name. You can skip that and get the id from project general setting on GitLab
$repo = $repoUrl.Split("/")[-2,-1] -join "%2F"
$project = Invoke-RestMethod -Uri "$gitApi/projects/$repo" -Headers $head
$projID = $project.id
log "Setting up $repoUrl (project id $projID)"
# --- Adding deploy key to GitLab project (public repos can skip) ---
# You can copy the key manually - Go to Project / Settings / Repository / Deploy Keys
log "Adding deploy keys to GitLab project"
$keyBody = #{title="Azure_Key";key=$deployKey; can_push=$true} | ConvertTo-Json
Invoke-RestMethod "$gitApi/projects/$projID/deploy_keys/" -Headers $gitHead -Body $keyBody -Method Post
log "Setting up a webhook"
# this can be set manualy - go to Project / Settings / Integrations.
$whBody = #{url = "https://$deployCreds#$webapp.scm.azurewebsites.net/deploy"} | ConvertTo-Json
Invoke-RestMethod -Uri "$gitApi/projects/$projID/hooks/" -Headers $gitHead -Body $whBody -Method Post
log "deployment completed `ncheck out your app at https://$webapp.azurewebsites.net"
Try the command below for Az, my repository is public, it works fine on my side.
$gitrepo="your git repository url"
$webappname="joyazapp"
$location="centralus"
New-AzResourceGroup -Name joyazgroup -Location $location
New-AzAppServicePlan -Name joyazplan -Location $location -ResourceGroupName joyazgroup -Tier Free
New-AzWebApp -Name joyazapp -Location $location -AppServicePlan joyazplan -ResourceGroupName joyazgroup
$PropertiesObject = #{
repoUrl = "$gitrepo";
branch = "master";
isManualIntegration = $false
}
Set-AzResource -PropertyObject $PropertiesObject -ResourceGroupName joyazgroup -ResourceType Microsoft.Web/sites/sourcecontrols -ResourceName $webappname/web -ApiVersion 2018-02-01 -Force

Azure Kudu API - Can't Edit Web.config via Azure Automation

I am attempting to edit my WebApp's Web.config using the Kudu WebApp API and an Azure Automation Runbook.
Logging into the WebApp via the Kudu interface and executing the code in the PowerShell debugger removes the XML node as expected:
$username = "`$myusername"
$password = "123456"
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
$ProgressPreference="SilentlyContinue"
$apiUrl = "https://mysite.scm.azurewebsites.net/api/command"
$pscommand = $webConfigPath = "D:\home\site\wwwroot\Web.config"
[xml] $webConfigXML = Get-Content $webConfigPath
#remove the following handler in the <httpHanderls> section in the web.config
$targetName = "Sitecore.FeedRequestHandler"
$nodePath = "configuration/system.webServer/handlers/add[#name='{0}']" -f $targetName
$node = $webConfigXML.SelectSingleNode($nodePath)
if($node -ne $null)
{
$webConfigXML.configuration.'system.webServer'.handlers.RemoveChild($node)
}
$webConfigXML.Save($webConfigPath)
$commandBody = #{
command = "powershell -command `"$pscommand`""
}
Invoke-RestMethod -Uri $apiUrl -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method POST -ContentType "application/json" -Body (ConvertTo-Json $commandBody)
However, if I try to run this via PowerShell ISE or an Azure Automation Runbook, it produces the following errors:
Program 'Web.config' failed to run: Access is deniedAt line:1 char:1..
Get-Content : Cannot find path 'D:\home\site\wwwroot\Web.config'
because it does not exist.
Any ideas on how I can edit the XML of the Web.config via an Azure Automation Runbook?
Looking at your code, I think it is confusing two model:
Some of it is written to call Kudu from the outside via http, when you call the /api/command API.
Some of it is written to run directly from within Kudu, when you try to access D:\home\site\wwwroot\Web.config on the local file system.
2 has no chance of working if you run anywhere other than in Kudu.
What you need to do instead is use the Kudu vfs API to read the file, modify it locally, and then save it back using the vfs API.

Deploy to azure functions using powershell

Is there any way, I can deploy to azure functions using powershell scripts? CI will not work for us because we use octopus deploy to deploy to all of our production services. So it would be beneficial if there is a way to deploy using powershell scripts.
Thanks!
You can deploy functions to Azure using the Kudu REST API. You can also see some code/samples of doing this in our templates repository. In this code sample, you can see how our test script calls out to the Kudu Rest apis to deploy a zip to the Function App.
The folder structure for functions is a function per folder. You need to deploy your Function folders to ./site/wwwroot on the Function App. You also need to add any app settings which might contain your secrets if you add any new bindings between updates.
The PowerShell code would look something along the lines of:
$apiUrl = $config.scmEndpoint + "/api/zip/"
if ($destinationPath)
{
$apiUrl = $apiUrl + $destinationPath
}
$response = Invoke-RestMethod -Uri $apiUrl -Headers #{Authorization=("Basic {0}" -f $config.authInfo)} -Method PUT -InFile $zipFilePath -ContentType "multipart/form-data"
Just in case there are people like me who need step by step solutions. Here is the procedure to deploy azure functions using powershell (non ARM way)
Create an azure function with the following structure
myFunctionName(Folder)
|
|_ function.json (contains info on input, output, trigger)
|_ run.csx (contains code)
|_ [OPTIONAL] project.json (for nuget packages)
|_ [OPTIONAL] bin(Folder)
|_ all custom DLLs go in this folder
Create a zip of the myFunctionName folder - let's name it my.zip. Make sure that after zipping my.zip contains the myFunctionName folder and all its contents
Find your publish profile username and password as described here, namely
$creds = Invoke-AzureRmResourceAction -ResourceGroupName YourResourceGroup -ResourceType Microsoft.Web/sites/config -ResourceName YourWebApp/publishingcredentials -Action list -ApiVersion 2015-08-01 -Force
$username = $creds.Properties.PublishingUserName
$password = $creds.Properties.PublishingPassword
and then invoke the Kudu REST API using powershell as follows
$username = '<publish username>' #IMPORTANT: use single quotes as username may contain $
$password = "<publish password>"
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
$apiUrl = "https://<yourFunctionApp>.scm.azurewebsites.net/api/zip/site/wwwroot"
$filePath = "<yourFunctionName>.zip"
Invoke-RestMethod -Uri $apiUrl -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method PUT -InFile $filePath -ContentType "multipart/form-data"
Go to <yourFunctionApp>.scm.azurewebsites.net -> Debug menu at the top -> CMD. In the page that appears, navigate to site -> wwwroot. You should see the contents of your zip file extracted there and you can also verify that your azure function is available in the azure portal.
REFERENCES
https://github.com/projectkudu/kudu/wiki/REST-API#sample-of-using-rest-api-with-powershell
http://markheath.net/post/deploy-azure-functions-kudu-powershell
In addition to what Chris describes, there is a first class ARM API you can use to deploy functions. Here is what it looks like in PowerShell:
Function DeployHttpTriggerFunction($ResourceGroupName, $SiteName, $FunctionName, $CodeFile, $TestData)
{
$FileContent = "$(Get-Content -Path $CodeFile -Raw)"
$props = #{
config = #{
bindings = #(
#{
type = "httpTrigger"
direction = "in"
webHookType = ""
name = "req"
}
#{
type = "http"
direction = "out"
name = "res"
}
)
}
files = #{
"index.js" = $FileContent
}
test_data = $TestData
}
New-AzureRmResource -ResourceGroupName $ResourceGroupName -ResourceType Microsoft.Web/sites/functions -ResourceName $SiteName/$FunctionName -PropertyObject $props -ApiVersion 2015-08-01 -Force
}
See https://github.com/projectkudu/kudu/wiki/Functions-API for information about the underlying API.
You can also use Azure CLI 2.0 + Azure Function CLI to deploy Azure functions form commandline/powershell
Azure CLI API can be used to provision a function app, using command
az functionapp create --name $functionappName --resource-group $resourceGroup --storage-account $storageAccountName --consumption-plan-location $consumptionPlanLocation
And apply application setting
az functionapp config appsettings set --name $functionappName --resource-group $resourceGroup --settings "test=value"
And Azure Function CLI api can be used to deploy the functions you have
func azure functionapp publish <azurefunctionapp>
Handy tools!
In addition to all of the above, we have released a Preview module for Azure Functions (https://www.powershellgallery.com/packages/Az.Functions/0.0.1-preview).
Currently, this module contains cmdlets to manged function apps and function app plans. I have opened an issue to request creating a cmdlet to deploy a function app. If you are interested in this feature, please vote up at https://github.com/Azure/azure-powershell/issues/10966.
To install the Azure Functions (Az.Functions) module, run the following command from the latest version of psws which can be downloaded at https://github.com/PowerShell/PowerShell/releases.
Install-Module -Name Az.Functions -AllowPrerelease
Please give it a try and send us feedback at https://github.com/Azure/azure-powershell/issues. When opening an issue, please make sure [Az.Functions] is included in the title.
Cheers,
Francisco

Resources