Disable billing usage with notifications

This document explains how to automatically disable billing on a project when your costs meet or exceed your project budget. When you disable billing on a project, you terminate all Google Cloud services in the project, including Free Tier services. For a more nuanced response to budget notifications, see Control resource usage with notifications.

You might limit costs because you have a maximum amount of money that you can spend on Google Cloud. In these cases, when your budget limit is reached, you might be willing to shut down all of your Google Cloud services and usage to stop incurring costs. Disabling billing on your project is an efficient method to stop incurring costs in that project.

Limitations

  • There's a delay between incurring costs and receiving budget notifications, so you might incur additional costs for usage that hasn't arrived at the time that all services are stopped. Following the steps in this example doesn't guarantee that you won't spend more than your budget. If you have a limited amount of funds, set your maximum budget below your available funds to account for billing delays.

  • You can't disable billing on a project that's locked to a billing account. To learn more about locking and unlocking projects, see Secure the link between a project and its billing account.

Before you begin

Before you begin, you must complete the following tasks:

  1. Enable the Cloud Billing API
  2. Create a budget that's scoped to a single project
  3. Set up programmatic budget notifications

Set up a Cloud Run function

To disable Cloud Billing for a project, create a Cloud Run function and configure it to call the Cloud Billing API.

  1. Complete the steps in Create a Cloud Run function. Ensure that the Trigger type is set to the same Pub/Sub topic that your budget will use.
  2. Add the following dependencies:

    Node.js

    Copy the following to your package.json file:

    {
      "name": "cloud-functions-billing",
      "private": "true",
      "version": "0.0.1",
      "description": "Examples of integrating Cloud Functions with billing",
      "main": "index.js",
      "engines": {
        "node": ">=16.0.0"
      },
      "scripts": {
        "compute-test": "c8 mocha -p -j 2 test/periodic.test.js --timeout=600000",
        "test": "c8 mocha -p -j 2 test/index.test.js --timeout=5000 --exit"
      },
      "author": "Ace Nassri <anassri@google.com>",
      "license": "Apache-2.0",
      "dependencies": {
        "@google-cloud/billing": "^4.0.0",
        "@google-cloud/compute": "^4.0.0",
        "google-auth-library": "^9.0.0",
        "googleapis": "^143.0.0",
        "slack": "^11.0.1"
      },
      "devDependencies": {
        "@google-cloud/functions-framework": "^3.0.0",
        "c8": "^10.0.0",
        "gaxios": "^6.0.0",
        "mocha": "^10.0.0",
        "promise-retry": "^2.0.0",
        "proxyquire": "^2.1.0",
        "sinon": "^18.0.0",
        "wait-port": "^1.0.4"
      }
    }
    

    Python

    Copy the following to your requirements.txt file:

    slackclient==2.9.4
    google-api-python-client==2.131.0
    

  3. Copy the following code into your Cloud Run function:

    Node.js

    const {CloudBillingClient} = require('@google-cloud/billing');
    const {InstancesClient} = require('@google-cloud/compute');
    
    const PROJECT_ID = process.env.GOOGLE_CLOUD_PROJECT;
    const PROJECT_NAME = `projects/${PROJECT_ID}`;
    const billing = new CloudBillingClient();
    
    exports.stopBilling = async pubsubEvent => {
      const pubsubData = JSON.parse(
        Buffer.from(pubsubEvent.data, 'base64').toString()
      );
      if (pubsubData.costAmount <= pubsubData.budgetAmount) {
        return `No action necessary. (Current cost: ${pubsubData.costAmount})`;
      }
    
      if (!PROJECT_ID) {
        return 'No project specified';
      }
    
      const billingEnabled = await _isBillingEnabled(PROJECT_NAME);
      if (billingEnabled) {
        return _disableBillingForProject(PROJECT_NAME);
      } else {
        return 'Billing already disabled';
      }
    };
    
    /**
     * Determine whether billing is enabled for a project
     * @param {string} projectName Name of project to check if billing is enabled
     * @return {bool} Whether project has billing enabled or not
     */
    const _isBillingEnabled = async projectName => {
      try {
        const [res] = await billing.getProjectBillingInfo({name: projectName});
        return res.billingEnabled;
      } catch (e) {
        console.log(
          'Unable to determine if billing is enabled on specified project, assuming billing is enabled'
        );
        return true;
      }
    };
    
    /**
     * Disable billing for a project by removing its billing account
     * @param {string} projectName Name of project disable billing on
     * @return {string} Text containing response from disabling billing
     */
    const _disableBillingForProject = async projectName => {
      const [res] = await billing.updateProjectBillingInfo({
        name: projectName,
        resource: {billingAccountName: ''}, // Disable billing
      });
      return `Billing disabled: ${JSON.stringify(res)}`;
    };

    Python

    import base64
    import json
    import os
    
    from googleapiclient import discovery
    
    PROJECT_ID = os.getenv("GCP_PROJECT")
    PROJECT_NAME = f"projects/{PROJECT_ID}"
    def stop_billing(data, context):
        pubsub_data = base64.b64decode(data["data"]).decode("utf-8")
        pubsub_json = json.loads(pubsub_data)
        cost_amount = pubsub_json["costAmount"]
        budget_amount = pubsub_json["budgetAmount"]
        if cost_amount <= budget_amount:
            print(f"No action necessary. (Current cost: {cost_amount})")
            return
    
        if PROJECT_ID is None:
            print("No project specified with environment variable")
            return
    
        billing = discovery.build(
            "cloudbilling",
            "v1",
            cache_discovery=False,
        )
    
        projects = billing.projects()
    
        billing_enabled = __is_billing_enabled(PROJECT_NAME, projects)
    
        if billing_enabled:
            __disable_billing_for_project(PROJECT_NAME, projects)
        else:
            print("Billing already disabled")
    
    
    def __is_billing_enabled(project_name, projects):
        """
        Determine whether billing is enabled for a project
        @param {string} project_name Name of project to check if billing is enabled
        @return {bool} Whether project has billing enabled or not
        """
        try:
            res = projects.getBillingInfo(name=project_name).execute()
            return res["billingEnabled"]
        except KeyError:
            # If billingEnabled isn't part of the return, billing is not enabled
            return False
        except Exception:
            print(
                "Unable to determine if billing is enabled on specified project, assuming billing is enabled"
            )
            return True
    
    
    def __disable_billing_for_project(project_name, projects):
        """
        Disable billing for a project by removing its billing account
        @param {string} project_name Name of project disable billing on
        """
        body = {"billingAccountName": ""}  # Disable billing
        try:
            res = projects.updateBillingInfo(name=project_name, body=body).execute()
            print(f"Billing disabled: {json.dumps(res)}")
        except Exception:
            print("Failed to disable billing, possibly check permissions")
    
    

  4. Set the Entry point to the correct function to execute:

    Node.js

    Set the Entry point to stopBilling.

    Python

    Set the Entry point to stop_billing.

  5. Review the list of environment variables set automatically to determine if you need to manually set the GOOGLE_CLOUD_PROJECT variable to the project that you want to disable Cloud Billing for.

  6. Click DEPLOY.

Configure service account permissions

Your Cloud Run function runs as an automatically created service account. To disable billing, you need to grant the service account permissions to any services on the project that it needs to modify by completing the following steps:

  1. Identify the correct service account by viewing the details of your Cloud Run function. The service account is listed at the bottom of the page.
  2. Go to the IAM page in the Google Cloud console to set the appropriate permissions.

    Go to the IAM page

  3. To modify the billing account permissions, in the Google Cloud console, go to the Billing Account management page, add the service account as a principle on the Cloud Billing account, and set the appropriate billing account permissions.

    Go to the Account management page in Cloud Billing

Learn more about how to configure permissions for Cloud Billing accounts.

Test that Cloud Billing is disabled

When the budget sends a notification, the specified project will no longer have a Cloud Billing account associated with it. To ensure your function works as expected, follow the steps in Test a Cloud Run function.

If successful, the project is no longer visible under the Cloud Billing account and resources in the project are disabled, including the Cloud Run function if it's in the same project.

To continue using Google Cloud resources in the project, in the Google Cloud console, manually re-enable Cloud Billing for your project.

What's next

Review other programmatic notification examples to learn how to do the following: