Can't locate Maven package feed with Deploy to Azure Web App

I have the following setup:

  • A Tomcat application, inside a app.2.1.7.war style file.
  • A Jfrog Artifactory repository where the .war file is stored.
  • Maven tool for deployment to Artifactory
  • An Azure Web App service hosting the application at C:\site\wwwroot\webapps\ROOT.war. This machine is a windows machine.

The main goal I’m trying to accomplish is moving the war file from jfrog over to the Azure App Service, and change the name from app.2.1.7.war into ROOT.war.

I’m trying to use Octopus Deploy to deploy the application, but I’m running into the following main roadblock:

I can’t set the Azure Web App computer as a deployment target, because it’s a windows machine and can’t use ssh, and because it’s a Web App service I don’t have access to install polling or listening tentacles on it.

I have gotten around this one by using offline package drop to transfer the .war file from jfrog to the computer hosting the Octopus Deploy server, but it changes the .war file into a very odd name and buries it inside the build number. If I can figure out how to use Offline-Package drop to deploy the .war file with the name ROOT.war and in a very expected place, I can use a powershell script in a different step of the process to ftp transfer the ROOT.war file over to the Azure Web App machine.

When I attempt to do other types of steps, the package feed drop-down doesn’t populate with Maven style package feeds. I’ve tried the following three steps which run into that problem:

  • “Deploy to IIS”
  • “Deploy a Package”
  • “Deploy an Azure Web App”

Hi rodesha, thanks for getting in touch.

Sorry to hear that you are having issues with this. I believe the Deploy Java Archive step could possibly be what you are looking for. It is an alternative to the Deploy a package step and has better support for WAR files and more importantly, should be able to work with a maven feed. The deploy a package step did not support maven feeds until version 2018.2.7. You can see the original issue for that here .

You should be able to upload the war file to the given web app using a bash script in a post-deployment script as part of the Deploy Java Archive step such as the following:

Post-deployment bash script

war=`get_octopusvariable "Octopus.Tentacle.CurrentDeployment.PackagePath"`
curl -T $war -u "ExampleWebApp\someuser:somepassword" \
    ftp://waws-prod-am2-189.ftp.azurewebsites.windows.net/site/wwwroot/webapps/ROOT.war

It goes without saying, but you would have to replace the credentials and ftp hostname with the details in the Azure portal for the given web app. You may also need to restart the web app for the changes to take effect, which should be possible using an Azure Powershell script.

Please let us know if this helps!

Regards,
Shaun Marx

1 Like

This looks extremely helpful and very promising.

I’m currently brushing up against "Skipping this step as no machines were found in the role: ", and in the process of troubleshooting that message.

I seem to have gotten past that error message, and I’ve successfully gotten Octopus Deploy to FTP the war file, and change the name to ROOT.war, to the Azure web app. I’ll come back and write out some of the details when I have time this week.

1 Like

Here’s the solution I settled on.

The first step is a “Deploy Java Archive” to a deployment target of “Offline Package Drop”, which saves the part I need, a .war file, inside some location on the local disk. I looked into setting up the Octopus Deploy local repository, but I couldn’t figure out how to get that to work with the setup I was attempting to accomplish because of various things that seemed to be getting in the way, such as:

  • I couldn’t do any of the typical deployment targets, and I didn’t know about the “you don’t need a deployment target” setting until I had already found a solution.
  • The development team I work with likes to do a lot of minor changes without increment the build number. So for example we may have a lot of builds on 1.2.7-SNAPSHOT that need to be redeployed as new versions, and Octopus Deploy seemed to want to assume that all artifacts named 1.2.7-SNAPSHOT in the repository were the same and so it didn’t need to get, and then upload, a fresh file.

So anyway, the “Deploy Java Archive” process step puts the whateverwhatever.war file somewhere on the local file system, and then I wrote a PS script to take it from there.

