Creating your own notifier

Cloud Build can notify you of updates to your build status by sending you notifications to desired channels. In addition to notifiers maintained by Cloud Build such as Slack or SMTP, you can also use the provided library in the cloud-build-notifiers repository to create your own notifier.

This page explains how you can create your own notifier.

Before you begin

  • Enable the Cloud Build, Cloud Run, Pub/Sub, and Secret Manager APIs.

    Enable the APIs

  • Install the Go programming language.

  • Install the Google Cloud CLI.

Setting up

  1. Open a terminal window on your machine.

  2. Clone and navigate into the cloud-build-notifiers repository:

      git clone https://github.com/GoogleCloudPlatform/cloud-build-notifiers.git && cd cloud-build-notifiers
    
  3. Add a directory for your own notifier and navigate into it, where DIRECTORY_NAME is the name of your directory:

      mkdir DIRECTORY_NAME && cd DIRECTORY_NAME
    
  4. Initialize go modules in your new directory, where DIRECTORY_NAME is the name of your new directory:

      go mod init github.com/GoogleCloudPlatform/cloud-build-notifiers/DIRECTORY_NAME
    

    You should now see a go.mod file in your directory.

  5. Add the following line to your go.mod file to ensure you are using the latest version of notifiers:

     replace github.com/GoogleCloudPlatform/cloud-build-notifiers/lib/notifiers => ../
    

Your dependencies are now set up and you are ready to create your own notifier.

Creating your own notifier

The cloud-build-notifiers contains a lib/notifiers directory. In the lib/notifiers directory, you will see a file named notifier.go. This file contains the framework you can use to create your own notifier.

You will need to define two methods to create a notifier in your main file.

  1. In the new directory, create a file named main.go.

  2. In main.go, import the notifiers library framework and any other dependencies:

    package main
    
    import (
    	"context"
    	"fmt"
    
    	cbpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb"
    	"github.com/GoogleCloudPlatform/cloud-build-notifiers/lib/notifiers"
    	log "github.com/golang/glog"
    	"google.golang.org/protobuf/encoding/prototext"
    )
    
  3. Define a main method for your notifier. In this example, logger is the name of the notifier:

    func main() {
    	if err := notifiers.Main(new(logger)); err != nil {
    		log.Fatalf("fatal error: %v", err)
    	}
    }
    

    The main method uses the Main method defined in the notifier.go file, which is used to set up notifier binaries.

  4. Define a struct for your notifier, where you will define the variables for your interface. In this example, logger is the name of the notifier. E.g.

    type logger struct {
    	filter notifiers.EventFilter
    }
    

