GCP Resource Hierarchy

Live project state drift

On April 15, 2026, gcloud projects describe bq-wh-nb returned lifecycleState: DELETE_REQUESTED. The read-only hierarchy, metadata, and IAM checks in this note were refreshed against that state. The label, lien, delete, and undelete examples remain documented command patterns and were not re-executed during this pass because they would change cloud state.

Bootstrap Access

Project-level hierarchy commands did not work initially with the service account because the project had BigQuery and Storage roles, but not Cloud Resource Manager access. The exact bootstrap sequence below is what unlocked the rest of this note.

Linux / Bash | bootstrap hierarchy access

Use this subsection when a service account can access product APIs like BigQuery but fails on gcloud projects describe, gcloud organizations list, or gcloud services list.

Enable the Cloud Resource Manager API

Before the first project, folder, or organization metadata command against a new project. It is typically triggered by gcloud projects describe fails with API [cloudresourcemanager.googleapis.com] not enabled. Run this as a project owner or another identity that already has Service Usage Admin capability on the target project. This is state-changing. Turn on the control-plane API that serves project, folder, and organization metadata.

Owner bootstrap only

A service account that is already locked out of Cloud Resource Manager cannot usually self-bootstrap this API. A human owner or a stronger automation identity must perform this first activation.

Enable once, then hand off

After cloudresourcemanager.googleapis.com is enabled and the service account has the right IAM roles, all read-only hierarchy inspection commands can move to the service account instead of a human user.

Enable the Cloud Resource Manager API for bq-wh-nb.

gcloud services enable cloudresourcemanager.googleapis.com --project=bq-wh-nb --quiet
Operation "operations/acat.p2-348557092514-46a7640e-718d-42dd-b9bb-52fca77e815f" finished successfully.

The successful operation ID confirms that cloudresourcemanager.googleapis.com was activated on project 348557092514. Without this API, even basic metadata commands such as gcloud projects describe can fail before IAM is evaluated.

Grant the minimum additional project roles to the service account

After the API is enabled and before validating the service account against hierarchy and service commands. It is typically triggered by the service account can authenticate, but project metadata, service listing, label mutation, or lien commands still fail. Run as a project owner on bq-wh-nb. These commands are state-changing because they modify the project’s IAM policy. Add only the roles needed for this chapter’s hierarchy, API enablement, label, and lien workflows.

Grant the bootstrap hierarchy and service roles to bq-wh-sa@bq-wh-nb.iam.gserviceaccount.com.

gcloud projects add-iam-policy-binding bq-wh-nb \
  --member="serviceAccount:bq-wh-sa@bq-wh-nb.iam.gserviceaccount.com" \
  --role="roles/browser" \
  --quiet
 
gcloud projects add-iam-policy-binding bq-wh-nb \
  --member="serviceAccount:bq-wh-sa@bq-wh-nb.iam.gserviceaccount.com" \
  --role="roles/serviceusage.serviceUsageAdmin" \
  --quiet
 
gcloud projects add-iam-policy-binding bq-wh-nb \
  --member="serviceAccount:bq-wh-sa@bq-wh-nb.iam.gserviceaccount.com" \
  --role="roles/resourcemanager.projectMover" \
  --quiet
 
gcloud projects add-iam-policy-binding bq-wh-nb \
  --member="serviceAccount:bq-wh-sa@bq-wh-nb.iam.gserviceaccount.com" \
  --role="roles/resourcemanager.lienModifier" \
  --quiet
Updated IAM policy for project [bq-wh-nb].
Updated IAM policy for project [bq-wh-nb].
Updated IAM policy for project [bq-wh-nb].
Updated IAM policy for project [bq-wh-nb].

The service account already had workload roles such as roles/bigquery.admin and roles/storage.admin. Those product-specific roles were not enough for hierarchy inspection because they do not grant Cloud Resource Manager or Service Usage permissions.

Verify the new role bindings

Immediately after applying IAM changes. It is typically triggered by you need to prove that the intended least-privilege bindings landed before switching to the service account. Read-only gcloud command. It queries the current IAM policy on the project. Confirm that the bootstrap roles are present and correctly attached to the service account.

List the hierarchy bootstrap roles currently granted to the service account.

gcloud projects get-iam-policy bq-wh-nb \
  --flatten="bindings[].members" \
  --filter="bindings.members:serviceAccount:bq-wh-sa@bq-wh-nb.iam.gserviceaccount.com AND (bindings.role:roles/browser OR bindings.role:roles/serviceusage.serviceUsageAdmin OR bindings.role:roles/resourcemanager.projectMover OR bindings.role:roles/resourcemanager.lienModifier)" \
  --format="table(bindings.role)"