At the top of the script, the place where it chooses where to deploy is via the variable $AzureSlotName, and that’s an Octopus Deploy variable that is dependent on the environment Octopus Deploy is deploying to.

The PS script is a “Azure Powershell Script” process step, and it uses the Azure service principal for permissions. The first thing it does is get the ftp username/password/url from the Azure publishing profile and saves those to variables. Then it uses those credentials to delete the ROOT.war file in the wepapps folder on the Azure Web App. I found that if I tried to simply overwrite the ROOT.war file, the Azure kudu thing wouldn’t always unpack the ROOT.war file correctly and the app was sometimes unstable.

After deleting the ROOT.war file, the PS script waits around for kudu to delete the ROOT directory, then uploads the application.war file as ROOT.war into the Azure Web App webapps slot. I actually wrote a script to recursively FTP delete the ROOT directory, but it would sometimes clash with the Kudu. When I deleted the ROOT.war file before attempting to delete the ROOT directory, the recursive FTP delete script would scan folders then send FTP commands to delete files and occasionally the file would be removed by Kudu before the FTP command got to it, which would cause the FTP server to send back some 5xx error. The environment running the PS script would then crash, and it was too much of a headache to deal with the combination of the OD script running environment + recursion + PS + FTP 5xx errors.

The other problem I’d run into, if I ran the recursively FTP delete on the ROOT folder first, was that the Azure kudu tool would sometimes reconize that some files were missing in the ROOT folder and start unpacking the ROOT.war into it, which would down the road cause complete app instability. So what I settled on was removing the ROOT.war file, then waiting up to around 3 minutes for the Kudu to remove the ROOT folder, and if it didn’t then have the recursively delete script kick in. I ended up canning even that, however, because it was just too much of a headache to include that as well in the script, so I just assumed that Kudu would do its job and if it failed I’d expect a human to manually fix it.

So after the Kudu removes the ROOT folder, the remaining part of the script gets the .war file and uploads it via FTP to the webapps folder.

