Creating a Serverless Mobile Delivery Pipeline

This tutorial describes how to create a serverless mobile delivery pipeline in Google Cloud:

  • First, it walks you through setting up a test Android application that you can build and distribute using Gradle commands on your local machine. These commands build the Android application package (APK) and upload it to Firebase App Distribution for distribution to beta testers.
  • Next, to automate this process, you install the Android Builder for Cloud Build and set up a build trigger to automatically trigger build and distribution of the Android application with changes to its source code.

Objectives

  • Create a build task that builds a signed APK.
  • Create a build task that uses Firebase App Distribution to distribute new builds.
  • Install the Android Builder with the required Android SDK dependencies.
  • Create a build trigger in Google Cloud that uses the Android Builder to build the signed APK and distribute it using Firebase App Distribution.

Costs

This tutorial uses billable components of Google Cloud, including:

The base Android Builder image is about 1.9 GB. The Pricing Calculator estimates the cost of storage of 2 GB environment at around $.05 per month. Building the base Android Builder image takes about 11 minutes and costs $.03. Each additional build should take about 5 minutes and cost $.02. See Cloud Build pricing for more information.

Before you begin

  1. Sign in to your Google Account.

    If you don't already have one, sign up for a new account.

  2. In the Cloud Console, on the project selector page, select or create a Cloud project.

    Go to the project selector page

  3. Make sure that billing is enabled for your Google Cloud project. Learn how to confirm billing is enabled for your project.

  4. Enable the Compute Engine API.

    Enable the API

  5. Install and initialize the Cloud SDK.
  6. Make sure you have Gradle installed on your computer.
  7. Make sure you have Android Studio installed on your computer.
  8. Make sure that Git is installed on your computer.

This tutorial assumes the following:

  • You're running macOS or Linux. Some of these procedures could be adapted to a Windows environment, but the tutorial does not provide complete instructions for that use case.
  • You have some familiarity with Android development, Android Studio, and Gradle and Git.

Get the signed Android beta build working locally

In this section, you set up a test Android application in Android Studio. The application can be built and distributed using Gradle build tasks run in the terminal window of your computer.

Create a test Android application to build

  1. In Android Studio, create a new Android Project that uses an empty activity.

    The following screenshots show the steps in the wizard for performing this task.

    Android Studio wizard, page 1

    Android Studio wizard, page 2

    Android Studio wizard, page 3

    Android Studio wizard, page 4

  2. From the terminal window on your computer, change to the directory where you created your test application:

    cd ${HOME}/android-apps/TestAndroidBuild
  3. In that directory, initialize a Git repository:

    git init && git checkout -b develop
  4. Edit the .gitignore file and add the names of files and extensions that Git should not push to your source code repository:

    cat >> .gitignore <<EOF
    google-services.json
    app-distro.json
    keystore.properties
    EOF
  5. Commit the application to the repository:

    git add -A
    git commit -m "empty application"
  6. Configure the gcloud command-line tool to use your project. For [PROJECT_NAME], use the project name that you selected or created.

    PROJECT=[PROJECT_NAME]
    gcloud config configurations create $PROJECT
    gcloud config config set project $PROJECT
  7. Create a cloud source repository for your Android application:

    gcloud source repos create android-application
  8. Add the cloud source repository as a remote repository for your Android application:

    git remote add google \
        https://source.developers.google.com/p/${PROJECT}/r/android-application