ROLE
roles/browser
roles/resourcemanager.lienModifier
roles/resourcemanager.projectMover
roles/serviceusage.serviceUsageAdmin

The filtered IAM policy confirms that the four bootstrap roles are present. This verification is more useful than dumping the full project policy because it isolates the exact bindings you intended to create.

Activate the service account and target the project

After the bootstrap IAM changes are complete and before validating project-level commands as the service account. It is typically triggered by you want the rest of the hierarchy workflow to run under the non-human principal rather than the owner account. Runs in the local shell and writes credentials plus default project settings into the active gcloud configuration. This is state-changing for the local CLI profile. Switch the command context from the owner account to the service account that will execute the live examples.

Key file handling

JSON key files are long-lived secrets. Do not commit them to git, do not copy them into notebook output, and do not leave them on shared machines without filesystem protection.

Prefer keyless identities in production

For CI/CD and long-lived automation, prefer Workload Identity Federation or attached service identities. Use static JSON keys only when you explicitly cannot use a keyless pattern.

Activate the service account from the local key file and set bq-wh-nb as the active project.

gcloud auth activate-service-account --key-file=gcp-bq-key.json --quiet
gcloud config set project bq-wh-nb --quiet
Activated service account credentials for: [bq-wh-sa@bq-wh-nb.iam.gserviceaccount.com]
Updated property [core/project].

At this point the service account becomes the active CLI identity and the rest of the commands in this note can run without falling back to the owner account.

Resource Hierarchy Model

The full Google Cloud hierarchy has four conceptual levels: organization, folder, project, and resource. Projects are where billing, API enablement, and most day-to-day engineering work happen, but the higher levels still matter because IAM bindings and organization policy flow downward. A permission granted too high in the tree becomes visible to every descendant resource unless a more specific control blocks it.

Visualize the ancestor tree and inheritance direction.


flowchart TD
    subgraph ORG["Organization"]
        O["example.com<br>organization/123456789012"]
    end

    subgraph FOLDERS["Folders"]
        FPAD[" "]
        F1["folders/2100<br>Data Platform"]
        F2["folders/2200<br>Production"]
        FPAD ~~~ F1
        FPAD ~~~ F2
    end

    subgraph PROJECTS["Projects"]
        PPAD[" "]
        P1["projectId: bq-wh-nb<br>projectNumber: 348557092514"]
        P2["projectId: analytics-dev"]
        PPAD ~~~ P1
        PPAD ~~~ P2
    end

    subgraph RESOURCES["Resources"]
        RPAD[" "]
        R1["BigQuery datasets"]
        R2["Cloud Storage buckets"]
        R3["Cloud Run services"]
        RPAD ~~~ R1
        RPAD ~~~ R2
        RPAD ~~~ R3
    end

    O --> F1
    F1 --> F2
    F2 --> P1
    F1 --> P2
    style FPAD fill:transparent,stroke:transparent,color:transparent
    style PPAD fill:transparent,stroke:transparent,color:transparent
    style RPAD fill:transparent,stroke:transparent,color:transparent
    P1 --> R1
    P1 --> R2
    P1 --> R3
    O -. "IAM and org policy inherit downward" .-> F1
    F1 -. "Bindings cascade" .-> F2
    F2 -. "Bindings cascade" .-> P1
    P1 -. "Project IAM applies to resources" .-> R1

At the project layer, three operational facts matter most:

  1. A project is the billing boundary. One project can be attached to only one billing account at a time.
  2. A project is the API activation unit. Enabling an API in one project does not enable it in another.
  3. A project is the common IAM execution boundary for most day-to-day engineering commands.

Folder-level over-granting

An IAM binding granted on a folder applies to every project and resource underneath that folder. If a shared engineering folder contains both development and production projects, a broad role at folder scope can become an unintended privilege escalation path.

Grant high-scope roles only when the blast radius is intentional

Use folder or organization scope only for controls that genuinely need to be shared across all descendants. Keep operational roles as low in the tree as practical, usually at project scope.

Organization

Organizations only exist when the cloud estate is anchored to Google Workspace or Cloud Identity. A standalone project can exist without a visible organization parent, and that is exactly what the live bq-wh-nb environment demonstrates.

Linux / Bash | gcloud organizations list

Use this command to discover which organization resources are visible to the active identity. In the current environment the result is empty, which is operationally meaningful because it explains why folder-level commands below cannot be executed against a real ancestor.

