Tech fail: deploying to blobs

Maybe this is a rough draft. I wanted to write this all down before I forgot. Long story short, these things don’t go as well together as you might expect:

  • Azure blob storage
  • A “static” website that relies on Javascript
  • Azure DevOps Release Pipelines

I burned a couple of days on this foolishness. Given that cloud tools are always changing, it’s worth noting that I did it in the last couple days of June 2020 and your results will vary.

Original plan

I have an Angular 9 application that builds nicely in a Build Pipeline. That went rather well, so I had useful Build Artifacts in my output. The idea was to use a Release Pipeline to shove my dist folder into Azure Blob Storage using the “$web” container convention.

Therefore, my Release Pipeline should be just an Azure File Copy task, right? Wrong.

After a tremendous amount of fussing around, I discovered that when I completed a Release I could access index.html through my web browser (and curl and whatever other means) but it was looking blank because it couldn’t load any of the bundled Javascript files. And it couldn’t load the bundled Javascript files because the Content-Type of the blobs they were stored in was set to “application/plain”.

I googled around a lot, and found helpful advice to add a switch like /ApplicationType to the Azure File Copy task, but that was for a long-past version of the Task. (Maybe I could have used it anyway. IDK.) I also found some advice to set up a file called AzCopyConfig.json that the azcopy.exe executable would automatically interpret to map file extensions to Content-types, but [shrug emoji] after jumping through some hoops that didn’t seem to work either.

What did work finally was writing a little PowerShell script that did something like this:

$container.CloudBlobContainer.Uri.AbsoluteUri
    if ($container) {
        $filesToUpload = Get-ChildItem $sourceFileRootDirectory -Recurse -File

        foreach ($x in $filesToUpload) {
            $targetPath = ($x.fullname.Substring($sourceFileRootDirectory.Length + 1)).Replace("\", "/")
            $extn = $x.extension
            $contentType = ''
            switch ($extn) {
              ".json" { $contentType = "application/json" }
              ".js" { $contentType = "application/javascript" }
              ".svg" { $contentType = "image/svg+xml" }
              ".html" { $contentType = "text/html" }
              ".htm" { $contentType = "text/html" }
              ".png" { $contentType = "image/png" }
              ".css" { $contentType = "style/css" }
              ".ico" { $contentType = "image/x-icon" } # or "image/vnd.microsoft.icon"
              ".txt" { $contentType = "text/plain"}
             Default { $contentType = "" }
            }

            #Write-Verbose "Uploading $("\" + $x.fullname.Substring($sourceFileRootDirectory.Length + 1)) to $($container.CloudBlobContainer.Uri.AbsoluteUri + "/" + $targetPath)"
            Write-Verbose "Uploading $($x.fullname) to $($container.CloudBlobContainer.Uri.AbsoluteUri + "/" + $targetPath) as $($contentType)"
            Set-AzStorageBlobContent -File $x.fullname -Container $container.Name -Blob $targetPath -Context $ctx -Force:$Force -Properties @{"ContentType" = $contentType} | Out-Null
        }
    }