Pub/Sub notifications

This document describes how to set up notifications for updates to notes and occurrences.

Container Registry provides notifications via Pub/Sub whenever it scans for vulnerabilities and other metadata. When a note or occurrence is created or updated, a message is published to the corresponding topic for each API version. Use the topic respective your current API version.

Before you begin

  1. Enable the Container Analysis API.

  2. Read the Container Analysis overview.

Create Pub/Sub topics

After you activate the Container Analysis API, the following Pub/Sub topics are created for you in your project:

  • container-analysis-notes-v1 + container-analysis-occurrences-v1

If the topics were accidentally deleted or are missing, you can add them yourself.

Console

  1. Go to the Pub/Sub topics page in the Cloud Console.

    Open the Pub/Sub topics page

  2. Click Create Topic.

  3. Enter a topic for notes with the URI:

    projects/[PROJECT-ID]/topics/container-analysis-notes-v1
    

    where [PROJECT-ID] is your Google Cloud project ID.

  4. Click Create.

  5. Create another topic for occurrences with the URI:

     projects/[PROJECT-ID]/topics/container-analysis-occurrences-v1
    

gcloud

Run the following commands in your shell or terminal window:

gcloud pubsub topics create projects/[PROJECT-ID]/topics/container-analysis-notes-v1
gcloud pubsub topics create projects/[PROJECT-ID]/topics/container-analysis-occurrences-v1

To learn more about the gcloud pubsub topics command, see the topics documentation.

Anytime a note or an occurrence is created or updated, a message is published to the respective topic.

Pub/Sub payloads are in JSON and their schema is as follows:

Notes:

{
    "name": "projects/[PROJECT_ID]/notes/[NOTE_ID]",
    "kind": "[NOTE_KIND]",
    "notificationTime": "[NOTIFICATION_TIME]",
}

Occurrences:

{
    "name": "projects/[PROJECT_ID]/occurrences/[OCCURRENCE_ID]",
    "kind": "[NOTE_KIND]",
    "notificationTime": "[NOTIFICATION_TIME]",
}

where:

  • [NOTE_KIND] is one of the values in NoteKind
  • [NOTIFICATION_TIME] is a timestamp in RFC 3339 UTC "Zulu" format, accurate to nanoseconds.

Create Pub/Sub subscriptions

To listen to events, create a Pub/Sub subscription associated with the topic:

Console

  1. Go to the Pub/Sub subscriptions page in the Cloud Console.

    Open the Pub/Sub subscriptions page

  2. Click Create Subscription.

  3. Type a name for the subscription. For example, notes.

  4. Enter the URI of the topic for notes:

    projects/[PROJECT-ID]/topics/container-analysis-notes-v1
    

    where [PROJECT-ID] is your Google Cloud project ID.

  5. Click Create.

  6. Create another subscription for occurrences with the URI:

     projects/[PROJECT-ID]/topics/container-analysis-occurrences-v1
    

gcloud

To receive Pub/Sub events, you must first create a subscription associated with the container-analysis-occurrences-v1 topic:

gcloud pubsub subscriptions create \
    --topic container-analysis-occurrences-v1 occurrences

Going forward, you can pull messages concerning your occurrences using your new subscription:

gcloud pubsub subscriptions pull \
    --auto-ack occurrences

Java

To learn how to install and use the client library for Container Registry, see the Container Registry Client Libraries. For more information, see the Container Registry Java API reference documentation.

import com.google.cloud.pubsub.v1.AckReplyConsumer;
import com.google.cloud.pubsub.v1.MessageReceiver;
import com.google.cloud.pubsub.v1.Subscriber;
import com.google.cloud.pubsub.v1.SubscriptionAdminClient;
import com.google.pubsub.v1.ProjectSubscriptionName;
import com.google.pubsub.v1.ProjectTopicName;
import com.google.pubsub.v1.PubsubMessage;
import com.google.pubsub.v1.PushConfig;
import com.google.pubsub.v1.Subscription;
import io.grpc.StatusRuntimeException;
import java.io.IOException;
import java.lang.InterruptedException;
import java.util.concurrent.TimeUnit;