List organizations visible to the active principal

At the start of a hierarchy audit or before planning folder-level changes. It is typically triggered by you need to determine whether the environment is organization-backed or a standalone project. Read-only control-plane query. No project mutation occurs. Identify visible organization IDs and confirm whether a higher-level resource exists above the project.

Return all organizations visible to the active service account.

gcloud organizations list --format=json
[]

The empty JSON array means the active principal sees no organization resources. In this environment that result aligns with the project metadata later in the note, where bq-wh-nb has no parent field. Without an organization ID, folder commands cannot target a live ancestor.

FlagSyntaxDescription
--formatgcloud organizations list --format=jsonControls output shape such as json, yaml, table(...), or value(...).
--filtergcloud organizations list --filter="displayName:Finance"Filters the returned organizations by field value.
--limitgcloud organizations list --limit=5Caps the number of returned rows.
--sort-bygcloud organizations list --sort-by=displayNameSorts the result set client-side.
--urigcloud organizations list --uriPrints only resource URIs instead of the default fields.

Linux / Bash | gcloud organizations describe

This command is only meaningful when an organization ID or domain exists. Because gcloud organizations list returned [], there is no live organization target in the current project.

Describe a specific organization

After you have a valid organization ID or domain. It is typically triggered by you need metadata such as displayName, directoryCustomerId, or owner.directoryCustomerId. Read-only control-plane query. Requires visibility to the organization resource. Confirm which organization a project belongs to and inspect the metadata that identifies that top-level ancestor.

Describe an organization by numeric ID or domain name.

gcloud organizations describe ORGANIZATION_ID
No live output in this environment: `gcloud organizations list` returned `[]`, so there is no visible organization ID to describe from `bq-wh-nb`.

When this command is valid, the output normally includes the organization resource name, display name, directory customer ID, and owner metadata. In bq-wh-nb, the correct operational conclusion is not “permission denied”; it is that no organization ancestor is currently visible at all.

FlagSyntaxDescription
ORGANIZATION_IDgcloud organizations describe 123456789012Numeric organization ID or domain name such as example.com.
--formatgcloud organizations describe 123456789012 --format=yamlChooses output layout for scripting or review.
--flattengcloud organizations describe 123456789012 --flatten=ownerFlattens nested fields before formatting or filtering.
--verbositygcloud organizations describe 123456789012 --verbosity=debugAdds more client-side logging for troubleshooting.

Folders

Folders are optional. They only exist beneath an organization and are useful when you need an intermediate administrative boundary between the company-wide organization and individual projects. Because bq-wh-nb has no visible organization ancestor, folder management in this environment is a command-shape reference rather than a live parent-child walkthrough.

Linux / Bash | gcloud resource-manager folders list

Use folder listing when an organization or parent folder exists and you need to enumerate the next layer down.

List child folders under an organization or parent folder

After confirming that an organization or folder parent exists. It is typically triggered by you need to inventory the folder tree or find the correct target parent for a new project. Read-only query against Cloud Resource Manager. Exactly one of --organization or --folder must be supplied. Enumerate child folders and confirm the folder topology above your projects.

List folders under an organization.

gcloud resource-manager folders list --organization=ORG_ID --format="table(name,displayName,parent.name,state)"
No live output in this environment: `bq-wh-nb` has no visible organization parent, so there is no `ORG_ID` available for a real folder listing.
FlagSyntaxDescription
--organizationgcloud resource-manager folders list --organization=123456789012Lists folders directly beneath an organization.
--foldergcloud resource-manager folders list --folder=2100Lists folders directly beneath another folder.
--filtergcloud resource-manager folders list --organization=123456789012 --filter="displayName:Prod"Filters returned folders.
--limitgcloud resource-manager folders list --organization=123456789012 --limit=10Limits the number of results.
--page-sizegcloud resource-manager folders list --organization=123456789012 --page-size=50Adjusts API paging size.
--sort-bygcloud resource-manager folders list --organization=123456789012 --sort-by=displayNameSorts the returned folders.
--urigcloud resource-manager folders list --organization=123456789012 --uriPrints resource URIs only.

Linux / Bash | gcloud resource-manager folders describe

Folder description is the next step after listing, when you need the metadata for one specific folder.

Describe a folder by ID

After obtaining a real folder ID. It is typically triggered by you need the display name, parent reference, or lifecycle state of a folder. Read-only query. Requires access to the folder resource. Inspect one folder in detail before moving projects or granting folder-level IAM.