Next, you will add the notifier functionality. The notifier interface is defined by two methods:

  • SetUp: The SetUp method accepts a configuration, fetches secrets, and pulls specified filters out of the configuration and stores them as a Common Expression Language predicate that can be used to send notifications. To learn more about CEL, see the cel-spec repository.
  • SendNotification: The SendNotification method is what is used to send notifications to your desired channel or service.

    The definition of the notifier is available in notifier.go and in the Go documentation.

    In the example below, the notifier interface is defined using the SetUp and the SendNotification method to print build logs, with logger as the name of your notifier:

    func (h *logger) SetUp(_ context.Context, cfg *notifiers.Config, _ notifiers.SecretGetter, _ notifiers.BindingResolver) error {
    	prd, err := notifiers.MakeCELPredicate(cfg.Spec.Notification.Filter)
    	if err != nil {
    		return fmt.Errorf("failed to create CELPredicate: %w", err)
    	}
    	h.filter = prd
    	return nil
    }
    
    func (h *logger) SendNotification(ctx context.Context, build *cbpb.Build) error {
    	// Include custom functionality here.
    	// This example logs the build.
    	if h.filter.Apply(ctx, build) {
    		log.V(1).Infof("printing build\n%s", prototext.Format(build))
    	} else {
    		log.V(1).Infof("build (%q, %q) did NOT match CEL filter", build.ProjectId, build.Id)
    	}
    
    	return nil
    }
    

    Your final main.go file should look similar to the following file. In this example, logger is used as the name of the notifier.

    package main
    
    import (
    	"context"
    	"fmt"
    
    	cbpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb"
    	"github.com/GoogleCloudPlatform/cloud-build-notifiers/lib/notifiers"
    	log "github.com/golang/glog"
    	"google.golang.org/protobuf/encoding/prototext"
    )
    
    
    func main() {
    	if err := notifiers.Main(new(logger)); err != nil {
    		log.Fatalf("fatal error: %v", err)
    	}
    }
    
    
    type logger struct {
    	filter notifiers.EventFilter
    }
    
    
    func (h *logger) SetUp(_ context.Context, cfg *notifiers.Config, _ notifiers.SecretGetter, _ notifiers.BindingResolver) error {
    	prd, err := notifiers.MakeCELPredicate(cfg.Spec.Notification.Filter)
    	if err != nil {
    		return fmt.Errorf("failed to create CELPredicate: %w", err)
    	}
    	h.filter = prd
    	return nil
    }
    
    func (h *logger) SendNotification(ctx context.Context, build *cbpb.Build) error {
    	// Include custom functionality here.
    	// This example logs the build.
    	if h.filter.Apply(ctx, build) {
    		log.V(1).Infof("printing build\n%s", prototext.Format(build))
    	} else {
    		log.V(1).Infof("build (%q, %q) did NOT match CEL filter", build.ProjectId, build.Id)
    	}
    
    	return nil
    }
    

    Now that you have your notifier defined, you can follow the following steps below to configure your notifier.