Add Firebase to your application

  1. Go to the Firebase Console:

    Go to the Firebase Console

  2. Click Add Project.

  3. Under Enter your project name, select the Google Cloud project that you created earlier:

    Firebase Cloud project menu.

  4. Confirm the Firebase Blaze Billing Plan.

  5. Select whether you want to enable Google Analytics. For this tutorial, you do not need to use analytics.

  6. Wait for the Firebase project to be created, and then click Continue.

  7. Follow the instructions at Add Firebase to your Android project to add Firebase to your Android application.

  8. Ensure that your google-services.json file is moved to the following directory:

    {HOME}/android-apps/TestAndroidBuild

  9. Follow the instructions at Get started with Firebase Crashlytics to add Firebase Crashlystics to your Android project.

  10. Change directories to the app-level directory of your project:

    cd {HOME}/android-apps/TestAndroidBuild/app

  11. Create a Google Cloud service account with the Firebase admin role:

    gcloud iam service-accounts create app-distro --display-name=app-distro
    APP_DISTRO_SA=$(gcloud iam service-accounts list --filter="displayName:app-distro" --format='value(email)')
    gcloud projects add-iam-policy-binding $PROJECT --role roles/firebase.admin --member serviceAccount:$APP_DISTRO_SA
    gcloud iam service-accounts keys create app-distro.json $APP_DISTRO_SA
    

Create the signing key and keystore

To distribute the pre-release Android application, you must sign the application using a certificate that's stored in a Java keystore (.jks) file.

  1. In the terminal window, change to the parent directory of your application:

    cd ${HOME}/AndroidStudioProjects/TestAndroidBuild
  2. Create the Java keystore and key to sign the application, and create the keystore.properties file that stores the key alias, password, and relative path for the keystore file:

    JKS_PASSWORD=$(openssl rand -base64 12)
    keytool -genkey -noprompt -keystore ../signing/android.jks \
      -alias android-key \
      -dname "CN=example.com, OU=IT, O=Example, L=Sunnyvale, S=California, C=US" \
      -storepass ${JKS_PASSWORD} \
      -keypass ${JKS_PASSWORD}
    
    cat > ../signing/keystore.properties <<EOF
    storeFile=../signing/android.jks
    storePassword=${JKS_PASSWORD}
    keyPassword=${JKS_PASSWORD}
    keyAlias=android-key
    EOF

Encrypt the android secret files and add them to your source repository.

  1. Enable the key management service:

    gcloud services enable cloudkms.googleapis.com
    
  2. Create a keyring to store your keys:

    gcloud kms keyrings create my-app --location=global
    
  3. Create a key to store your secrets:

    gcloud kms keys create android-builder \
        --location=global \
        --keyring=my-app \
        --purpose=encryption
    
  4. Add the encrypt/decrypt role to the Cloud Build service account:

    CLOUDBUILD_ACCOUNT=$(gcloud projects get-iam-policy $PROJECT \
        --filter="(bindings.role:roles/cloudbuild.builds.builder)" \
        --flatten="bindings[].members" \
        --format="value(bindings.members[])" | tail -1)
    
    gcloud kms keys add-iam-policy-binding android-builder --location=global \
        --keyring=my-app --member="${CLOUDBUILD_ACCOUNT} \
        --role=roles/cloudkms.cryptoKeyEncrypterDecrypter
    
  5. Encrypt the keystore.properties file:

    gcloud kms encrypt --plaintext-file=signing/keystore.properties \
        --ciphertext-file=signing/keystore.properties.enc --location=global \
        --keyring=my-app --key=android-builder
    
  6. Add the signing files to your source repository:

    git add signing
    git commit -m "encrypted signing information"
    
  7. Encrypt the google-services.json file:

    gcloud kms encrypt --plaintext-file=app/google-services.json \
        --ciphertext-file=app/google-services.json.enc --location=global \
        --keyring=my-app --key=android-builder
    
  8. Add the google-services.json.enc file to your source repository:

    git add app/google-services.json.enc
    git commit -m "encrypted application account"
    
  9. Encrypt the app-distro.json file:

    gcloud kms encrypt --plaintext-file=app-distro.json \
        --ciphertext-file=app-distro.json.enc \
        --location=global \
        --keyring=my-app --key=android-builder
    
  10. Add the app-distro.json.enc file to your source repository:

    git add app-distro.json.enc
    git commit -m "encrypted service account"
    

Add Firebase App Distribution