Describe a folder by numeric ID.

gcloud resource-manager folders describe FOLDER_ID
No live output in this environment: there is no visible folder ID to describe because the project has no organization or folder ancestor.
FlagSyntaxDescription
FOLDER_IDgcloud resource-manager folders describe 3589215982Numeric ID of the folder to inspect.
--formatgcloud resource-manager folders describe 3589215982 --format=yamlControls output shape.
--flattengcloud resource-manager folders describe 3589215982 --flatten=parentFlattens nested fields before formatting.
--verbositygcloud resource-manager folders describe 3589215982 --verbosity=debugAdds troubleshooting output.

Linux / Bash | gcloud resource-manager folders create

Folder creation is an organization-governance operation, not an everyday application workflow. It should be rare, deliberate, and reviewed because every folder becomes a new inheritance point for IAM and policy.

Create a new folder beneath an organization or folder

During platform or environment design, not during routine application deployment. It is typically triggered by you need a new administrative boundary such as a business unit, environment group, or compliance partition. State-changing command. Requires a valid organization or parent folder and the authority to create folders there. Insert a new folder into the hierarchy so projects can inherit IAM and policy from a controlled intermediate parent.

Create a folder beneath an organization.

gcloud resource-manager folders create --display-name="Data Platform" --organization=ORG_ID
No live output in this environment: folder creation is not applicable because `bq-wh-nb` has no visible organization parent to host a folder.
FlagSyntaxDescription
--display-namegcloud resource-manager folders create --display-name="Data Platform" --organization=123456789012Human-readable folder name.
--organizationgcloud resource-manager folders create --display-name="Data Platform" --organization=123456789012Uses an organization as the parent.
--foldergcloud resource-manager folders create --display-name="Production" --folder=2100Uses another folder as the parent.
--asyncgcloud resource-manager folders create --display-name="Data Platform" --organization=123456789012 --asyncReturns before the create operation completes.
--tagsgcloud resource-manager folders create --display-name="Data Platform" --organization=123456789012 --tags=123/environment=productionBinds tags during create.

Linux / Bash | gcloud resource-manager folders move

Folder moves are powerful because they reparent an entire subtree, not just one resource.

Reparent a folder under a different parent

Only during a planned hierarchy redesign. It is typically triggered by teams, environments, or business units are being regrouped beneath a different parent folder or organization. State-changing control-plane command. Exactly one of --folder or --organization must be supplied. Move a folder to a new parent while keeping all child resources underneath it.

Folder move changes inheritance

Moving a folder changes the ancestor chain for every project and resource beneath that folder. That means IAM inheritance, policy inheritance, and any ancestor-based guardrails can all change in one operation.

Audit before and after the move

Before any folder move, export the current IAM and policy state. After the move, re-run the same audit commands and compare the effective bindings to confirm that production access did not widen unexpectedly.

Move a folder to a new parent.

gcloud resource-manager folders move FOLDER_ID --organization=ORG_ID
No live output in this environment: there is no live folder to move because `bq-wh-nb` is not currently beneath a visible organization or folder tree.
FlagSyntaxDescription
FOLDER_IDgcloud resource-manager folders move 123456789 --organization=123456789012The folder being reparented.
--organizationgcloud resource-manager folders move 123456789 --organization=123456789012Moves the folder directly under the organization.
--foldergcloud resource-manager folders move 123456789 --folder=2345Moves the folder under another folder.
--asyncgcloud resource-manager folders move 123456789 --organization=123456789012 --asyncReturns before the move operation completes.

Projects

Projects are where most engineers live operationally. They are the point where billing attaches, APIs are enabled, service accounts run, and resources like BigQuery datasets, buckets, and Cloud Run services are created. The read-only inspection commands in this section were refreshed live against bq-wh-nb, which is currently in DELETE_REQUESTED.

Linux / Bash | gcloud projects list

Project listing is the first sanity check before any destructive or environment-specific work. It tells you which projects the active principal can currently see.

List projects visible to the service account

Before choosing a project target or validating which projects a principal can access. It is typically triggered by you need to inventory visible projects or verify that the active identity is scoped correctly. Read-only Cloud Resource Manager query. Enumerate accessible projects with their IDs, numbers, and lifecycle states.

List the projects currently visible to the active service account that are pending deletion.