Configuring notifications

  1. Write a notifier configuration file to configure your notifier and filter on build events:

    In the following example notifier configuration file, the filter field uses CEL with the available variable, build, to filter build events with a SUCCESS status:

    apiVersion: cloud-build-notifiers/v1
    kind: YourNotifier
    metadata:
      name: logging-sample
    spec:
      notification:
        filter: build.status == Build.Status.SUCCESS

    Where:

    • logging-sample is the name of the notifier.

    For additional fields you can filter by, see the Build resource. For additional filtering examples, see Using CEL to filter build events.

  2. Upload your notifier configuration file to a Cloud Storage bucket:

    1. If you do not have a Cloud Storage bucket, run the following command to create a bucket, where BUCKET_NAME is the name you want to give your bucket, subject to naming requirements.

      gsutil mb gs://BUCKET_NAME/
      
    2. Upload the notifier configuration file to your bucket:

      gsutil cp CONFIG_FILE_NAME gs://BUCKET_NAME/CONFIG_FILE_NAME
      

      Where:

      • BUCKET_NAME is the name of your bucket.
      • CONFIG_FILE_NAME is the name of your configuration file.
  3. Build and deploy your notifier:

    1. Create a Dockerfile for the logging-sample:

      
      FROM golang AS build-env
      COPY . /go-src/
      WORKDIR /go-src/
      RUN go build -o /go-app .
      
      # From the Cloud Run docs:
      # https://cloud.google.com/run/docs/tutorials/pubsub#looking_at_the_code
      # Use the official Debian slim image for a lean production container.
      # https://hub.docker.com/_/debian
      # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
      FROM debian:buster-slim
      RUN set -x && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
          ca-certificates && \
          rm -rf /var/lib/apt/lists/*
      
      FROM gcr.io/distroless/base
      COPY --from=build-env /go-app /
      ENTRYPOINT ["/go-app", "--v=1", "--alsologtostderr"]
      
    2. Build and deploy the notifier using the following cloudbuild.yaml file.

      steps:
      - # Build the binary and put it into the builder image.
        name: gcr.io/cloud-builders/docker
        args: ['build', '--tag=gcr.io/$PROJECT_ID/logging-sample', '.']
      
      - # Push the container image to Container Registry
        name: gcr.io/cloud-builders/docker
        args: ['push', 'gcr.io/$PROJECT_ID/logging-sample']
      
      - # Deploy to Cloud Run
        name: google/cloud-sdk
        args: 
          - gcloud
          - run
          - deploy
          - logging-sample-notifier
          - --platform=managed
          - --region=us-central1
          - --image=gcr.io/$PROJECT_ID/logging-sample
          - --no-allow-unauthenticated
          - --update-env-vars=CONFIG_PATH=${_CONFIG_PATH}
      
      # Push the image with tags.
      images:
      - gcr.io/$PROJECT_ID/logging-sample

      Where:

      • _CONFIG_PATH is the path to your notifier configuration, such as gs://BUCKET_NAME/CONFIG_FILE_NAME.yaml.

    To run the cloudbuild.yaml, pass in the notifier path as a substitution variable.

     gcloud builds submit .  --substitutions=_CONFIG_PATH=gs://BUCKET_NAME/CONFIG_FILE_NAME
    
  4. Grant Pub/Sub permissions to create authentication tokens in your project:

     gcloud projects add-iam-policy-binding PROJECT_ID \
       --member=serviceAccount:service-PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \
       --role=roles/iam.serviceAccountTokenCreator
    

    Where:

    • PROJECT_ID is the ID of your Google Cloud project.
    • PROJECT_NUMBER is your Google Cloud project number.
  5. Create a service account to represent your Pub/Sub subscription identity:

    gcloud iam service-accounts create cloud-run-pubsub-invoker \
      --display-name "Cloud Run Pub/Sub Invoker"
    

    You can use cloud-run-pubsub-invoker or use a name unique within your Google Cloud project.

  6. Give the cloud-run-pubsub-invoker service account the Cloud Run Invoker permission:

    gcloud run services add-iam-policy-binding SERVICE_NAME \
       --member=serviceAccount:cloud-run-pubsub-invoker@PROJECT_ID.iam.gserviceaccount.com \
       --role=roles/run.invoker
    

    Where:

    • SERVICE_NAME is the name of the Cloud Run service to which you're deploying the image.
    • PROJECT_ID is the ID of your Google Cloud project.
  7. Create the cloud-builds topic to receive build update messages for your notifier:

    gcloud pubsub topics create cloud-builds
    
  8. Create a Pub/Sub push subscriber for your notifier:

     gcloud pubsub subscriptions create subscriber-id \
       --topic=cloud-builds \
       --push-endpoint=service-url \
       --push-auth-service-account=cloud-run-pubsub-invoker@project-id.iam.gserviceaccount.com
    

    Where:

    • subscriber-id is the name you want to give your subscription.
    • service-url is the Cloud Run-generated URL for your new service.
    • project-id is the ID of your Google Cloud project.

Notifications for your Cloud Build project are now set up. The next time you invoke a build, you will receive a notification in your channel if the build matches the filter you've configured.

Testing notifications

To test notification functionality for the example used in this guide, you can invoke a build by running the gcloud builds submit command.

In the following example, we specify success.yaml as the configuration path. Running this command should result in a minimal successful build. You should also be able to see an output of your build logs.

 gcloud builds submit --no-source --config=success.yaml

Where success.yaml is:

 steps:
 - name: busybox
   args: ["true"]

In the following example, we specify failure.yaml as the configuration path. Running this command should result in a failed build. Instead of seeing an output of your build logs, you will see an output informing you that there was no match for the CEL filters you specified in your source.

gcloud builds submit --no-source --config=failure.yaml

Where failure.yaml is:

 steps:
 - name: busybox
   args: ["false"]

If you created a notifier that's configured to perform another task other than logging output to Cloud Run service logs, you can also run the gcloud builds submit command to test for notification functionality. To examine errors associated with your build, check the Cloud Run logs for your service. To learn more, see Viewing logs in Cloud Run.

What's next