Create a Firebase distribution group

  1. Go to the Firebase console:

    Go to the Firebase console

  2. Click the app you created for Firebase. The name is similar to the name of the Cloud project.

  3. Click App Distribution.

  4. Click Testers.

  5. Click Add Testers.

  6. Enter your email address.

  7. Click Create a tester group.

  8. Enter the name beta-testers.

  9. Click Add testers.

  10. Add your email address to the beta-testers group.

Update the build.gradle file to include the signing and Firebase distribution configurations

  1. Update the {HOME}/AndroidStudioProjects/TestAndroidBuild/build.gradle file with lines in bold below:

    buildscript {
        ext.kotlin_version = '1.3.41'
        repositories {
            google()
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.5.0'
            classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version'
            classpath 'com.google.firebase:firebase-appdistribution-gradle:2.0.0'
            classpath 'com.google.gms:google-services:4.3.1'
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
     }
    
  2. Add the following snippets to the {HOME}/AndroidStudioProjects/TestAndroidBuild/app/build.gradle file:

    android {
    // ...
    def debugKeystorePropertiesFile = rootProject.file("signing/keystore.properties")
    def keystoreProperties = new Properties()
    keystoreProperties.load(new FileInputStream(debugKeystorePropertiesFile))
    //...
    android {
      //...
        signingConfigs {
           debug {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile file(keystoreProperties['storeFile'].trim())
            storePassword keystoreProperties['storePassword']
          }
         }
          buildTypes {
            debug {
              firebaseAppDistribution {
                releaseNotesFile="release-notes.txt"
                groups="beta-testers"
              }
            }
          // ...
            packagingOptions {
              exclude "app-distro.json"
            }
          }
      }
    

Fix the test dependency in the Gradle file

  1. Ensure the following line in bold is in your ${HOME}/AndroidStudioProjects/TestAndroidBuild/app/build.gradle file:

    dependencies {
      //...
      androidTestImplementation 'androidx.test:runner:1.2.0'
      androidTestImplementation 'androidx.test.ext:junit:1.1.1'
      androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
     }
    

Build and distribute the app from your computer

  1. In the terminal window, make sure you're at the parent directory for your test application:

    {HOME}/AndroidStudioProjects/TestAndroidBuild

  2. Build and distribute the app:

    gradle assembleDebug appDistributionUploadDebug
    

    An email is sent to the distribution group with a link that allows the user to install the application.

Save the changes to the build in your source repository

  • Commit your changes to the source repository:

    git add app/build.gradle && git commit -m "Update build for distribution."
    

Create your Android build in Cloud Build

At this point, you have working Gradle build tasks that build and distribute your Android application. In this section, you set up Cloud Build to run these build tasks whenever there's a change to the master branch of the Cloud Source Repository.

  1. Set the PROJECT environment variable:

    export PROJECT=$(gcloud config get-value project)
    
  2. Create the storage bucket that the Android Builder will use to save its dependencies between builds:

    gsutil mb gs://$PROJECT-cache-bucket
    
  3. Create the storage bucket that the Android Builder will use to increment the Android Build number:

    gsutil mb gs://$PROJECT-config-bucket
    
  4. Create the storage bucket that the Android Builder will use to store artifacts:

    gsutil mb gs://$PROJECT-artifact-bucket
    

Create the Android Builder

  1. In a terminal window, clone the community builders repository to a directory outside of your Android application's directory:

    git clone https://github.com/GoogleCloudPlatform/cloud-builders-community
    

    The GitHub repository contains the community maintained builders.

  2. Change to the directory that contains the tar builder:

    cd cloud-builders-community/tar
    
  3. Add the tar builder to your project:

    gcloud builds submit --config=cloudbuild.yaml
    
  4. Change to the directory that contains the Android Builder:

    cd ../android
    
  5. Create the Android Builder, passing the target SDK version as a substitution:

    gcloud builds submit --config=cloudbuild.yaml \
        --substitutions=_ANDROID_VERSION=28 

Create the Android application build trigger

In this section, you create the Application build trigger that uses the Android Builder from the previous section to build and distribute the Android application.

  1. In the terminal window, copy the example cloudbuild.yaml file from the Android builder examples/ directory to your test Android application directory:

    cd ${HOME}/android-apps/TestAndroidBuild
    cp ${HOME}/cloud-builders-community/android/examples/build-test-deploy-firebase-distro.yaml \ ./cloudbuild.yaml
    
  2. Add the new cloudbuild.yaml file to the develop branch of your source repository:

    git add cloudbuild.yaml && git commit -m "Add android builder to project."
    
  3. In the Cloud Console, go to the Build Triggers page:

    Go to the Build Triggers page

  4. Click Create trigger.

  5. Select the android-application repository.

  6. For Name, enter android-application-build.

  7. Under Build configuration, select cloudbuild.yaml.

  8. Under cloudbuild.yaml location, enter /cloudbuild.yaml.

  9. Click Add item. This lets you set variables for the build.

  10. For each variable in the following table, enter the variable name (including the leading underscore), and then the value from your project.

    Variable Value Purpose
    _CONFIG_BUCKET
    The bucket used for storing the build number.
    gs://[PROJECT_NAME]-config-bucket The URL of the bucket that will be used to store the build number.
    _CACHE_BUCKET
    The bucket to use for caching build dependencies.
    gs://[PROJECT_NAME]-cache-bucket Prevents Gradle from downloading the same dependencies from the internet with each build.
    _ARTIFACT_BUCKET
    The Cloud Storage bucket used to store build artifacts.
    gs://[PROJECT_NAME]-artifact-bucket The Android Builder stores intermediate and other artifacts here during the build.
  11. Click Create trigger.

Execute the application build trigger

You can now test that the application build trigger works and confirm that the application is distributed to your email.

  1. In a terminal window, change directories to the top-level directory of your test Android application project:

    cd ${HOME}/android-apps/TestAndroidBuild
  2. To trigger the build, push to the master branch of the source code repository

    git push google develop
  3. In the Cloud Console, go to the Cloud Build page.

  4. Click the latest build in progress to see the build output for the application build.

Cleaning up

After you've finished this tutorial, you should clean up the resources that you created on Google Cloud so you won't be billed for them in the future. The following sections describe how to delete or turn off these resources.

Delete the project

The easiest way to eliminate billing is to delete the project that you created for the tutorial.

To delete the project:

  1. In the Cloud Console, go to the Manage resources page.

    Go to the Manage resources page

  2. In the project list, select the project that you want to delete and then click Delete .
  3. In the dialog, type the project ID and then click Shut down to delete the project.

Delete image registries

Delete the image registries for your Android SDK image and Android application image.

  1. In the left navigation menu of the Cloud Console, scroll to the Tools section and click Container Registry > Images.
  2. Select all the images.

    Selecting all the images

  3. Click Delete.

Delete Cloud Storage buckets

Delete the Cloud Storage buckets that contain the Android signing information and the Fabric properties. In Cloud Shell, use these commands, substituting your values for the placeholders:

  1. Set the PROJECT environment variable.

    export PROJECT=$(gcloud config get-value project)
    
  2. Delete the storage buckets created for the build.

    gsutil rm gs://$PROJECT-cache-bucket
    gsutil rm gs://$PROJECT-config-bucket
    gsutil rm gs://$PROJECT-artifact-bucket
    

Delete the build triggers

  1. Delete the base Android SDK build trigger.
  2. Delete the Android build trigger.

Delete the application from Firebse

  1. Navigate to the Firebase console

  2. Select the Firebase project you created for the tutorial. It should have a name similar to [PROJECT_NAME].

  3. From the settings menu, select Project Settings. Firebase Project Settings

  4. Scroll to the bottom of the page and click Delete Project.

What's next