gcloud projects list --filter="lifecycleState:DELETE_REQUESTED" --format="table(projectId,name,projectNumber,lifecycleState)"
PROJECT_ID        NAME          PROJECT_NUMBER  LIFECYCLE_STATE
bq-wh-nb          BQ Database   348557092514    DELETE_REQUESTED
index-lab-2       Index Lab     1052700078743   DELETE_REQUESTED
index-lab-491012  index-lab     624680779669    DELETE_REQUESTED
seclab-dev-2026   Security Lab  935469410151    DELETE_REQUESTED
seclab-dev-ap-26  Security Lab  922174528852    DELETE_REQUESTED
seclab-dev-ap26   Security Lab  972728025985    DELETE_REQUESTED

The service account can now see bq-wh-nb directly through Cloud Resource Manager, which was not true before the bootstrap steps. The important nuance is that a plain gcloud projects list --filter="projectId=bq-wh-nb" currently returns no rows for this project, while the lifecycle-state filter above does. That suggests pending-deletion projects are not surfaced in the ordinary list path unless you ask for the deletion state explicitly.

FlagSyntaxDescription
--filtergcloud projects list --filter="lifecycleState:DELETE_REQUESTED"Restricts results by project metadata such as ID, name, labels, or lifecycle state.
--formatgcloud projects list --format="table(projectId,name)"Controls output layout for humans or scripts.
--limitgcloud projects list --limit=20Caps the number of rows returned.
--sort-bygcloud projects list --sort-by=nameSorts project output by one or more fields.
--page-sizegcloud projects list --page-size=50Controls API paging size.

Linux / Bash | gcloud projects describe

Project description is the canonical metadata check. It is how you confirm lifecycle state, labels, create time, and parent association before changing the project.

Describe the live project metadata

Before modifying labels, IAM, APIs, or billing on a project. It is typically triggered by you need to confirm exactly which project you are touching and what its current metadata looks like. Read-only Cloud Resource Manager query. Retrieve the authoritative metadata record for one project.

Describe the live metadata for bq-wh-nb.

gcloud projects describe bq-wh-nb --format="yaml(projectId,projectNumber,name,lifecycleState,parent,labels,createTime)"
createTime: '2026-03-22T16:26:19.672Z'
lifecycleState: DELETE_REQUESTED
name: BQ Database
projectId: bq-wh-nb
projectNumber: '348557092514'

Three details matter here. First, lifecycleState: DELETE_REQUESTED means the project is already inside the 30-day recovery window and should be treated as pending deletion rather than as a healthy operating baseline. Google’s current Resource Manager guidance also notes that billing is disconnected at shutdown and that some services can delete data sooner than the full 30-day window. Second, the absence of both parent and labels confirms this project currently has no visible organization or folder ancestor and no labels applied. Third, the createTime value gives you a precise audit point for when the project entered the environment.

FieldMeaningOperational implication
nameHuman-readable project display name.Useful for audits and consoles, but not the durable automation key.
projectIdImmutable string identifier used by most APIs and CLIs.Use this in --project flags, client configs, and Terraform.
projectNumberImmutable numeric identifier.Required by some IAM, service-agent, and resource-manager APIs.
lifecycleStateCurrent project state.DELETE_REQUESTED means the recovery window is open; restore is possible, but the project should be treated as pending deletion.
createTimeRFC 3339 creation timestamp.Useful for audit trails and environment age tracking.
parentFolder or organization ancestor, if present.Missing here means there is no visible ancestor resource.
labelsArbitrary project metadata tags.Missing here means no labels are currently applied.
FlagSyntaxDescription
--formatgcloud projects describe bq-wh-nb --format=yamlControls output serialization.
--flattengcloud projects describe bq-wh-nb --flatten=labelsFlattens nested fields before formatting.
--verbositygcloud projects describe bq-wh-nb --verbosity=debugAdds client debug logging.

Linux / Bash | gcloud projects create

Project creation is a control-plane provisioning step, not a normal application action. It is safe only when you are prepared to consume a new globally unique project ID and, in most cases, attach billing afterward.

Create a new project

During environment bootstrap or platform expansion. It is typically triggered by you need a new isolated administrative and billing boundary. State-changing command. It creates a brand-new Google Cloud project and may optionally attach it to a folder or organization. Provision a project that can later receive APIs, billing, IAM, and workload resources.

Create a new project with a specific name and optional labels.

