Deploying
GoogleCloudPlatform/race-condition
Guides deployment of Race Condition to a GCP project. An agent skill from GoogleCloudPlatform/race-condition.
Set up the Datadog Google Cloud integration with Terraform - creates a service account in the host project, lets Datadog's delegate principal impersonate it via roles/iam.serviceAccountTokenCreator…
$ npx skills add datadog-labs/agent-skills --skill dd-gcp-integration -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install datadog-labs/agent-skills dd-gcp-integration --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/datadog-labs/agent-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/dd-gcp-integration .claude/skills/dd-gcp-integration && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "dd-gcp-integration" agent skill from https://github.com/datadog-labs/agent-skills/tree/main/dd-gcp-integration into .claude/skills/dd-gcp-integration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "dd-gcp-integration", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/datadog-labs/agent-skills/tree/main/dd-gcp-integrationType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add datadog-labs/agent-skills --skill dd-gcp-integration -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install datadog-labs/agent-skills dd-gcp-integration --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/datadog-labs/agent-skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/dd-gcp-integration .agents/skills/dd-gcp-integration && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "dd-gcp-integration" agent skill from https://github.com/datadog-labs/agent-skills/tree/main/dd-gcp-integration into .agents/skills/dd-gcp-integration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "dd-gcp-integration", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add datadog-labs/agent-skills --skill dd-gcp-integration -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install datadog-labs/agent-skills dd-gcp-integration --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/datadog-labs/agent-skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/dd-gcp-integration .cursor/skills/dd-gcp-integration && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "dd-gcp-integration" agent skill from https://github.com/datadog-labs/agent-skills/tree/main/dd-gcp-integration into .cursor/skills/dd-gcp-integration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "dd-gcp-integration", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/datadog-labs/agent-skills.git --path dd-gcp-integration--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add datadog-labs/agent-skills --skill dd-gcp-integration -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install datadog-labs/agent-skills dd-gcp-integration --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/datadog-labs/agent-skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/dd-gcp-integration .gemini/skills/dd-gcp-integration && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "dd-gcp-integration" agent skill from https://github.com/datadog-labs/agent-skills/tree/main/dd-gcp-integration into .gemini/skills/dd-gcp-integration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "dd-gcp-integration", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install datadog-labs/agent-skills dd-gcp-integrationInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add datadog-labs/agent-skills --skill dd-gcp-integration -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/datadog-labs/agent-skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/dd-gcp-integration .github/skills/dd-gcp-integration && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "dd-gcp-integration" agent skill from https://github.com/datadog-labs/agent-skills/tree/main/dd-gcp-integration into .github/skills/dd-gcp-integration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "dd-gcp-integration", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add datadog-labs/agent-skills --skill dd-gcp-integration -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install datadog-labs/agent-skills dd-gcp-integration --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/datadog-labs/agent-skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/dd-gcp-integration .opencode/skills/dd-gcp-integration && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "dd-gcp-integration" agent skill from https://github.com/datadog-labs/agent-skills/tree/main/dd-gcp-integration into .opencode/skills/dd-gcp-integration/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "dd-gcp-integration", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
dd-gcp-integrationSet up the Datadog Google Cloud integration with Terraform - creates a service account in the host project, lets Datadog's delegate principal impersonate it via roles/iam.serviceAccountTokenCreator…
Dd GCP Integration is an agent skill from datadog-labs/agent-skills. Set up the Datadog Google Cloud integration with Terraform - creates a service account in the host project, lets Datadog's delegate principal impersonate it via roles/iam.serviceAccountTokenCreator (no service-account keys), enables the required APIs, grants the monitoring roles across the chosen projects and folders, and registers the account through datadogintegrationgcpsts. Use when the user wants to monitor GCP resources such as Compute Engine, Cloud SQL, GKE, Cloud Run, or Pub/Sub, wants to connect a GCP…
Its SKILL.md is about 8k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files, including reference files (for example `references/terraform.md`).
It sits in DevOps & Cloud, covering Infrastructure as code and Event-driven systems. It works with Google Cloud, Datadog, Terraform and Cloud Run. The repository describes itself as: Public repository for Datadog Agent Skills. The licence is MIT.
4 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit d2411cc. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
terraformcurlgcloudjqtofuFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
us3.datadoghq.comus5.datadoghq.comap1.datadoghq.comap2.datadoghq.comuk1.datadoghq.comdeveloper.hashicorp.comcloudresourcemanager.googleapis.comAlso links to:
docs.datadoghq.comFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
DD_API_KEYDD_APP_KEYGOOGLE_APPLICATION_CREDENTIALSFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Dd GCP Integration loads about 8k tokens when it runs, and up to ~10k if it reads all its reference files. Until then it costs about 166 tokens; SKILL.md has 2,778 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check noted patterns worth knowing about, such as sudo or a known installer.
to `.env.local` / `.env`) and validate both keys - they fail independently:for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\"a wrong non-empty `DD_SITE` sitting in `.env` would survive it and every call would go tofor f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\", not ':=': a wrong non-empty DD_SITE in .env would otherwise survive.for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\", not ':=': a wrong non-empty DD_SITE in .env would otherwise survive.for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive.for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from datadog-labs/agent-skills at commit d2411cc, republished under its MIT licence (© datadog-labs). 2,778 words, ~8,044 tokens.
.claude/skills/dd-gcp-integration/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.You are helping a user set up the Datadog GCP integration using Terraform.
The integration creates a GCP service account in the customer's project and grants Datadog's delegate principal
the ability to impersonate it via roles/iam.serviceAccountTokenCreator. This avoids long-lived keys entirely.
This is a hands-on setup: run the commands yourself as part of the conversation rather than handing the
user a list, keep them in the loop, and pause for confirmation before terraform apply.
Terraform or OpenTofu. Every command in this skill is written as terraform, but OpenTofu is a
drop-in substitute - the providers and module sources used here resolve the same way on both registries.
Check which binary the user actually has before Phase 1:
command -v terraform tofuIf only tofu is on the PATH, read every terraform <subcommand> below as tofu <subcommand>. If both
are present, ask which one the user wants rather than guessing.
Datadog credentials. Load DD_SITE / DD_API_KEY / DD_APP_KEY from the environment (falling back
to .env.local / .env) and validate both keys - they fail independently:
for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done
: "${DD_SITE:=datadoghq.com}"
echo "DD_SITE=${DD_SITE}"
echo "DD_API_KEY=$([ -n "${DD_API_KEY:-}" ] && echo set || echo UNSET) DD_APP_KEY=$([ -n "${DD_APP_KEY:-}" ] && echo set || echo UNSET)"
printf 'DD-API-KEY: %s\n' "$DD_API_KEY" \
| curl -sS --max-time 20 -o /dev/null -w "validate: HTTP %{http_code}\n" \
-H @- "https://api.${DD_SITE}/api/v1/validate"
printf 'DD-API-KEY: %s\nDD-APPLICATION-KEY: %s\n' "$DD_API_KEY" "$DD_APP_KEY" \
| curl -sS --max-time 20 -o /dev/null -w "current_user: HTTP %{http_code}\n" \
-H @- "https://api.${DD_SITE}/api/v2/current_user"| Result | Meaning | What to do |
|---|---|---|
Both 200 | Keys are good for this site | Continue to Phase 1 |
validate is 403 | The API key is invalid, or belongs to a different region than DD_SITE | Ask which site the key belongs to, fix DD_SITE, re-check |
validate 200, current_user 403 | The app key is wrong or from another region - not the API key | Get one from <APP_BASE>/organization-settings/application-keys |
| Either key unset | Nothing to validate | On a commercial site, run the dd-account-setup skill, then come back. On ddog-gov.com or us2.ddog-gov.com, ask the user for the keys directly - that skill validates DD_SITE against a list that excludes both government sites and will reject them |
App URL. The Datadog app host is not app.${DD_SITE} for every site. It is
https://app.datadoghq.com (US1), https://app.datadoghq.eu (EU1), https://app.ddog-gov.com (Gov),
and for every other site it is https://${DD_SITE} itself - https://us3.datadoghq.com,
https://us5.datadoghq.com, https://ap1.datadoghq.com, https://ap2.datadoghq.com,
https://uk1.datadoghq.com. Resolve it once and substitute it wherever <APP_BASE> appears below.
Full list: https://docs.datadoghq.com/getting_started/site/
Remember the resolved DD_SITE. Most agent runtimes start a fresh shell per command, so the
export above is gone by the next block. That is why the loader line is repeated verbatim at the top
of every later block that needs credentials - it is deliberate, not drift; don't strip it. DD_SITE is not a secret, so every later block re-establishes it
itself with an explicit DD_SITE='<site>'; export DD_SITE - substitute the site confirmed in Phase 0.
It is a plain assignment rather than : "${DD_SITE:=...}" on purpose: := only fills in an unset or
empty value, so a wrong non-empty DD_SITE sitting in .env would survive it and every call would go to
the wrong region. The keys are guarded with :? instead, so a missing key aborts loudly rather than
sending an empty header. Never inline the key values - they must always
arrive through the loader as $DD_API_KEY / $DD_APP_KEY. Two consequences follow, and both are
deliberate:
printf 'DD-API-KEY: %s\n' "$DD_API_KEY" | curl -H @- ....
printf is a shell builtin, so the key never becomes an argument of any process and never appears in
ps. Writing -H "DD-API-KEY: $DD_API_KEY" instead would put it in curl's argv. (-H @- needs
curl 7.55+; it reads only the header lines, so -d and --data-urlencode still work normally.)DD_API_KEY / DD_APP_KEY from the environment, so there are no root variables, no -var=
arguments, and nothing for Terraform to record in state or a saved plan. (OCI is the exception - its
module needs them as inputs, so there they travel as TF_VAR_*.)Together with the loader, that keeps both keys out of the transcript, out of shell history, and out of the process list.
Tools. terraform is required, plus jq for Phase 1. The gcloud CLI is optional for discovery,
but the google provider needs ambient GCP credentials either way
(gcloud auth application-default login, or GOOGLE_APPLICATION_CREDENTIALS):
command -v terraform || echo "MISSING terraform - https://developer.hashicorp.com/terraform/install"
command -v jq || echo "MISSING jq - needed to read the delegate email in Phase 1"
command -v gcloud >/dev/null 2>&1 && echo "gcloud: available" || echo "gcloud: not installed"If jq isn't available, read data.attributes.delegate_account_email out of the raw JSON response in
Phase 1 yourself instead of piping through jq.
The snippets here are POSIX shell. Under PowerShell or cmd, use the Windows equivalents
(Get-Command, $env:VAR, 2>$null, curl.exe) - same calls, same order.
Datadog impersonates the customer's service account through a per-org delegate service account. Get its email - GET first, and only POST if the org has no delegate yet, so an existing one is reused rather than re-created:
for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done
DD_SITE='datadoghq.com'; export DD_SITE # <- replace with the site confirmed in Phase 0.
# Explicit assignment, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive.
: "${DD_API_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"; : "${DD_APP_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"
resp=$(printf 'DD-API-KEY: %s\nDD-APPLICATION-KEY: %s\n' "$DD_API_KEY" "$DD_APP_KEY" \
| curl -sS -w '\n%{http_code}' -X GET \
-H @- "https://api.${DD_SITE}/api/v2/integration/gcp/sts_delegate")
code=$(printf '%s' "$resp" | tail -1); body=$(printf '%s' "$resp" | sed '$d')
if [ "$code" = "404" ]; then
echo "no delegate exists yet (HTTP 404) - creating one"
resp=$(printf 'DD-API-KEY: %s\nDD-APPLICATION-KEY: %s\n' "$DD_API_KEY" "$DD_APP_KEY" \
| curl -sS -w '\n%{http_code}' -X POST -H "Content-Type: application/json" -d '{}' \
-H @- "https://api.${DD_SITE}/api/v2/integration/gcp/sts_delegate")
code=$(printf '%s' "$resp" | tail -1); body=$(printf '%s' "$resp" | sed '$d')
elif [ "$code" != "200" ]; then
echo "GET sts_delegate returned HTTP $code - not creating anything. Response:"
printf '%s\n' "$body"
echo "403 means the app key lacks permission, 429 is rate limiting, 000 is a transport failure, 5xx is"
echo "server-side. None of those mean 'no delegate exists', so a POST here could create one you did not"
echo "intend. Resolve the error, then re-run. If the body says no delegate exists, POST explicitly."
exit 1
fi
[ "$code" = "200" ] || { echo "sts_delegate failed with HTTP $code:"; printf '%s\n' "$body"; exit 1; }
email=$(printf '%s' "$body" | jq -r '.data.attributes.delegate_account_email // empty')
case "$email" in
*@*.iam.gserviceaccount.com) echo "DATADOG_PRINCIPAL_ID=$email" ;;
*) echo "did not get a delegate service-account email; response was:"; printf '%s\n' "$body"; exit 1 ;;
esacRemember the printed email as DATADOG_PRINCIPAL_ID - the Terraform below needs it. It is org-specific
and cannot be hardcoded.
Never continue on an empty or null email. The guards above exist because curl -s | jq -r on an
error response prints null, which would silently become member = "serviceAccount:null" in the
Terraform and produce an integration that cannot authenticate.
If jq is unavailable, run the block exactly as written except the email=$(... | jq -r ...) line -
do not remove the printf ... | curl -H @- pipeline, which is what supplies authentication. Read
data.attributes.delegate_account_email out of $body yourself and apply the same rule: refuse to proceed
unless it looks like a ...@....iam.gserviceaccount.com address.
Ask the user if they already know which GCP project IDs and/or folder IDs they want Datadog to monitor.
If they do, collect:
If they don't know or want help figuring it out:
If the gcloud CLI is available, offer to discover their GCP
environment. Explain that you will use gcloud to list their organizations, folders, and projects
so they can pick which ones to monitor. This is best-effort - run each command independently and
work with whatever succeeds:
Organizations:
gcloud organizations list --format="table(displayName, name)"Folders (uses the REST API to search all active folders across the org; also requires gcloud
for the access token):
printf 'Authorization: Bearer %s\n' "$(gcloud auth print-access-token)" \
| curl -sS -H @- -H "Content-Type: application/json" \
-d '{"query": "lifecycleState=ACTIVE"}' \
"https://cloudresourcemanager.googleapis.com/v2/folders:search"If the response contains a nextPageToken, paginate by adding "pageToken": "<token>" to the request body
until all folders are retrieved.
Expand nested folders yourself. The search above returns every active folder in the org, so use it to
build the full descendant set for whatever the user picks: for each chosen folder, collect every folder
whose parent chain leads back to it, then list the projects of all of them:
# replace these with the chosen folder and every folder nested beneath it
set -- 123456789012 987654321098
for folder do
gcloud projects list --filter="parent.id=${folder} AND lifecycleState=ACTIVE AND NOT projectId:sys*" \
--format="value(projectId)"
done | sort -uPut that expanded list into the Terraform's project_ids in addition to keeping the chosen folders in
folder_ids. The reason is in the template: folder-level IAM is inherited by every descendant project, but
API enablement is not, and the google_projects data source matches on immediate parent only. Folders in
folder_ids cover IAM (including projects created later); the expanded project_ids is what actually
enables the required APIs in each existing project. Skipping the expansion produces an integration that
looks configured but silently collects nothing from projects in sub-folders.
Projects (active only, excluding system projects):
gcloud projects list --filter="lifecycleState=ACTIVE AND NOT projectId:sys*" --format="table(projectId, name, parent.id)"If any individual command fails (e.g., the user lacks permission to list organizations or folders), inform the user which command failed and why, but continue with whatever information was successfully retrieved. For example, if they can list projects but not folders, proceed with project-level setup.
Otherwise (gcloud not installed or not authenticated), ask the user to gather the IDs from
the GCP Console:
gcloud (which can expand the subtree automatically) and offer project-scoped setup instead.Present whatever results were gathered in a readable format and let the user choose:
Then ask which project should host the Datadog service account.
Do this before generating or applying anything, and in this order. Local state first, because it decides whether an existing integration is something you can update or something you must not touch:
find . -maxdepth 1 -type f \( -name '*.tf' -o -name 'terraform.tfstate' \) -print
# A project is "present" if it has configuration - .terraform/ may simply not exist yet on a fresh clone
# with a remote backend, and terraform.tfstate does not exist at all when state is remote.
if [ -n "$(find . -maxdepth 1 -type f \( -name '*.tf' -o -name '*.tf.json' \) -print -quit)" ]; then
terraform init -input=false >/dev/null || { echo "terraform init failed - resolve that before concluding anything about existing state"; exit 1; }
out=$(terraform state list 2>&1); rc=$?
if [ "$rc" -ne 0 ]; then
case $out in
*'No state file'*|*'no state'*|*'Backend initialization required'*)
echo "project is initialized but has no state yet - treat as a clean install" ;;
*)
printf '%s\n' "$out"
echo "could not read state (backend or credentials problem) - do NOT treat this as 'nothing exists'"; exit 1 ;;
esac
elif [ -z "$out" ]; then
echo "state is empty - treat as a clean install"
else
printf '%s\n' "$out" | grep -F 'datadog_integration_gcp_sts' || echo "state exists but holds no datadog_integration_gcp_sts resource"
fi
else
echo "no Terraform configuration here yet - clean install"
fiMatch the exact resource address datadog_integration_gcp_sts, not a loose grep -i datadog: unrelated Datadog
resources, or cloud IAM left behind by a partial apply, would otherwise read as a managed integration.
Then ask Datadog what it already has:
for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done
DD_SITE='datadoghq.com'; export DD_SITE # <- replace with the site confirmed in Phase 0.
# Explicit assignment, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive.
: "${DD_API_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"; : "${DD_APP_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"
resp=$(printf 'DD-API-KEY: %s\nDD-APPLICATION-KEY: %s\n' "$DD_API_KEY" "$DD_APP_KEY" \
| curl -sS -w '\n%{http_code}' -X GET -H @- "https://api.${DD_SITE}/api/v2/integration/gcp/accounts")
code=$(printf '%s' "$resp" | tail -1); body=$(printf '%s' "$resp" | sed '$d')
[ "$code" = "200" ] || { echo "lookup failed with HTTP $code - do not assume 'not connected':"; printf '%s\n' "$body"; exit 1; }
printf '%s\n' "$body"Now reconcile the two answers before doing anything:
Not in Datadog, nothing in local state - a clean install. Continue.
In Datadog and present in local state - this is the update/repair case, not a duplicate. Continue into the Terraform below as a change to the existing resources, and let the plan show what it will alter.
In Datadog but absent from local state (a service account for the host project from Phase 2 is already registered) - stop. Applying would either create a
duplicate or fight with whatever manages it. Say so plainly and offer the options: import the existing
object into this project (terraform import datadog_integration_gcp_sts.datadog_integration "<integration-uuid>" - the uuid
comes from GET /api/v2/integration/gcp/accounts, not the project id or service-account email), manage it where it is already managed, or delete it in Datadog first.
Only continue if the user picks one and confirms.
Two things about importing, in this order. Generate the configuration first (the Terraform below,
adapted to the identity that already exists - same role/app/service-account name), because terraform import binds an existing object to a configured resource address and fails without one. And importing
the Datadog registration alone is not enough: the cloud-side identity (the IAM role, the app registration,
the service account) is still outside state, so either import those too or reference them with data
sources, or the next apply will try to create them again and collide.
# the uuid comes from the accounts listing, not the project id or service-account email
terraform import datadog_integration_gcp_sts.datadog_integration "<integration-uuid>"Present in local state but absent from Datadog - a partial or rolled-back install. The cloud-side resources may exist while the registration does not. Do not start from scratch: run the plan and let it show what is missing, and expect it to re-create only the registration.
Before generating a new Terraform configuration, check if the user already has a Terraform project in the current directory or nearby:
find . -maxdepth 1 -type f \( -name '*.tf' -o -name 'terraform.tfstate' \) -printIf existing .tf files are found:
datadog provider already exists, reuse its configuration - do not create a duplicate.google provider already exists, reuse it.datadog_integration_gcp_sts)
to the existing project. Do not regenerate providers, variables, or terraform blocks that
already exist.datadog-gcp-integration.tf for the
Datadog resources.If no existing Terraform is found, generate a standalone configuration.
The full HCL template - providers, API enablement, the service account, the token-creator binding for
Datadog's delegate, the project and folder role bindings, and the datadog_integration_gcp_sts
registration - is in references/terraform.md, along with the projects-only and folders-only
variants. Read it now and emit it with the placeholders filled in.
Replace all <PLACEHOLDER> values in the template with the actual values gathered:
<USER_FOLDER_IDS> and <USER_PROJECT_IDS> from Phase 2<HOST_PROJECT_ID> from Phase 2<DATADOG_PRINCIPAL_ID> from Phase 1<DD_SITE> from Phase 0If the user selected only projects (no folders), remove the folder_ids local, the google_projects.folder_projects data source, the google_folder_iam_member resource, and simplify all_project_ids to just local.project_ids.
If the user selected only folders (no explicit projects), still put the expanded descendant project
list from Phase 2 into project_ids - do not set it to []. folder_ids grants IAM (and covers
projects created later), but API enablement is per-project, so an empty project_ids leaves every
existing project in those folders without the required APIs.
Run terraform init to install providers.
Plan, and save the plan to a file. The Datadog provider reads DD_API_KEY / DD_APP_KEY
straight from the environment, so there are no root variables and no -var= arguments - nothing secret
ends up in the plan file, in state, or on a command line:
for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done
DD_SITE='datadoghq.com'; export DD_SITE # <- replace with the site confirmed in Phase 0.
# Explicit assignment, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive.
: "${DD_API_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"; : "${DD_APP_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"
umask 077 # tighten permissions on the plan file anyway
terraform plan -out=tfplanShow the plan output to the user and wait for explicit confirmation.
Apply that saved plan, only after the user confirms it. Applying the file is what makes the
approval meaningful: terraform apply with no plan file computes a brand-new plan, and -auto-approve
would execute it without anyone seeing it, so anything changed since the plan would go in unreviewed:
for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done
DD_SITE='datadoghq.com'; export DD_SITE # <- replace with the site confirmed in Phase 0.
# Explicit assignment, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive.
: "${DD_API_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"; : "${DD_APP_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"
trap 'rm -f tfplan' EXIT HUP INT TERM # the plan file goes away even if this is interrupted
if terraform apply tfplan; then
echo "apply complete"
else
echo "terraform apply FAILED - do NOT verify or report success"
exit 1
fiThe trap removes the plan file on every exit path, including Ctrl-C while the user is deciding. The
if/else around the apply matters because a cleanup command as the block's last line would make a
failed apply exit 0, and the agent would go on to "verify" a deployment that never happened. (It is an
if rather than status=$? on purpose: status is a read-only variable in zsh.)
The plan file holds no key material at all, because the keys never become Terraform values - the provider
reads them from the environment. That is what makes -out safe here, on any Terraform version, and it is
why an existing project needs no variable changes either.
After terraform apply succeeds, verify the integration registered with Datadog:
for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done
DD_SITE='datadoghq.com'; export DD_SITE # <- replace with the site confirmed in Phase 0.
# Explicit assignment, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive.
: "${DD_API_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"; : "${DD_APP_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"
resp=$(printf 'DD-API-KEY: %s\nDD-APPLICATION-KEY: %s\n' "$DD_API_KEY" "$DD_APP_KEY" \
| curl -sS -w '\n%{http_code}' -X GET -H @- "https://api.${DD_SITE}/api/v2/integration/gcp/accounts")
code=$(printf '%s' "$resp" | tail -1); body=$(printf '%s' "$resp" | sed '$d')
[ "$code" = "200" ] || { echo "lookup failed with HTTP $code - do not assume 'not connected':"; printf '%s\n' "$body"; exit 1; }
printf '%s\n' "$body"Confirm the response includes the service account email just provisioned. If it's missing, surface the response to the user so they can debug.
Once terraform apply completes successfully, congratulate the user and let them know metrics typically
arrive within 5-10 minutes. Then check for early metrics and show a widget.
Give it a few seconds, then make a single query to the metrics API - substitute a project you actually put in
monitoring scope for <MONITORED_PROJECT_ID> - not necessarily the host project, which the template
allows to be out of scope:
for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done
DD_SITE='datadoghq.com'; export DD_SITE # <- replace with the site confirmed in Phase 0.
# Explicit assignment, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive.
: "${DD_API_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"; : "${DD_APP_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"
sleep 10
resp=$(printf 'DD-API-KEY: %s\nDD-APPLICATION-KEY: %s\n' "$DD_API_KEY" "$DD_APP_KEY" \
| curl -sS -w '\n%{http_code}' -G -H @- "https://api.${DD_SITE}/api/v1/query" \
--data-urlencode "from=$(($(date +%s) - 900))" \
--data-urlencode "to=$(date +%s)" \
--data-urlencode "query=avg:gcp.gce.instance.cpu.utilization{project_id:<MONITORED_PROJECT_ID>} by {instance_name}")
code=$(printf '%s' "$resp" | tail -1); body=$(printf '%s' "$resp" | sed '$d')
[ "$code" = "200" ] || { echo "metric query failed with HTTP $code - that is NOT 'metrics still propagating':"; printf '%s\n' "$body"; exit 1; }
printf '%s\n' "$body"If the series array is non-empty, render an ASCII chart from the real data:
pointlist values to plot the line, scaling Y-axis to actual min/max.╭, ╰, ─, │, ┤) for the line.scope at the bottom.If the series array is empty, show this static preview instead and let the user know
metrics are still propagating:
┌─────────────────────────────────────────────────────────┐
│ gcp.gce.instance.cpu.utilization ▂▃▅▆▇▆▅▃▂▁▂▃▅ │
│ 100% ┤ ╭──╮ │
│ 75% ┤ ╭───╮ ╭──╯ │ │
│ 50% ┤ ╭────╯ ╰──╮ ╭────╯ │ │
│ 25% ┤ ╭────────╯ ╰────╯ │ │
│ 0% ┤────╯ │ │
│ └────────────────────────────────────────────┘ │
│ │
│ Metrics are on their way - check back in a few minutes │
└─────────────────────────────────────────────────────────┘
Metrics Explorer: <APP_BASE>/metric/explorer?exp_metric=gcp.gce.instance.cpu.utilizationConfirm to the user that their integration is configured and data will appear shortly. All links use DD_SITE - construct them as <APP_BASE>/....
<APP_BASE>/integrations/google-cloud-platform - access the pre-built dashboard and verify the integration is active.<APP_BASE>/metric/explorer?exp_metric=gcp.gce.instance.cpu.utilization - confirm data is flowing.Recommended Monitors - suggest creating monitors for common GCP health signals at <APP_BASE>/monitors/create:
If resource collection was enabled:
<APP_BASE>/infrastructure/catalog - browse Compute instances, Cloud SQL databases, GKE clusters, and more.<APP_BASE>/infrastructure/map - visualize GCP infrastructure.Explore more Datadog products:
<APP_BASE>/logs - stream GCP logs for centralized search and alerting. Setup: https://docs.datadoghq.com/integrations/google_cloud_platform/#log-collection<APP_BASE>/apm/getting-started - distributed tracing for applications on Cloud Run, GKE, or Compute Engine.<APP_BASE>/notebook - shareable investigations combining metrics, logs, and events.roles/resourcemanager.projectIamAdmin /
roles/resourcemanager.folderIamAdmin, roles/iam.serviceAccountAdmin on the host project, and
roles/serviceusage.serviceUsageAdmin on every project where the config enables an API (the host
project included - it gets iam.googleapis.com and iamcredentials.googleapis.com).
roles/serviceusage.serviceUsageConsumer, which the service account itself receives, is not enough to
enable an API.datadog provider reads
DD_API_KEY and DD_APP_KEY from the environment, so there are no root variables, no -var= arguments,
and nothing for Terraform to record in state or a saved plan. Don't declare key variables, and don't
write the keys into a committed .tfvars file or any other persistent file.terraform apply without showing the plan to the user first.© datadog-labs, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 1 other file (references) in dd-gcp-integration of datadog-labs/agent-skills.
Open the folder on GitHubat commit d2411cc
Dd GCP Integration next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Dd GCP Integration this skilldatadog-labs/agent-skills | 177 | — | ~8k | Automated safety check: Notes | MIT | |
| DeployingGoogleCloudPlatform/race-condition | 234 | — | ~3k | Automated safety check: Pass | Custom licence | |
| Gke Alert Configurationgoogle/skills | 21k | — | ~5.3k | Automated safety check: Pass | Apache-2.0 | |
| Google Cloud Solution N Tier Serverless Web Appgoogle/skills | 21k | — | ~5.5k | Automated safety check: Pass | Apache-2.0 | |
| Cloud Run Alert Configurationgoogle/skills | 21k | — | ~1.7k | Automated safety check: Pass | Apache-2.0 | |
| Google Cloud Storage Basicsgoogle/skills | 21k | — | ~2.8k | Automated safety check: Pass | Apache-2.0 |
GoogleCloudPlatform/race-condition
Guides deployment of Race Condition to a GCP project. An agent skill from GoogleCloudPlatform/race-condition.
google/skills
Configures alerting policies in Terraform for Google Kubernetes Engine (GKE) clusters, workloads, and services using PromQL and Google Cloud Managed Service for Prometheus.
google/skills
Assists in designing and implementing secure n-tier serverless web applications and microservices on Google Cloud.
google/skills
Configures best-practice, high-signal alerting policies for Cloud Run resources on Google Cloud (services, jobs, and worker pools) based on seasoned SRE practices.
google/skills
Stores, retrieves, and manages data as objects in Cloud Storage on Google Cloud (also known colloquially as GCS) buckets.
alirezarezvani/claude-skills
Design GCP architectures for startups and enterprises. An agent skill from alirezarezvani/claude-skills.
datadog-labs/agent-skills
Bootstrap a reproducible LLM Observability experiment through the Python ddtrace SDK or the Node dd-trace SDK.
datadog-labs/agent-skills
Ensure the user has an authenticated Datadog account with a valid DDAPIKEY on the right region before any Datadog setup or instrumentation.
datadog-labs/agent-skills
Entry point for Datadog onboarding. An agent skill from datadog-labs/agent-skills.
datadog-labs/agent-skills
APM - install, onboard, instrument, enable, set up, configure, traces, services, dependencies, performance analysis, Data Streams Monitoring (DSM), queue lag, pipeline latency.
datadog-labs/agent-skills
Install the Datadog Agent on Kubernetes using the Datadog Operator — required before enabling Single Step Instrumentation (SSI), which automatically instruments applications for APM without code…
datadog-labs/agent-skills
Set up the Datadog AWS integration with Terraform - creates the cross-account IAM role Datadog assumes (external ID, no stored credentials), attaches the permission policies Datadog publishes, and…
Categories
Set up the Datadog Google Cloud integration with Terraform - creates a service account in the host project, lets Datadog's delegate principal impersonate it via roles/iam.serviceAccountTokenCreator…. Dd GCP Integration is an agent skill from datadog-labs/agent-skills.serviceAccountTokenCreator (no service-account keys), enables the required APIs, grants the monitoring roles across the chosen projects and folders, and registers the account through datadogintegrationgcpsts.
Dd GCP Integration fits situations like: the user wants to monitor GCP resources such as Compute Engine; wants to connect a GCP project; organization to Datadog; repair the GCP integration.
Run `npx skills add datadog-labs/agent-skills --skill dd-gcp-integration -a claude-code`. Or copy the skill folder (dd-gcp-integration in datadog-labs/agent-skills) into .claude/skills/dd-gcp-integration in your project. Claude Code loads it when a task matches its description.
Run `npx skills add datadog-labs/agent-skills --skill dd-gcp-integration -a codex`. Or copy the skill folder (dd-gcp-integration in datadog-labs/agent-skills) into .agents/skills/dd-gcp-integration in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add datadog-labs/agent-skills --skill dd-gcp-integration -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/dd-gcp-integration, .gemini/skills/dd-gcp-integration, .github/skills/dd-gcp-integration and .opencode/skills/dd-gcp-integration in your project.
Going by SKILL.md and its folder, Dd GCP Integration needs the command-line tools its instructions call (terraform, curl, gcloud, jq and tofu) and credentials named DD_API_KEY, DD_APP_KEY and GOOGLE_APPLICATION_CREDENTIALS. Our summary lists: A credential in DD_API_KEY; A credential in DD_APP_KEY.
SKILL.md names 8 domains. In commands or code: us3.datadoghq.com, us5.datadoghq.com, ap1.datadoghq.com, ap2.datadoghq.com, uk1.datadoghq.com, developer.hashicorp.com and cloudresourcemanager.googleapis.com; the agent is likely to contact these when it follows the instructions. As links in the text: docs.datadoghq.com. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found notes only (mentions a .env file), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.
Dd GCP Integration is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 8k tokens (SKILL.md is roughly 32k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 2.3k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Dd GCP Integration: Deploying (GoogleCloudPlatform/race-condition, 234 stars), Gke Alert Configuration (google/skills, 21k stars), Google Cloud Solution N Tier Serverless Web App (google/skills, 21k stars) and Cloud Run Alert Configuration (google/skills, 21k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
datadog-labs (a GitHub organization) maintains it in datadog-labs/agent-skills, which has 177 GitHub stars. The repository holds 39 skills in this directory. The repository was last updated on October 8, 2026.
Source: datadog-labs/agent-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.