Skip to main content

Connect Your Google Cloud Organization

Prerequisites

  • Registered Spot account

  • You’ll need these permissions for the Google Cloud account:

    • roles/iam.organizationRoleAdmin to create and manage roles at the organization level.
    • roles/iam.serviceAccountCreator to create service accounts.
    • roles/iam.serviceAccountAdmin to manage service accounts.
    • roles/resourcemanager.projectIamAdmin to manage IAM policies for projects.

Connect Your Google Cloud Account

  1. In the Spot console, go to Eco and select Google Cloud > Log in to your Google Cloud account. After you sign in to Google Cloud, keep that browser tab open, then in the Spot console, click Next.

  2. If you don’t already have projects and datasets for your Cloud billing data, pricing, and recommendations, you can set them up:

  3. Create a service account and grant permissions to your Google Cloud environment:

    1. Open Cloud Shell.
    2. Set the project property in the core section to your project ID. This makes the project the default project, where the service account is bound. Make sure your data exports to this account.

      For example, gcloud config set project YOUR_MAIN_PROJECT_ID.

    3. Grant permissions to your account by running these commands in Cloud Shell: gcloud organizations add-iam-policy-binding YOUR_ORGANIZATION_ID --member="user:YOUR_EMAIL" --role="roles/iam.organizationRoleAdmin" gcloud projects add-iam-policy-binding YOUR_MAIN_PROJECT_ID --member="user:YOUR_EMAIL" --role="roles/iam.serviceAccountCreator" gcloud projects add-iam-policy-binding YOUR_MAIN_PROJECT_ID --member="user:YOUR_EMAIL" --role="roles/iam.serviceAccountAdmin" gcloud projects add-iam-policy-binding YOUR_MAIN_PROJECT_ID --member="user:YOUR_EMAIL" --role="roles/resourcemanager.projectIamAdmin"

    4. Create a file in Cloud Shell using a text editor, such as nano or vi. For example: nano setup_gcloud_iam_roles_and_service_accounts.sh.

      Copy this script and paste it into the file you just created:

      View the script
          set -uo pipefail

      FAILED=0
      log_error() {
      echo "ERROR: $1" >&2
      }

      log_success() {
      echo "SUCCESS: $1"
      }

      validate_command() {
      local err_msg="$1"
      local success_msg="$2"
      local cmd="$3"
      shift 3

      echo "Running: $cmd $*"

      if ! "$cmd" "$@"; then
      log_error "$err_msg"
      FAILED=$((FAILED+1))
      else
      log_success "$success_msg"
      fi
      }

      echo "Fetching org ID..."
      ANALYSIS_ORG_ID="$(gcloud projects get-ancestors $(gcloud config get-value project --quiet) | awk '/TYPE: organization/{print id} {id=$2}')"
      SERVICE_ACCOUNT_ORG_ID="$ANALYSIS_ORG_ID"

      CURRENT_PROJECT_ID=$(gcloud config get-value project --quiet)
      ANALYSIS_PROJECTS=("$CURRENT_PROJECT_ID")
      SERVICE_ACCOUNT_PROJECT_LIST=("$CURRENT_PROJECT_ID")

      ANALYSIS_ORG_ROLES=("roles/billing.viewer" "roles/browser")
      ANALYSIS_EMAILS=("ross.hardin@flexera.com" "greg.kuderna@flexera.com")
      ANALYSIS_PROJECT_ROLE="roles/bigquery.dataViewer"
      ANALYSIS_CUSTOM_ROLE_NAME="spot_read_only_custom_role"
      ANALYSIS_CUSTOM_ROLE_TITLE="Spot Read-Only Custom Role"
      ANALYSIS_CUSTOM_ROLE_DESCRIPTION="Spot Read-Only Permissions needed for programmatic visibility into commitment and cost data"
      ANALYSIS_CUSTOM_ROLE_PERMISSIONS="bigquery.capacityCommitments.get,bigquery.capacityCommitments.list,bigquery.jobs.listAll,cloudasset.assets.exportComputeCommitments,cloudasset.assets.listComputeCommitments,compute.commitments.get,compute.commitments.list,compute.instances.get,compute.instances.list,recommender.bigqueryCapacityCommitmentsInsights.get,recommender.bigqueryCapacityCommitmentsInsights.list,recommender.bigqueryCapacityCommitmentsRecommendations.get,recommender.bigqueryCapacityCommitmentsRecommendations.list,recommender.commitmentUtilizationInsights.get,recommender.commitmentUtilizationInsights.list,recommender.spendBasedCommitmentInsights.get,recommender.spendBasedCommitmentInsights.list,recommender.spendBasedCommitmentRecommendations.get,recommender.spendBasedCommitmentRecommendations.list,recommender.spendBasedCommitmentRecommenderConfig.get,recommender.usageCommitmentRecommendations.get,recommender.usageCommitmentRecommendations.list"
      SERVICE_ACCOUNT_PROJECT_ROLES=("roles/bigquery.dataViewer" "roles/bigquery.jobUser" "roles/bigquery.readSessionUser")
      SERVICE_ACCOUNT_NAME="spot-programmatic-access-role" #between 6 and 30 characters
      SERVICE_ACCOUNT_DESCRIPTION="Spot Service Account created for Programmatic Access to Resources"
      SERVICE_ACCOUNT_DISPLAY_NAME="spot-programmatic-access-service-account"
      SERVICE_ACCOUNT_CUSTOM_ROLE_NAME="spot_programmatic_access_sa"
      SERVICE_ACCOUNT_CUSTOM_ROLE_TITLE="Spot Programmatic Access Role"
      SERVICE_ACCOUNT_CUSTOM_ROLE_DESCRIPTION="Spot Custom Role for Programmatic Access"
      SERVICE_ACCOUNT_CUSTOM_ROLE_PERMISSIONS="monitoring.timeSeries.list,cloudquotas.quotas.get,cloudquotas.quotas.update,serviceusage.quotas.get,serviceusage.quotas.update,serviceusage.services.get,serviceusage.services.list,bigquery.jobs.create,bigquery.readsessions.create"

      echo "Validating service account IAM roles..."

      echo "Checking if service account $SERVICE_ACCOUNT_NAME exists..."
      if gcloud iam service-accounts describe "$SERVICE_ACCOUNT_NAME@$CURRENT_PROJECT_ID.iam.gserviceaccount.com" >/dev/null 2>&1; then
      echo "Validating service account IAM roles..."
      for ROLE in "roles/bigquery.dataViewer" "roles/bigquery.jobUser" "roles/bigquery.readSessionUser"; do
      echo "Checking if $SERVICE_ACCOUNT_NAME has $ROLE..."
      if [[ -z $(gcloud projects get-iam-policy "$CURRENT_PROJECT_ID" \
      --flatten="bindings[].members" \
      --filter="bindings.members:serviceAccount:$SERVICE_ACCOUNT_NAME@$CURRENT_PROJECT_ID.iam.gserviceaccount.com AND bindings.role:$ROLE" \
      --format="value(bindings.role)") ]]; then
      log_error "Service account does not have $ROLE"
      else
      log_success "Service account has $ROLE"
      fi
      done
      else
      log_success "Service account $SERVICE_ACCOUNT_NAME does not exist yet - skipping IAM role check"
      fi

      for ROLE in "${ANALYSIS_ORG_ROLES[@]}"; do
      for EMAIL in "${ANALYSIS_EMAILS[@]}"; do
      echo "Adding member: user:$EMAIL to role $ROLE ..."
      validate_command \
      "Failed to add user:$EMAIL to org role $ROLE" \
      "Added user:$EMAIL to org role $ROLE" \
      gcloud organizations add-iam-policy-binding $ANALYSIS_ORG_ID \
      --role=$ROLE \
      --member="user:$EMAIL"
      done
      done

      for PROJECT in "${ANALYSIS_PROJECTS[@]}"; do
      for EMAIL in "${ANALYSIS_EMAILS[@]}"; do
      echo "Adding user:$EMAIL to project role $ANALYSIS_PROJECT_ROLE in project $PROJECT..."
      validate_command \
      "Failed to add user:$EMAIL to project role" \
      "Added user:$EMAIL to project role in $PROJECT" \
      gcloud projects add-iam-policy-binding $PROJECT \
      --role=$ANALYSIS_PROJECT_ROLE \
      --member="user:$EMAIL"
      done
      done

      if gcloud iam roles describe "organizations/$ANALYSIS_ORG_ID/roles/$ANALYSIS_CUSTOM_ROLE_NAME" >/dev/null 2>&1; then
      log_success "Custom role $ANALYSIS_CUSTOM_ROLE_NAME already exists"
      else
      echo "Creating custom role $ANALYSIS_CUSTOM_ROLE_NAME..."
      validate_command \
      "Failed to create custom role" \
      "Created custom role $ANALYSIS_CUSTOM_ROLE_NAME" \
      gcloud iam roles create "$ANALYSIS_CUSTOM_ROLE_NAME" \
      --organization=$ANALYSIS_ORG_ID \
      --description="$ANALYSIS_CUSTOM_ROLE_DESCRIPTION" \
      --permissions="$ANALYSIS_CUSTOM_ROLE_PERMISSIONS" \
      --stage="GA" \
      --title="$ANALYSIS_CUSTOM_ROLE_TITLE"
      fi

      for EMAIL in "${ANALYSIS_EMAILS[@]}"; do
      echo "Granting custom role $ANALYSIS_CUSTOM_ROLE_NAME to $EMAIL..."
      validate_command \
      "Failed to grant custom role to $EMAIL" \
      "Granted custom role to $EMAIL" \
      gcloud organizations add-iam-policy-binding $ANALYSIS_ORG_ID \
      --member="user:$EMAIL" \
      --role="organizations/$ANALYSIS_ORG_ID/roles/$ANALYSIS_CUSTOM_ROLE_NAME"
      done

      # You will need roles/iam.serviceAccountCreator to create a service account
      # To Grant the service account access to the project, you need roles/resourcemanager.projectIamAdmin

      if gcloud iam service-accounts describe "$SERVICE_ACCOUNT_NAME@$CURRENT_PROJECT_ID.iam.gserviceaccount.com" >/dev/null 2>&1; then
      log_success "Service account $SERVICE_ACCOUNT_NAME already exists"
      else
      echo "Creating service account $SERVICE_ACCOUNT_NAME..."
      validate_command \
      "Failed to create service account" \
      "Created service account $SERVICE_ACCOUNT_NAME" \
      gcloud iam service-accounts create $SERVICE_ACCOUNT_NAME \
      --description="$SERVICE_ACCOUNT_DESCRIPTION" \
      --display-name="$SERVICE_ACCOUNT_DISPLAY_NAME"
      fi

      echo "Waiting for service account to propagate..."
      for i in {1..5}; do
      if gcloud iam service-accounts describe "$SERVICE_ACCOUNT_NAME@$CURRENT_PROJECT_ID.iam.gserviceaccount.com" >/dev/null 2>&1; then
      break
      fi
      echo "Still waiting..."
      sleep 2
      done

      for PROJECT in "${SERVICE_ACCOUNT_PROJECT_LIST[@]}"; do
      for ROLE in "${SERVICE_ACCOUNT_PROJECT_ROLES[@]}"; do
      echo "Adding member: serviceAccount:$SERVICE_ACCOUNT_NAME@$CURRENT_PROJECT_ID.iam.gserviceaccount.com to role $ROLE ..."
      validate_command \
      "Failed to add service account to project role $ROLE" \
      "Added service account to project role $ROLE" \
      gcloud projects add-iam-policy-binding $PROJECT \
      --role="$ROLE" \
      --member="serviceAccount:$SERVICE_ACCOUNT_NAME@$CURRENT_PROJECT_ID.iam.gserviceaccount.com"
      done
      done

      if gcloud iam roles describe "organizations/$SERVICE_ACCOUNT_ORG_ID/roles/$SERVICE_ACCOUNT_CUSTOM_ROLE_NAME" >/dev/null 2>&1; then
      log_success "Custom role $SERVICE_ACCOUNT_CUSTOM_ROLE_NAME already exists"
      else
      echo "Creating custom org level role for service account..."
      validate_command \
      "Failed to create service account custom role" \
      "Created custom org level role $SERVICE_ACCOUNT_CUSTOM_ROLE_NAME" \
      gcloud iam roles create "$SERVICE_ACCOUNT_CUSTOM_ROLE_NAME" \
      --organization=$SERVICE_ACCOUNT_ORG_ID \
      --description="$SERVICE_ACCOUNT_CUSTOM_ROLE_DESCRIPTION" \
      --permissions="$SERVICE_ACCOUNT_CUSTOM_ROLE_PERMISSIONS" \
      --stage="GA" \
      --title="$SERVICE_ACCOUNT_CUSTOM_ROLE_TITLE"
      fi

      echo "Granting custom role to service account..."
      validate_command \
      "Failed to grant custom role to service account" \
      "Granted custom role to service account" \
      gcloud organizations add-iam-policy-binding $SERVICE_ACCOUNT_ORG_ID \
      --member="serviceAccount:$SERVICE_ACCOUNT_NAME@$CURRENT_PROJECT_ID.iam.gserviceaccount.com" \
      --role="organizations/$SERVICE_ACCOUNT_ORG_ID/roles/$SERVICE_ACCOUNT_CUSTOM_ROLE_NAME"

      # You will need roles/iam.serviceAccountAdmin to create this service account key...
      # Or a relevant custom role with iam.serviceAccountKeys.create

      echo "Creating service account key..."
      validate_command \
      "Failed to create service account key" \
      "Created service account key" \
      gcloud iam service-accounts keys create ~/my-sa-key.json \
      --iam-account="$SERVICE_ACCOUNT_NAME@$CURRENT_PROJECT_ID.iam.gserviceaccount.com"

      echo "Downloading service account key..."
      validate_command \
      "Failed to download service account key" \
      "Downloaded service account key" \
      cloudshell download my-sa-key.json

      echo
      read -p "Press Enter once the file has finished downloading..."

      echo "Removing local key file..."
      validate_command \
      "Failed to remove local key file" \
      "Removed local key file" \
      rm ~/my-sa-key.json

      if [[ "$FAILED" -gt 0 ]]; then
      echo "Script completed with $FAILED errors"
      else
      echo "Onboarding script completed successfully."
      fi
      What the script does

      The script automates setting up IAM roles and service accounts in Google Cloud for programmatic access and analysis:

      • Retrieves organization IDs and project IDs for the GC projects.
      • Assigns predefined roles to specific email addresses for both organizational and project-level access.
      • Creates a custom IAM role with specific permissions for data visibility and analysis.
      • Sets up a service account with a custom role and permissions for programmatic access to resources.
      • Generates and downloads a service account key for use in automated processes.
    5. Save and close the file you just created with the script. If you’re using nano, Ctrl+O > Enter > Ctrl+X.

    6. Make the script executable by running this command: chmod +x setup_gcloud_iam_roles_and_service_accounts.sh.

    7. Run the script: ./setup_gcloud_iam_roles_and_service_accounts.sh.

    8. Click Download to save the service account key to your default downloads folder.

  4. Click Upload Saved JSON File, browse to find your service key: spot-programmatic-access-sa-key.json and click Next.

  5. Enter your BigQuery dataset and table IDs and click Next.

  6. Click Connect to Eco. It can take a few hours for your data to show up in the dashboard.