gcloud projects create PROJECT_ID --name="Project Name" --labels=env=dev
No live output in this environment: a throwaway project was intentionally not created because project IDs are globally unique, billing attachment is a separate follow-up step, and the bootstrap goal for this note was inspection and governance rather than provisioning extra projects.
FlagSyntaxDescription
PROJECT_IDgcloud projects create example-foo-bar-1Immutable project ID chosen at create time.
--namegcloud projects create example-foo-bar-1 --name="Happy project"Human-readable project name.
--labelsgcloud projects create example-foo-bar-1 --labels=env=devAdds labels during create.
--foldergcloud projects create example-2 --folder=12345Creates the project under a folder parent.
--organizationgcloud projects create example-3 --organization=2048Creates the project under an organization parent.
--set-as-defaultgcloud projects create example-foo-bar-1 --set-as-defaultSets the new project as the active core/project.
--no-enable-cloud-apisgcloud projects create example-foo-bar-1 --no-enable-cloud-apisSkips default cloudapis.googleapis.com enablement.

Linux / Bash | project lifecycle

Project deletion is intentionally slow because Google Cloud gives you a recovery window. That is a safety feature, not a reason to be casual about destructive commands.

Soft-delete a project

Only when you are certain the project is no longer required. It is typically triggered by environment retirement, cost cleanup, or a deliberate rebuild. State-changing destructive command. It starts a 30-day recovery window rather than immediate irreversible destruction. Move a project from ACTIVE into a recoverable deletion state. Only projects that are still ACTIVE can be shut down.

Project deletion is wide-scope

Deleting a project does not remove one resource. It requests deletion of the entire administrative boundary: APIs, service accounts, datasets, buckets, logs, and dependent automation all become affected at once.

Verify the target before delete

Before any gcloud projects delete, run gcloud config get-value project and gcloud projects describe PROJECT_ID to confirm the exact project ID, project number, and lifecycle state you are about to affect.

Request deletion of a project.

gcloud projects delete PROJECT_ID
Not run live in this refactor pass: the current reference project is already `DELETE_REQUESTED`, and Google Cloud only allows shutdown from the `ACTIVE` lifecycle state.

Restore a project during the recovery window

After an accidental delete request and before the recovery window closes. It is typically triggered by the project entered DELETE_REQUESTED, but the resources still need to be preserved. State-changing recovery command. Works only during the soft-delete retention window. Return a project to ACTIVE before irreversible deletion proceeds.

Restore a project that is still inside the undelete window.

gcloud projects undelete PROJECT_ID
Not run live in this refactor pass: `bq-wh-nb` meets the `DELETE_REQUESTED` precondition, but restoring it would change the current cloud state and was intentionally left for an explicit recovery task.
FlagSyntaxDescription
PROJECT_IDgcloud projects delete my-projectThe project being deleted or undeleted.
--quietgcloud projects delete my-project --quietSuppresses the interactive confirmation prompt.

Linux / Bash | project labels

Labels are the lightest-weight governance metadata you can add to a project. They are cheap, script-friendly, and ideal for filtering, but they are not access controls. On April 15, 2026, the installed Google Cloud SDK version 563.0.0 still exposes project label mutation on the alpha track in this environment: gcloud projects update only renames projects, while gcloud alpha projects update is still the path that exposes --update-labels and --remove-labels.

Add a label to the project

Before introducing cost allocation, environment filtering, or automation targeting that depends on project metadata. It is typically triggered by A project needs machine-readable metadata such as environment, team, or owner. State-changing project metadata update through Cloud Resource Manager. Attach a label key/value pair to the project.

Add the label codex-bootstrap=enabled to bq-wh-nb.

gcloud alpha projects update bq-wh-nb --update-labels=codex-bootstrap=enabled --quiet
PROJECT_ID  NAME         PROJECT_NUMBER  ENVIRONMENT
bq-wh-nb    BQ Database  348557092514

The update command returns the standard project summary table rather than a label dump. The important effect is the successful metadata write, which is verified immediately below.

Verify the label mutation

Immediately after adding or removing labels. It is typically triggered by you need proof that the update reached Cloud Resource Manager. Read-only metadata query. Confirm the exact current label set on the project.

Describe the project and return only the label block.

gcloud projects describe bq-wh-nb --format="yaml(projectId,labels)"
labels:
  codex-bootstrap: enabled
projectId: bq-wh-nb

Remove the temporary label

After validation or when the label no longer reflects reality. It is typically triggered by cleanup after a temporary test label or a metadata correction. State-changing metadata update. Remove one or more labels without touching the rest of the project.

Remove the temporary bootstrap label from bq-wh-nb.

gcloud alpha projects update bq-wh-nb --remove-labels=codex-bootstrap --quiet
PROJECT_ID  NAME         PROJECT_NUMBER  ENVIRONMENT
bq-wh-nb    BQ Database  348557092514

