androidfirebaseazureazure-devopsfirebase-app-distribution

Firebase App Distribution Login to firebase using gcloud service account on azure


I'm attempting to set up Firebase App Distribution in our Azure Pipeline for our app, using a service account for authentication. However, I'm encountering the following error:

  /bin/bash --noprofile --norc /Users/runner/work/_temp/9862a4f1-6542-4b7b-9dbb-9782561da5bb.sh Attempting Firebase authentication with service account... ##[debug]Agent environment resources - Disk: / Available 124774.09 MB out of 332471.96 MB, Memory: Used 7722.00 MB out of 14332.00 MB, CPU: Usage 99.30% Error: Failed to authenticate, have you run firebase login? Firebase authentication failed. ##[debug]Exit code 1 received from tool '/bin/bash' ##[debug]STDIO streams have closed for tool '/bin/bash' ##[error]Bash exited with code '1'.  

I have ensured that the service account key is provided, and the environment variable GOOGLE_APPLICATION_CREDENTIALS is set. Below is my pipeline YAML configuration:

  trigger: none pool: vmImage: 'macOS-latest' parameters: - name: appVariant displayName: 'App Variant' type: string default: '{{appName}}' values: - '{{appName}}' - 'BCDC' - name: firebaseEnv displayName: 'Firebase Environment' type: string default: 'UAT' values: - 'UAT' - 'Pilot' - name: testGroup displayName: 'Test Group' type: string default: 'Android_UAT_Testers' values: - 'Android_UAT_Testers' - 'Android_Pilot_Testers' - name: releaseNotes displayName: 'Release Notes' type: string default: 'No release notes provided.' stages: - stage: Build jobs: - job: BuildJob timeoutInMinutes: 120 variables: GRADLE_USER_HOME: $(Pipeline.Workspace)/.gradle steps: - task: CmdLine@2 inputs: script: "sudo rm -rf $ANDROID_HOME/ndk/ && sudo rm -rf $ANDROID_HOME/ndk-bundle/" - ${{ if eq(parameters.appVariant, '{{appName}}') }}: - ${{ if eq(parameters.firebaseEnv, 'UAT') }}: - task: Gradle@2 displayName: 'Gradle Compile {{appName}} for UAT' inputs: gradleWrapperFile: 'gradlew' tasks: 'assembleUatOne{{appName}}GoogleDebug' - ${{ if eq(parameters.firebaseEnv, 'Pilot') }}: - task: Gradle@2 displayName: 'Gradle Compile {{appName}} for Pilot' inputs: gradleWrapperFile: 'gradlew' tasks: 'assemblePilotOne{{appName}}GoogleDebug' - ${{ if eq(parameters.appVariant, 'BCDC') }}: - ${{ if eq(parameters.firebaseEnv, 'UAT') }}: - task: Gradle@2 displayName: 'Gradle Compile BCDC for UAT' inputs: gradleWrapperFile: 'gradlew' tasks: 'assembleUatOne{{appName}}BcdcGoogleDebug' - ${{ if eq(parameters.firebaseEnv, 'Pilot') }}: - task: Gradle@2 displayName: 'Gradle Compile BCDC for Pilot' inputs: gradleWrapperFile: 'gradlew' tasks: 'assemblePilotOne{{appName}}BcdcGoogleDebug' - task: CopyFiles@2 inputs: SourceFolder: $(Build.SourcesDirectory) contents: '**/*.apk' targetFolder: '$(build.artifactStagingDirectory)' overWrite: true - task: PublishBuildArtifacts@1 inputs: pathtoPublish: '$(Build.ArtifactStagingDirectory)/app/build/outputs/apk/' artifactName: 'apks' publishLocation: 'container' - task: CmdLine@2 displayName: 'Check Service Account and Credentials' inputs: script: | echo "$(MOBILEFIREBASEACCOUNTSERVICE)" > $(Pipeline.Workspace)/service-account.json export GOOGLE_APPLICATION_CREDENTIALS=$(Pipeline.Workspace)/service-account.json echo "GOOGLE_APPLICATION_CREDENTIALS is set to: $GOOGLE_APPLICATION_CREDENTIALS" echo "Service account JSON file:" cat $(Pipeline.Workspace)/service-account.json | jq '.' if [ -f "$(Pipeline.Workspace)/service-account.json" ]; then echo "Service account file exists." else echo "Service account file does NOT exist." exit 1 fi - task: CmdLine@2 displayName: 'Install Firebase CLI' inputs: script: 'npm install -g firebase-tools' - task: CmdLine@2 displayName: 'Authenticate Firebase CLI with Service Account' inputs: script: | echo "Attempting Firebase authentication with service account..." if npx firebase projects:list; then echo "Firebase authentication successful." else echo "Firebase authentication failed." exit 1 fi - task: CmdLine@2 displayName: 'Distribute APK to Firebase (UAT)' condition: eq('${{ parameters.firebaseEnv }}', 'UAT') inputs: script: | npx firebase appdistribution:distribute "$(build.artifactStagingDirectory)/app/build/outputs/apk/uatOne{{appName}}Google/debug/app-uat-one{{appName}}-google-debug.apk" \ --app $(FIREBASE_APP_ID) \ --groups ${{ parameters.testGroup }} \ --release-notes "${{ parameters.releaseNotes }}" \ --debug - task: CmdLine@2 displayName: 'Distribute APK to Firebase (Pilot)' condition: eq('${{ parameters.firebaseEnv }}', 'Pilot') inputs: script: | npx firebase appdistribution:distribute "$(build.artifactStagingDirectory)/app/build/outputs/apk/uatOne{{appName}}Google/release/app-uat-one{{appName}}-google-release.apk" \ --app $(FIREBASE_APP_ID) \ --groups ${{ parameters.testGroup }} \ --release-notes "${{ parameters.releaseNotes }}" \ --debug  

Error Details: It appears that Firebase CLI isn't authenticating properly using the service account credentials. The error suggests running firebase login, but since I'm using a service account, this shouldn't be necessary. What I've Tried: Verified the service account has sufficient permissions for Firebase App Distribution. Ensured that the environment variable GOOGLE_APPLICATION_CREDENTIALS is correctly set. Ran the command npx firebase projects:list to verify authentication, but it fails with the same error.


trigger: none

pool:
  vmImage: 'macOS-latest'

parameters:
  - name: appVariant
    displayName: 'App Variant'
    type: string
    default: '{{appName}}'
    values:
      - '{{appName}}'
      - 'BCDC'

  - name: firebaseEnv
    displayName: 'Firebase Environment'
    type: string
    default: 'UAT'
    values:
      - 'UAT'
      - 'Pilot'

  - name: testGroup
    displayName: 'Test Group'
    type: string
    default: 'Android_UAT_Testers'
    values:
      - 'Android_UAT_Testers'
      - 'Android_Pilot_Testers'

  - name: releaseNotes
    displayName: 'Release Notes'
    type: string
    default: 'No release notes provided.'

stages:
  - stage: Build
    jobs:
      - job: BuildJob
        timeoutInMinutes: 120
        variables:
          GRADLE_USER_HOME: $(Pipeline.Workspace)/.gradle

        steps:
          - task: CmdLine@2
            inputs:
              script: "sudo rm -rf $ANDROID_HOME/ndk/ && sudo rm -rf $ANDROID_HOME/ndk-bundle/"

          - ${{ if eq(parameters.appVariant, '{{appName}}') }}:
              - ${{ if eq(parameters.firebaseEnv, 'UAT') }}:
                  - task: Gradle@2
                    displayName: 'Gradle Compile {{appName}} for UAT'
                    inputs:
                      gradleWrapperFile: 'gradlew'
                      tasks: 'assembleUatOne{{appName}}GoogleDebug'

              - ${{ if eq(parameters.firebaseEnv, 'Pilot') }}:
                  - task: Gradle@2
                    displayName: 'Gradle Compile {{appName}} for Pilot'
                    inputs:
                      gradleWrapperFile: 'gradlew'
                      tasks: 'assemblePilotOne{{appName}}GoogleDebug'

          - ${{ if eq(parameters.appVariant, 'BCDC') }}:
              - ${{ if eq(parameters.firebaseEnv, 'UAT') }}:
                  - task: Gradle@2
                    displayName: 'Gradle Compile BCDC for UAT'
                    inputs:
                      gradleWrapperFile: 'gradlew'
                      tasks: 'assembleUatOne{{appName}}BcdcGoogleDebug'
              - ${{ if eq(parameters.firebaseEnv, 'Pilot') }}:
                  - task: Gradle@2
                    displayName: 'Gradle Compile BCDC for Pilot'
                    inputs:
                      gradleWrapperFile: 'gradlew'
                      tasks: 'assemblePilotOne{{appName}}BcdcGoogleDebug'

          - task: CopyFiles@2
            inputs:
              SourceFolder: $(Build.SourcesDirectory)
              contents: '**/*.apk'
              targetFolder: '$(build.artifactStagingDirectory)'
              overWrite: true

          - task: PublishBuildArtifacts@1
            inputs:
              pathtoPublish: '$(Build.ArtifactStagingDirectory)/app/build/outputs/apk/'
              artifactName: 'apks'
              publishLocation: 'container'

          - task: CmdLine@2
            displayName: 'Check Service Account and Credentials'
            inputs:
              script: |
                echo "$(MOBILEFIREBASEACCOUNTSERVICE)" > $(Pipeline.Workspace)/service-account.json
                export GOOGLE_APPLICATION_CREDENTIALS=$(Pipeline.Workspace)/service-account.json

                echo "GOOGLE_APPLICATION_CREDENTIALS is set to: $GOOGLE_APPLICATION_CREDENTIALS"
                echo "Service account JSON file:"
                cat $(Pipeline.Workspace)/service-account.json | jq '.'

                if [ -f "$(Pipeline.Workspace)/service-account.json" ]; then
                  echo "Service account file exists."
                else
                  echo "Service account file does NOT exist."
                  exit 1
                fi

          - task: CmdLine@2
            displayName: 'Install Firebase CLI'
            inputs:
              script: 'npm install -g firebase-tools'

          - task: CmdLine@2
            displayName: 'Authenticate Firebase CLI with Service Account'
            inputs:
              script: |
                echo "Attempting Firebase authentication with service account..."
                if npx firebase projects:list; then
                    echo "Firebase authentication successful."
                else
                    echo "Firebase authentication failed."
                    exit 1
                fi

          - task: CmdLine@2
            displayName: 'Distribute APK to Firebase (UAT)'
            condition: eq('${{ parameters.firebaseEnv }}', 'UAT')
            inputs:
              script: |
                npx firebase appdistribution:distribute "$(build.artifactStagingDirectory)/app/build/outputs/apk/uatOne{{appName}}Google/debug/app-uat-one{{appName}}-google-debug.apk" \
                  --app $(FIREBASE_APP_ID) \
                  --groups ${{ parameters.testGroup }} \
                  --release-notes "${{ parameters.releaseNotes }}" \
                  --debug

          - task: CmdLine@2
            displayName: 'Distribute APK to Firebase (Pilot)'
            condition: eq('${{ parameters.firebaseEnv }}', 'Pilot')
            inputs:
              script: |
                npx firebase appdistribution:distribute "$(build.artifactStagingDirectory)/app/build/outputs/apk/uatOne{{appName}}Google/release/app-uat-one{{appName}}-google-release.apk" \
                  --app $(FIREBASE_APP_ID) \
                  --groups ${{ parameters.testGroup }} \
                  --release-notes "${{ parameters.releaseNotes }}" \
                  --debug


Solution

  • From your YAML sample, you are setting the environment variable: GOOGLE_APPLICATION_CREDENTIALS with the command: export GOOGLE_APPLICATION_CREDENTIALS=$(Pipeline.Workspace)/service-account.json.

    In this case, the environment variable can only be used in the current CommandLine task. It cannot be passed to the next tasks.

    Since you are running the command: npx firebase projects:list in different tasks, it cannot read the environment variable: GOOGLE_APPLICATION_CREDENTIALS.

    To solve this issue, you can use the logging command to define pipeline variable.

    From:

    export GOOGLE_APPLICATION_CREDENTIALS=$(Pipeline.Workspace)/service-account.json
    

    To:

    echo "##vso[task.setvariable variable=GOOGLE_APPLICATION_CREDENTIALS]$(Pipeline.Workspace)/service-account.json"
    

    Example:

          - task: CmdLine@2
            displayName: 'Check Service Account and Credentials'
            inputs:
              script: |
                echo "$(MOBILEFIREBASEACCOUNTSERVICE)" > $(Pipeline.Workspace)/service-account.json
                
                 echo "##vso[task.setvariable variable=GOOGLE_APPLICATION_CREDENTIALS]$(Pipeline.Workspace)/service-account.json"
                echo "GOOGLE_APPLICATION_CREDENTIALS is set to: $GOOGLE_APPLICATION_CREDENTIALS"
                echo "Service account JSON file:"
                cat $(Pipeline.Workspace)/service-account.json | jq '.'
    
                if [ -f "$(Pipeline.Workspace)/service-account.json" ]; then
                  echo "Service account file exists."
                else
                  echo "Service account file does NOT exist."
                  exit 1
                fi
    

    In this case, the environment variable: GOOGLE_APPLICATION_CREDENTIALS can be read in the next tasks.

    Or you can also set the environment variable for all related Commandline tasks via the env argument.

     env:
       GOOGLE_APPLICATION_CREDENTIALS: $(Pipeline.Workspace)/service-account.json
    

    For example:

    stages:
      - stage: Build
        jobs:
          - job: BuildJob
            timeoutInMinutes: 120
            variables:
              GRADLE_USER_HOME: $(Pipeline.Workspace)/.gradle
    
            steps:
              - task: CmdLine@2
                inputs:
                  script: "sudo rm -rf $ANDROID_HOME/ndk/ && sudo rm -rf $ANDROID_HOME/ndk-bundle/"
    
              - ${{ if eq(parameters.appVariant, '{{appName}}') }}:
                  - ${{ if eq(parameters.firebaseEnv, 'UAT') }}:
                      - task: Gradle@2
                        displayName: 'Gradle Compile {{appName}} for UAT'
                        inputs:
                          gradleWrapperFile: 'gradlew'
                          tasks: 'assembleUatOne{{appName}}GoogleDebug'
    
                  - ${{ if eq(parameters.firebaseEnv, 'Pilot') }}:
                      - task: Gradle@2
                        displayName: 'Gradle Compile {{appName}} for Pilot'
                        inputs:
                          gradleWrapperFile: 'gradlew'
                          tasks: 'assemblePilotOne{{appName}}GoogleDebug'
    
              - ${{ if eq(parameters.appVariant, 'BCDC') }}:
                  - ${{ if eq(parameters.firebaseEnv, 'UAT') }}:
                      - task: Gradle@2
                        displayName: 'Gradle Compile BCDC for UAT'
                        inputs:
                          gradleWrapperFile: 'gradlew'
                          tasks: 'assembleUatOne{{appName}}BcdcGoogleDebug'
                  - ${{ if eq(parameters.firebaseEnv, 'Pilot') }}:
                      - task: Gradle@2
                        displayName: 'Gradle Compile BCDC for Pilot'
                        inputs:
                          gradleWrapperFile: 'gradlew'
                          tasks: 'assemblePilotOne{{appName}}BcdcGoogleDebug'
    
              - task: CopyFiles@2
                inputs:
                  SourceFolder: $(Build.SourcesDirectory)
                  contents: '**/*.apk'
                  targetFolder: '$(build.artifactStagingDirectory)'
                  overWrite: true
    
              - task: PublishBuildArtifacts@1
                inputs:
                  pathtoPublish: '$(Build.ArtifactStagingDirectory)/app/build/outputs/apk/'
                  artifactName: 'apks'
                  publishLocation: 'container'
    
              - task: CmdLine@2
                displayName: 'Check Service Account and Credentials'
                inputs:
                  script: |
                    echo "$(MOBILEFIREBASEACCOUNTSERVICE)" > $(Pipeline.Workspace)/service-account.json
                    export GOOGLE_APPLICATION_CREDENTIALS=$(Pipeline.Workspace)/service-account.json
    
                    echo "GOOGLE_APPLICATION_CREDENTIALS is set to: $GOOGLE_APPLICATION_CREDENTIALS"
                    echo "Service account JSON file:"
                    cat $(Pipeline.Workspace)/service-account.json | jq '.'
    
                    if [ -f "$(Pipeline.Workspace)/service-account.json" ]; then
                      echo "Service account file exists."
                    else
                      echo "Service account file does NOT exist."
                      exit 1
                    fi
    
              - task: CmdLine@2
                displayName: 'Install Firebase CLI'
                inputs:
                  script: 'npm install -g firebase-tools'
    
              - task: CmdLine@2
                displayName: 'Authenticate Firebase CLI with Service Account'
                inputs:
                  script: |
                    echo "Attempting Firebase authentication with service account..."
                    if npx firebase projects:list; then
                        echo "Firebase authentication successful."
                    else
                        echo "Firebase authentication failed."
                        exit 1
                    fi
                env:
                  GOOGLE_APPLICATION_CREDENTIALS: $(Pipeline.Workspace)/service-account.json
                 
    
              - task: CmdLine@2
                displayName: 'Distribute APK to Firebase (UAT)'
                condition: eq('${{ parameters.firebaseEnv }}', 'UAT')
                inputs:
                  script: |
                    npx firebase appdistribution:distribute "$(build.artifactStagingDirectory)/app/build/outputs/apk/uatOne{{appName}}Google/debug/app-uat-one{{appName}}-google-debug.apk" \
                      --app $(FIREBASE_APP_ID) \
                      --groups ${{ parameters.testGroup }} \
                      --release-notes "${{ parameters.releaseNotes }}" \
                      --debug
                env:
                  GOOGLE_APPLICATION_CREDENTIALS: $(Pipeline.Workspace)/service-account.json
              - task: CmdLine@2
                displayName: 'Distribute APK to Firebase (Pilot)'
                condition: eq('${{ parameters.firebaseEnv }}', 'Pilot')
                inputs:
                  script: |
                    npx firebase appdistribution:distribute "$(build.artifactStagingDirectory)/app/build/outputs/apk/uatOne{{appName}}Google/release/app-uat-one{{appName}}-google-release.apk" \
                      --app $(FIREBASE_APP_ID) \
                      --groups ${{ parameters.testGroup }} \
                      --release-notes "${{ parameters.releaseNotes }}" \
                      --debug
                env:
                  GOOGLE_APPLICATION_CREDENTIALS: $(Pipeline.Workspace)/service-account.json