Here’s the full contents of the PS script. There’s 5 parts:

  1. Get FTP Credential info
  2. Scan the directory (I don’t even need this part in anymore, but I’m somewhat scared to take it out)
  3. Delete ROOT.war
  4. Wait around
  5. Deploy ROOT.war
    #Azure version of "Delete, Wait, Upload - FTP"

    ######################################
    ###Connect and get connection details
    ######################################

    $publishProfileStr = Get-AzureRmWebAppSlotPublishingProfile -Name $AzureAppName -ResourceGroupName $AzureResourceGroupName -Slot $AzureSlotName -OutputFile null

    $xmlvar = New-Object -TypeName System.Xml.XmlDocument
    $xmlvar.LoadXml($publishProfileStr)

    $ftpUsername = $xmlvar.SelectNodes("//publishProfile[@publishMethod=`"FTP`"]/@userName").value
    $ftpPassword = $xmlvar.SelectNodes("//publishProfile[@publishMethod=`"FTP`"]/@userPWD").value
    $ftpUrl = $xmlvar.SelectNodes("//publishProfile[@publishMethod=`"FTP`"]/@publishUrl").value + "/webapps/"

    $ftpCreds = New-Object System.Net.NetworkCredential($ftpUsername, $ftpPassword)

    #####################
    ### Look at directory
    #####################

    # Get info about directory, using ftp creds
    try
    {
        $listRequest = [Net.WebRequest]::Create($ftpUrl)
        $listRequest.Method = [System.Net.WebRequestMethods+FTP]::ListDirectoryDetails
        $listRequest.Credentials = $ftpCreds
        
        $lines = New-Object System.Collections.ArrayList
        
        $listResponse = $listRequest.GetResponse()
        $listStream = $listResponse.GetResponseStream()
        $listReader = New-Object System.IO.StreamReader($listStream)
        
        while (!$listReader.EndOfStream -and $listReader -ne $null)
        {
            $line = $listReader.ReadLine()
            $lines.Add($line) | Out-Null
        }
    }
    catch
    {

    }
    finally
    {
        if ($listReader -ne $null)
        {
            $listReader.Dispose()
        }
        if ($listStream -ne $null)
        {
            $listStream.Dispose()
        }
        if ($listResponse -ne $null)
        {
            $listResponse.Dispose()
        }
    }

    $fileCount = $lines.Count

    ###################
    ### Delete ROOT.war
    ###################

    foreach($line in $lines)
    {
        Write-Host $line
    }

    try
    {
        $deleteFileUrl = $ftpUrl + "ROOT.war"
        $deleteRequest = [Net.WebRequest]::Create($deleteFileUrl)
        $deleteRequest.Credentials = $ftpCreds
        $deleteRequest.Method = [System.Net.WebRequestMethods+FTP]::DeleteFile
        $deleteRequest.GetResponse() | Out-Null
    }
    catch
    {
        Write-Host "Couldn't find ROOT.war at location."
    }

    #############################################################################################
    ### Wait around for 2-5 minutes, occationally checking to see whether the ROOT folder is gone
    #############################################################################################

    $retries = 0
    $fileCount = 1

    while($fileCount -gt 0 -and $retries -lt 6)
    {
        Write-Host "Attempt: $retries"

        ##########################
        ### Checking the directory
        ##########################

        try
        {
            $listRequest = [Net.WebRequest]::Create($ftpUrl)
            $listRequest.Method = [System.Net.WebRequestMethods+FTP]::ListDirectoryDetails
            $listRequest.Credentials = $ftpCreds
            
            $lines = New-Object System.Collections.ArrayList
            
            $listResponse = $listRequest.GetResponse()
            $listStream = $listResponse.GetResponseStream()
            $listReader = New-Object System.IO.StreamReader($listStream)
            
            while (!$listReader.EndOfStream -and $listReader -ne $null)
            {
                $line = $listReader.ReadLine()
                $lines.Add($line) | Out-Null
            }
        }
        catch
        {

        }
        finally
        {
            if ($listReader -ne $null)
            {
                $listReader.Dispose()
            }
            if ($listStream -ne $null)
            {
                $listStream.Dispose()
            }
            if ($listResponse -ne $null)
            {
                $listResponse.Dispose()
            }
        }

        $fileCount = $lines.Count
        Write-Host "Files Count: $fileCount"

        
        $retries = $retries + 1
        Start-Sleep -s 15
    }

    Write-Host "Done retrying, files counted: $fileCount"

    ############################
    ### Deploy the ROOT.war file
    ############################

    $buildVersion = $OctopusParameters["Octopus.Release.Number"]
    $packageRootPath = "C:\Octopus\Packages\TempTestDeploy\Dev\MyApplication\$buildVersion\Packages"

    Write-Output "Package Root Path: $packageRootPath"


    $likeFileName = "com.company.app.*.war"

    $rootWarUrl = $ftpUrl + "ROOT.war"

    $file = Get-ChildItem -Path $packageRootPath -File
    Write-Output "File: $file"

    $warPath = $file.FullName

    Write-Output "war file path: $warPath"
    try{
        $webClient = New-Object -TypeName System.Net.WebClient
        $webClient.Credentials = New-Object System.Net.NetworkCredential($ftpUsername, $ftpPassword)
        $uri = New-Object System.Uri($rootWarUrl)
        Write-Output "Uploading war file to $($uri.AbsoluteUri)"
        $webClient.UploadFile($uri, $warPath)
    }
    catch{
        Write-Error "Something went wrong: $_"
    }
    finally {
        Write-Output "Done, disposing webClient."
        $webClient.Dispose()
    }

Note: the “preformatted text” button didn’t work at all. Three back-tick characters at beginning and end did.

Hi Rodesha, glad to hear you managed to get this working, although it sounds like it was more tedious than it should have been.

We appreciate you taking the time to provide the details. Hopefully, this can be made easier in the near future.

Regards,
Shaun Marx