The remove-labels command returns the same project summary shape. The important effect is that the temporary label is gone, which is verified immediately below.

Filter projects by label

During inventory, governance audits, or automation that targets only one class of projects. It is typically triggered by you need to select projects by metadata rather than by manually curated lists. Read-only list command with server-side filtering. Return only the projects whose labels match the filter expression.

List projects labeled env=dev.

gcloud projects list --filter="labels.env=dev" --format=json
[]

The empty result is expected because bq-wh-nb currently has no permanent labels. In a real environment, this pattern is how you target all development or platform projects without hardcoding project IDs.

FlagSyntaxDescription
--update-labelsgcloud alpha projects update bq-wh-nb --update-labels=env=devAdds or changes one or more labels.
--remove-labelsgcloud alpha projects update bq-wh-nb --remove-labels=envRemoves one or more label keys.
--clear-labelsgcloud alpha projects update bq-wh-nb --clear-labelsRemoves all labels before any optional updates.
--namegcloud alpha projects update bq-wh-nb --name="BQ Database"Renames the project display name.
--filtergcloud projects list --filter="labels.env=dev"Restricts project listing by label criteria.

Linux / Bash | project liens

Liens are deletion-protection controls. They do not manage access, but they stop specific destructive operations until the lien is explicitly removed.

Create a temporary deletion-protection lien

Before handing a sensitive project to automation or before a risky administrative period. It is typically triggered by you need an extra guardrail against accidental project deletion. State-changing alpha command. Requires lien modification permission on the project. Add a project lien that blocks resourcemanager.projects.delete.

Create a lien that blocks project deletion.

gcloud alpha resource-manager liens create \
  --project=bq-wh-nb \
  --reason="Temporary Codex lien validation" \
  --restrictions="resourcemanager.projects.delete" \
  --origin="codex-validation" \
  --format="value(name)"
liens/p348557092514-l11906663-8e8e-44b3-8175-f770f6bc32c3

The returned resource name is the authoritative lien handle. You need that ID later if you want to delete the lien.

List active liens on the project

After creating a lien or when investigating why a delete operation is blocked. It is typically triggered by A project cannot be deleted or you need to audit the current deletion-protection state. Read-only alpha command. Show every active lien attached to the current project.

List all current liens on bq-wh-nb.

gcloud alpha resource-manager liens list --project=bq-wh-nb --format="table(name,reason,origin,restrictions)"
NAME                                                 REASON                           ORIGIN            RESTRICTIONS
p348557092514-l11906663-8e8e-44b3-8175-f770f6bc32c3  Temporary Codex lien validation  codex-validation  ['resourcemanager.projects.delete']

The restrictions value is the key field. Here it shows that the lien blocks project deletion specifically, not every possible project mutation.

Delete the temporary lien after validation

After the protection test is complete or when the project should become deletable again. It is typically triggered by cleanup after a temporary guardrail or a planned project retirement. State-changing alpha command. Remove the lien so the restricted operation can proceed in the future.

Delete the test lien by lien ID.

gcloud alpha resource-manager liens delete p348557092514-l11906663-8e8e-44b3-8175-f770f6bc32c3 --quiet
Deleted [liens/p348557092514-l11906663-8e8e-44b3-8175-f770f6bc32c3].

Verify that no liens remain

Immediately after deleting a lien. It is typically triggered by you need to prove that the project is no longer protected by a lingering restriction. Read-only alpha list command. Confirm that the project has returned to a no-lien state.

Return the remaining liens as JSON after cleanup.

gcloud alpha resource-manager liens list --project=bq-wh-nb --format=json
[]
FlagSyntaxDescription
--reasongcloud alpha resource-manager liens create --reason="Temporary Codex lien validation" ...Human-readable explanation for the lien.
--restrictionsgcloud alpha resource-manager liens create --restrictions="resourcemanager.projects.delete" ...IAM permission(s) curtailed by the lien.
--origingcloud alpha resource-manager liens create --origin="codex-validation" ...Source string recorded on the lien.
--projectgcloud alpha resource-manager liens list --project=bq-wh-nbTargets the current project explicitly.
LIEN_IDgcloud alpha resource-manager liens delete p348557092514-l...The specific lien resource ID to remove.

Cross-Hierarchy Navigation Patterns

Cross-hierarchy navigation is where the parent-child model becomes operational. These patterns answer three recurring questions: “What is the parent of this project?”, “Which IAM bindings exist here?”, and “How would I navigate upward if a folder existed?”