public class Subscriptions {
  // Handle incoming Occurrences using a Cloud Pub/Sub subscription
  public static int pubSub(String subId, long timeoutSeconds, String projectId)
      throws InterruptedException {
    // String subId = "my-occurrence-subscription";
    // long timeoutSeconds = 20;
    // String projectId = "my-project-id";
    Subscriber subscriber = null;
    MessageReceiverExample receiver = new MessageReceiverExample();

    try {
      // Subscribe to the requested Pub/Sub channel
      ProjectSubscriptionName subName = ProjectSubscriptionName.of(projectId, subId);
      subscriber = Subscriber.newBuilder(subName, receiver).build();
      subscriber.startAsync().awaitRunning();
      // Sleep to listen for messages
      TimeUnit.SECONDS.sleep(timeoutSeconds);
    } finally {
      // Stop listening to the channel
      if (subscriber != null) {
        subscriber.stopAsync();
      }
    }
    // Print and return the number of Pub/Sub messages received
    System.out.println(receiver.messageCount);
    return receiver.messageCount;
  }

  // Custom class to handle incoming Pub/Sub messages
  // In this case, the class will simply log and count each message as it comes in
  static class MessageReceiverExample implements MessageReceiver {
    public int messageCount = 0;

    @Override
    public synchronized void receiveMessage(PubsubMessage message, AckReplyConsumer consumer) {
      // Every time a Pub/Sub message comes in, print it and count it
      System.out.println("Message " + messageCount + ": " + message.getData().toStringUtf8());
      messageCount += 1;
      // Acknowledge the message
      consumer.ack();
    }
  }

  // Creates and returns a Pub/Sub subscription object listening to the Occurrence topic
  public static Subscription createOccurrenceSubscription(String subId, String projectId) 
      throws IOException, StatusRuntimeException, InterruptedException {
    // This topic id will automatically receive messages when Occurrences are added or modified
    String topicId = "container-analysis-occurrences-v1";
    ProjectTopicName topicName = ProjectTopicName.of(projectId, topicId);
    ProjectSubscriptionName subName = ProjectSubscriptionName.of(projectId, subId);

    SubscriptionAdminClient client = SubscriptionAdminClient.create();
    PushConfig config = PushConfig.getDefaultInstance();
    Subscription sub = client.createSubscription(subName, topicName, config, 0);
    return sub;
  }
}

Go

To learn how to install and use the client library for Container Registry, see the Container Registry Client Libraries. For more information, see the Container Registry Go API reference documentation.


import (
	"context"
	"fmt"
	"io"
	"sync"
	"time"

	pubsub "cloud.google.com/go/pubsub"
)

// occurrencePubsub handles incoming Occurrences using a Cloud Pub/Sub subscription.
func occurrencePubsub(w io.Writer, subscriptionID string, timeout time.Duration, projectID string) (int, error) {
	// subscriptionID := fmt.Sprintf("my-occurrences-subscription")
	// timeout := time.Duration(20) * time.Second
	ctx := context.Background()

	var mu sync.Mutex
	client, err := pubsub.NewClient(ctx, projectID)
	if err != nil {
		return -1, fmt.Errorf("pubsub.NewClient: %v", err)
	}
	// Subscribe to the requested Pub/Sub channel.
	sub := client.Subscription(subscriptionID)
	count := 0

	// Listen to messages for 'timeout' seconds.
	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()
	err = sub.Receive(ctx, func(ctx context.Context, msg *pubsub.Message) {
		mu.Lock()
		count = count + 1
		fmt.Fprintf(w, "Message %d: %q\n", count, string(msg.Data))
		msg.Ack()
		mu.Unlock()
	})
	if err != nil {
		return -1, fmt.Errorf("sub.Receive: %v", err)
	}
	// Print and return the number of Pub/Sub messages received.
	fmt.Fprintln(w, count)
	return count, nil
}

// createOccurrenceSubscription creates a new Pub/Sub subscription object listening to the Occurrence topic.
func createOccurrenceSubscription(subscriptionID, projectID string) error {
	// subscriptionID := fmt.Sprintf("my-occurrences-subscription")
	ctx := context.Background()
	client, err := pubsub.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("pubsub.NewClient: %v", err)
	}
	defer client.Close()

	// This topic id will automatically receive messages when Occurrences are added or modified
	topicID := "container-analysis-occurrences-v1"
	topic := client.Topic(topicID)
	config := pubsub.SubscriptionConfig{Topic: topic}
	_, err = client.CreateSubscription(ctx, subscriptionID, config)
	return fmt.Errorf("client.CreateSubscription: %v", err)
}

Node.js

