Azure DevOps: Populating secure file references with job matrix variables

Viewed 513

For context, I am trying to use an Azure build pipeline to build multiple flavors of an Android app. Each flavor has its own separate signing keystore, and all of those keystores are stored in my 'secure files' in the library.

However, when I try to dereference the $(Keystore) variable during the 'android signing' task, it doesn't seem to recognize that that is a variable that exists, and tries instead to locate a file called '$(Keystore)'

Am I doing something wrong here? This seems like it should work.

A sanitized example looks like this:

# Android
# Build your Android project with Gradle.
# Add steps that test, sign, and distribute the APK, save build artifacts, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/android

trigger:
- feat/ci-setup

pool:
  vmImage: 'macos-latest'

variables:
  ${{ if startsWith(variables['build.sourceBranch'], 'refs/heads/feat/') }}: 
    Branch_Type: 'feature'
  ${{ if startsWith(variables['build.sourceBranch'], 'refs/heads/hotfix/') }}: 
    Branch_Type: 'hotfix'
  ${{ if startsWith(variables['build.sourceBranch'], 'refs/heads/release/') }}: 
    Branch_Type: 'release'
  ${{ if eq(variables['Branch_Type'], 'release') }}: 
    Configuration: 'release'
    ConfigurationCC: 'Release'
  ${{ if ne(variables['Branch_Type'], 'release') }}: 
    Configuration: 'debug'
    ConfigurationCC: 'Debug'

jobs:
- job: Build
  variables:
  - group: android_keystores
  strategy:
    maxParallel: 2
    matrix:
      Flavor_1:
        AppFlavor: '1'
        AppFlavorCC: '1'
        Keystore: 'flavor1.keystore'
        KeyAlias: 'flavor1'
        KeystorePass: '$(flavor1_storepass)'
        KeyPass: '$(flavor1_keypass)'
      Flavor_2:
        AppFlavor: '2'
        AppFlavorCC: '2'
        Keystore: 'flavor2.keystore'
        KeyAlias: 'flavor2'
        KeystorePass: '$(flavor2_storepass)'
        KeyPass: '$(flavor2_keypass)'

  steps:
  - task: Gradle@2
    inputs:
      workingDirectory: ''
      gradleWrapperFile: 'gradlew'
      gradleOptions: '-Xmx3072m'
      publishJUnitResults: false
      tasks: 'assemble$(AppFlavorCC)$(ConfigurationCC)'

  - task: AndroidSigning@3
    displayName: Signing .apk
    inputs:
      apkFiles: 'app/build/outputs/apk/$(AppFlavor)/$(Configuration)/*.apk'
      apksign: true
      apksignerKeystoreFile: '$(Keystore)'
      apksignerKeystorePassword: '$(KeystorePass)'
      apksignerKeystoreAlias: '$(KeyAlias)'
      apksignerKeyPassword: '$(KeyPass)'
      zipalign: true

  - task: Bash@3
    displayName: Move APK to Artifact Folder
    continueOnError: true
    inputs:
      targetType: 'inline'
      script: |
        mv \
        app/build/outputs/apk/$(AppFlavor)/$(Configuration)/*.apk \
        $(Build.ArtifactStagingDirectory)/$(ArtifactName)/

  - task: PublishBuildArtifacts@1
    displayName: Publish Build Artifacts
    inputs:
      PathtoPublish: '$(Build.ArtifactStagingDirectory)'
      ArtifactName: 'Blueprint-Build'
      publishLocation: 'Container'

But when the pipeline runs I am told this:

There was a resource authorization issue: "The pipeline is not valid. Job Build: Step AndroidSigning input keystoreFile references secure file $(Keystore) which could not be found. The secure file does not exist or has not been authorized for use. For authorization details, refer to https://aka.ms/yamlauthz."
2 Answers

Azure DevOps: Populating secure file references with job matrix variables

This is a limitation from the task itself.

When we test it with Classic mode, we could find out that the value of the option Keystore file could not be entered manually, we could only select a certain file through the drop-down menu:

enter image description here

That the reason why it doesn't seem to recognize that that is a variable that exists, and tries instead to locate a file called '$(Keystore)'.

To resolve this issue, you could change the task version from 3 to 1, which supports manual input:

enter image description here

And as another solution, you could also use the command line to sign the *.apk:

Android apk signing: sign an unsigned apk using command line

You're missing the step to download the Secure File. Unlike variable groups, you need to explicitly download them to have access via the secure file name.

You'll want to add something similar to the example task below to your steps to pull the secure file. Then, you'll access your secure file via NAME_PARAMETER.secureFilePath:

          - task: DownloadSecureFile@1
            displayName: "Download Keyfile 1"
            name: "YOUR_SECUREFILE_NAME"
            inputs:
                secureFile: keyfile1
          - task: AndroidSigning@3
            displayName: Signing .apk
            inputs:
                apkFiles: 'app/build/outputs/apk/$(AppFlavor)/$(Configuration)/*.apk'
                apksign: true
                apksignerKeystoreFile: '$(YOUR_SECUREFILE_NAME.secureFilePath)'
                apksignerKeystorePassword: '$(KeystorePass)'
                apksignerKeystoreAlias: '$(KeyAlias)'
                apksignerKeyPassword: '$(KeyPass)'
                zipalign: true
Related