Linux / Bash | find the parent of the current project

This pattern is the fastest way to learn whether a project is attached to a folder or organization.

Return the parent type and parent ID

At the start of any hierarchy investigation. It is typically triggered by you need to know whether folder or organization commands are applicable to the current project. Read-only metadata query. Return the immediate parent reference of the current project.

Return the parent type and parent ID for bq-wh-nb.

gcloud projects describe bq-wh-nb --format="value(parent.type,parent.id)"
 

The blank output is meaningful: bq-wh-nb has no visible parent resource in this environment. That is why organization and folder sections in this note cannot be populated with live ancestor objects.

FlagSyntaxDescription
--formatgcloud projects describe bq-wh-nb --format="value(parent.type,parent.id)"Extracts only the parent fields instead of the full metadata record.

Linux / Bash | audit direct project IAM bindings

This pattern does not compute the full inherited effective policy, but it shows the bindings applied directly on the project itself.

List the direct roles granted to the service account on the project

During access reviews or after changing IAM policy. It is typically triggered by you need to prove which roles are bound directly on the project. Read-only IAM policy query. Show the direct project-level bindings attached to the service account.

List the roles granted directly on bq-wh-nb to the service account used in this note.

gcloud projects get-iam-policy bq-wh-nb \
  --flatten="bindings[].members" \
  --filter="bindings.members:serviceAccount:bq-wh-sa@bq-wh-nb.iam.gserviceaccount.com" \
  --format="table(bindings.role)"
ROLE
roles/bigquery.admin
roles/browser
roles/datastore.owner
roles/datastore.user
roles/iam.serviceAccountAdmin
roles/iam.workloadIdentityPoolAdmin
roles/resourcemanager.lienModifier
roles/resourcemanager.projectMover
roles/run.developer
roles/serviceusage.serviceUsageAdmin
roles/storage.admin
roles/storage.objectUser

These are direct project bindings, not the full inherited effective policy. The current binding set is broader than the original bootstrap-only role set because the service account now also has direct IAM administration, Workload Identity Pool administration, and Cloud Run development roles on the project. In an organization-backed environment, you would run the equivalent IAM policy commands at organization and folder scope as well, then reason about inheritance from top to bottom.

FlagSyntaxDescription
--flattengcloud projects get-iam-policy bq-wh-nb --flatten="bindings[].members"Expands nested IAM arrays so each binding can be filtered row by row.
--filtergcloud projects get-iam-policy bq-wh-nb --filter="bindings.members:serviceAccount:..."Restricts the policy view to a specific member or role.
--formatgcloud projects get-iam-policy bq-wh-nb --format="table(bindings.role)"Renders the final policy slice as a table.

Linux / Bash | list projects under a folder

This is the pattern you would use in an organization-backed environment to inventory all projects under one folder. It is included here because it is one of the most common hierarchy-audit tasks, even though the current environment does not expose a folder ancestor.

List projects beneath a folder

During environment inventories or folder-level access reviews. It is typically triggered by you know the folder ID and need the projects contained beneath it. Read-only list pattern. Requires a real folder ancestor and visibility to it. Enumerate projects scoped by one folder rather than by the full account view.

List projects whose parent folder ID matches a specific folder.

gcloud projects list --filter="parent.id=FOLDER_ID AND parent.type=folder" --format="table(projectId,name,lifecycleState)"
No live output in this environment: `bq-wh-nb` has no visible folder ancestor, so there is no real `FOLDER_ID` available for a live folder-scoped project listing.
FlagSyntaxDescription
--filtergcloud projects list --filter="parent.id=12345 AND parent.type=folder"Restricts projects to a folder parent.
--formatgcloud projects list --format="table(projectId,name)"Controls output shape for human review or automation.

Terraform Equivalents

The CLI is useful for discovery and one-off administration, but infrastructure-as-code is the safer long-term pattern when you want the hierarchy and bindings to be reproducible.

Terraform resource mapping

Map the hierarchy concepts to Terraform resources.

resource "google_project" "warehouse" {
  project_id = "bq-wh-nb"
  name       = "BQ Database"
}
 
resource "google_folder" "data_platform" {
  display_name = "Data Platform"
  parent       = "organizations/123456789012"
}
 
resource "google_organization_iam_member" "viewer" {
  org_id = "123456789012"
  role   = "roles/browser"
  member = "serviceAccount:bq-wh-sa@bq-wh-nb.iam.gserviceaccount.com"
}

GCP Resource Hierarchy References