To learn how to install and use the client library for Container Registry, see the Container Registry Client Libraries. For more information, see the Container Registry Node.js API reference documentation.

/**
 * TODO(developer): Uncomment these variables before running the sample
 */
// const projectId = 'your-project-id', // Your GCP Project ID
// const subscriptionId = 'my-sub-id', // A user-specified subscription to the 'container-analysis-occurrences-v1' topic
// const timeoutSeconds = 30 // The number of seconds to listen for the new Pub/Sub Messages

// Import the pubsub library and create a client, topic and subscription
const {PubSub} = require('@google-cloud/pubsub');
const pubsub = new PubSub({projectId});
const subscription = pubsub.subscription(subscriptionId);

// Handle incoming Occurrences using a Cloud Pub/Sub subscription
let count = 0;
const messageHandler = message => {
  count++;
  message.ack();
};

// Listen for new messages until timeout is hit
subscription.on('message', messageHandler);

setTimeout(() => {
  subscription.removeListener('message', messageHandler);
  console.log(`Polled ${count} occurrences`);
}, timeoutSeconds * 1000);

Ruby

To learn how to install and use the client library for Container Registry, see the Container Registry Client Libraries. For more information, see the Container Registry Ruby API reference documentation.

# subscription_id = "A user-specified identifier for the new subscription"
# timeout_seconds = "The number of seconds to listen for new Pub/Sub messages"
# project_id      = "Your Google Cloud project ID"

require "google/cloud/pubsub"

pubsub = Google::Cloud::Pubsub.new project: project_id
topic = pubsub.topic "container-analysis-occurrences-v1"
subscription = topic.subscribe subscription_id

count = 0
subscriber = subscription.listen do |received_message|
  count += 1
  # Process incoming occurrence here
  puts "Message #{count}: #{received_message.data}"
  received_message.acknowledge!
end
subscriber.start
# Wait for incomming occurrences
sleep timeout_seconds
subscriber.stop.wait!
subscription.delete
# Print and return the total number of Pub/Sub messages received
puts "Total Messages Received: #{count}"
count

Python

To learn how to install and use the client library for Container Registry, see the Container Registry Client Libraries. For more information, see the Container Registry Python API reference documentation.

def pubsub(subscription_id, timeout_seconds, project_id):
    """Respond to incoming occurrences using a Cloud Pub/Sub subscription."""
    # subscription_id := 'my-occurrences-subscription'
    # timeout_seconds = 20
    # project_id = 'my-gcp-project'

    import time
    from google.cloud.pubsub import SubscriberClient

    client = SubscriberClient()
    subscription_name = client.subscription_path(project_id, subscription_id)
    receiver = MessageReceiver()
    client.subscribe(subscription_name, receiver.pubsub_callback)

    # listen for 'timeout' seconds
    for _ in range(timeout_seconds):
        time.sleep(1)
    # print and return the number of pubsub messages received
    print(receiver.msg_count)
    return receiver.msg_count


class MessageReceiver:
    """Custom class to handle incoming Pub/Sub messages."""
    def __init__(self):
        # initialize counter to 0 on initialization
        self.msg_count = 0

    def pubsub_callback(self, message):
        # every time a pubsub message comes in, print it and count it
        self.msg_count += 1
        print('Message {}: {}'.format(self.msg_count, message.data))
        message.ack()


def create_occurrence_subscription(subscription_id, project_id):
    """Creates a new Pub/Sub subscription object listening to the
    Container Analysis Occurrences topic."""
    # subscription_id := 'my-occurrences-subscription'
    # project_id = 'my-gcp-project'

    from google.api_core.exceptions import AlreadyExists
    from google.cloud.pubsub import SubscriberClient

    topic_id = 'container-analysis-occurrences-v1'
    client = SubscriberClient()
    topic_name = f"projects/{project_id}/topics/{topic_id}"
    subscription_name = client.subscription_path(project_id, subscription_id)
    success = True
    try:
        client.create_subscription({"name": subscription_name, "topic": topic_name})
    except AlreadyExists:
        # if subscription already exists, do nothing
        pass
    else:
        success = False
    return success

Subscriber applications only receive messages that are published to the topic after the subscription is created.

What's next

  • For instructions on how to use Container Analysis to store and manage your customers' metadata, see Providing Metadata for Images.

  • You can integrate Binary Authorization with vulnerability scanning to prevent images with known security issues from running in your deployment environment. For instructions on doing this see, Vulnerability Scanning Integration.