2

I have a simple script to deploy a pubsub application.

This script will run on every deploy of my Cloud Run service and I have a line with:

gcloud pubsub topics create some-topic

I want to improve my script if the topic already exist, currently if I run my script, the output will be:

ERROR: Failed to create topic [projects/project-id/topics/some-topic]: Resource already exists in the project (resource=some-topic).

ERROR: (gcloud.pubsub.topics.create) Failed to create the following: [some-topic].

I tried the flag --no-user-output-enabled but no success.

Is there a way to ignore if the resource already exists, or a way to check before create?

Rodrigo
  • 135
  • 4
  • 45
  • 107

2 Answers2

5

Yes.

You can repeat the operation knowing that, if the topic didn't exist beforehand, it will if the command succeeds.

You can swallow stderr (with 2>/dev/null) and then check whether the previous command ($?) succeeded (0):

gcloud pubsub topic create do-something 2>/dev/null
if [ $? -eq 0 ]
then
  # Command succeeded, topic did not exist
  echo "Topic ${TOPIC} did not exist, created."
else
  # Command did not succeed, topic may (!) not have existed
  echo "Failure"
fi

NOTE This approach misses the fact that, the command may fail and the topic didn't exist (i.e. some other issue).

Alternatively (more accurately and more expensively!) you can enumerate the topics first and then try (!) to create it if it doesn't exist:

TOPIC="some-topic"
RESULT=$(\
  gcloud pubsub topics list \
  --filter="name.scope(topics)=${TOPIC}" \
  --format="value(name)" 2>/dev/null)

if [ "${RESULT}" == "" ]
then
  echo "Topic ${TOPIC} does not exist, creating..."
  gcloud pubsub topics create ${TOPIC}
  if [ $? -eq 0 ]
  then
    # Command succeeded, topic created
  else
    # Command did not succeed, topic was not created
  fi
fi

Depending on the complexity of your needs, you can automate using:

  • any of Google's (Pub/Sub) libraries which provide better error-handling and retry capabilities.
  • Terraform e.g. google_pubsub_topic
DazWilkin
  • 32,823
  • 5
  • 47
  • 88
  • Running the command `gcloud pubsub topics list --filter=topicId=report.topic --format="value(topicId)"` I got the warning: `WARNING: The following filter keys were not present in any resource : topicId` – Rodrigo Jun 22 '21 at 18:49
  • 1
    Sorry about that... I took it from the [docs](https://cloud.google.com/sdk/gcloud/reference/pubsub/topics/list). Let me spin up a project and confirm what it should be. – DazWilkin Jun 22 '21 at 19:06
  • 2
    The documentation is incorrect. Please try `gcloud pubsub topics list --project=${PROJECT} --filter="name.scope(topics)=${TOPIC}" --format="value(name)"` instead – DazWilkin Jun 22 '21 at 19:17
  • 2
    There's a problem with my solution. `gcloud --filter` fails if there are no results. If you have zero topics, you'll get errors from `gcloud --filter`. If you want to include this case, add `2>/dev/null` to the end of that command to swallow the error.... you'll continue to get `RESULT==""` which is good. – DazWilkin Jun 22 '21 at 19:20
  • Perfect. Thank you so much!! – Rodrigo Jun 22 '21 at 20:23
0

I had this same issue so I thought I'd try to give a full-fledged function to address this. Building on what @DazWilkin posted, below is a bash script that takes 2 inputs

  1. Project you want to point to
  2. Topic/Subscription Name (In this example the topic and subscription names are the same, however, it's quite straightforward to have an additional input be assigned to the subscription name)

The function will:

  1. Check if the current working project is the same. If not it will set it
  2. Check if the topic exists in the project. If not it will attempt to create it and wait for the response
  3. Check if the subscription exists in the project. If not it will also attempt to create it and wait for a response
function create_pubsub() {
    # Get Current Project
    current_project=$(gcloud config get-value project)
    echo "Current Project is: ${current_project}"

    # Check if Current project matches the specified project
    if [[ "$current_project" != "$1" ]]; then
        gcloud config set project $1
    else
        echo "The project provided matches the current working project"
    fi
    # Check if topic exists in project
    _topic=$(gcloud pubsub topics list \
        --filter="name.scope(topics)=$2" \
        --format="value(name)" 2>/dev/null)
    # React accordingly
    if [[ "${_topic}" != "" ]]; then
        echo "Topic $2 already exists in project ${current_project}"
    else
        echo "The topic '$2' does not exist in project ${current_project}. Creating it now..."
        gcloud pubsub topics create $2
        # Check if command executed successfully
        if [ $? -eq 0 ]; then
            echo "Topic $2 was created successfully"
        else
            echo "An error occured. Topic was NOT created"
        fi
    fi
    # Check if subscription exists in project
    _subscription=$(gcloud pubsub subscriptions list \
    --filter="name=projects/$1/subscriptions/$2" \
    --format="value(name)" 2>/dev/null)
    # React Accordingly
    if [[ "${_subscription}" != "" ]]; then
        echo "Subscription $2 already exists in project ${current_project}"
    else
        echo "The subscription '$2' does not exist in project ${current_project}. Creating it now..."
        gcloud pubsub subscriptions create $2 --topic=$2
        # Check if command executed successfully
        if [ $? -eq 0 ]; then
            echo "Subscription $2 was created successfully"
        else
            echo "An error occured. Subscription was NOT created"
        fi
    fi
    
}

After adding this function to your bashrc or zshrc file, the way you would call this function in the terminal would be create_pubsub <PROJECT_ID> <TOPIC_ID>

Hope this is helpful.