Module 2: The Bedrock Cost Model

5. Hands-on: the honest attempt of `infracost scan` on `bedrock.tf`

Description

ADR-001-llm-as-escalation-path.md closed Module 1 with an exact sentence: "neither bedrock.tf exists, nor does extract-shipment-manifest-fields have a single line of code". This lesson writes this entire guide's first line of bedrock.tf — deliberately minimal, a single resource, written solely to be able to ask Infracost the question this whole module exists to answer: can Infracost put a price on a Bedrock resource? The attempt ran for real, in this very environment, while writing this lesson — and it stops at a point worth documenting precisely, no less honest than cloud-security-and-guardrails-guide M2.6's or this same guide's Module 1, lesson 7.

Connection to the module

Lesson 4 confirmed Infracost remains available, no reinstallation. This lesson points it, for the first time, at a real aws_bedrock_guardrail resource. Lesson 6, immediately after, explains why this lesson's result is what it is — not a flaw in this guide, but a structural consequence of how Infracost works, applied to a kind of resource it never had to face before.


Step 1 — This guide's first bedrock.tf, deliberately minimal

This is not Andes Cargo's complete AI workload infrastructure — that's Module 3's full job, with the modules/bedrock-guardrail/ module, Module 4's six guardrail policies, and the least-privilege IAM role. It's, on purpose, the smallest possible version of a real Bedrock resource: a single aws_bedrock_guardrail, enough to give Infracost something concrete to try reading, and nothing more.

At the root of andes-cargo-infra/, add bedrock.tf:

# bedrock.tf -- Module 2 draft. A single aws_bedrock_guardrail resource, written
# only to run `infracost scan` against a real Bedrock resource (Module 2, lesson 5).
# This is NOT the complete infrastructure -- Module 3 builds modules/bedrock-guardrail/
# and BedrockManifestExtractorRole in full. ADR-001 (Module 1) confirmed that, at the
# close of Module 1, no bedrock.tf existed yet; this is its first draft line.

resource "aws_bedrock_guardrail" "manifest_extractor" {
  name                      = "andes-cargo-manifest-extractor-guardrail"
  blocked_input_messaging   = "This input is not allowed due to content policy violations."
  blocked_outputs_messaging = "This output is not allowed due to content policy violations."

  content_policy_config {
    filters_config {
      type            = "PROMPT_ATTACK"
      input_strength  = "HIGH"
      output_strength = "NONE"
    }
  }

  tags = local.common_tags
}

Notice the header comment: it's the same traceability discipline iam.tf, s3.tf, and dynamodb.tf in andes-cargo-infra/ already established — every .tf file declares, in its first line, where it comes from and what it isn't yet.


Step 2 — terraform init / validate / plan: the part that does run to completion

Exactly as this guide's Module 1 confirmed (and, before that, this entire guide's design, verified by direct execution): declaring and planning a new Bedrock resource needs no LocalStack running, no AWS account, no token — because Terraform never needs to read from state a resource that doesn't exist there yet.

terraform init -input=false
terraform validate
terraform plan -out=tfplan -input=false

What to expect (literal — run for real in this environment, Terraform v1.15.8, provider hashicorp/aws v6.60.0):

Initializing the backend...
Initializing modules...
Initializing provider plugins...
- Reusing previous version of hashicorp/archive from the dependency lock file
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v6.60.0
- Using previously-installed hashicorp/archive v2.8.0

Terraform has been successfully initialized!
Success! The configuration is valid.
  # aws_bedrock_guardrail.manifest_extractor will be created
  + resource "aws_bedrock_guardrail" "manifest_extractor" {
      + blocked_input_messaging   = "This input is not allowed due to content policy violations."
      + blocked_outputs_messaging = "This output is not allowed due to content policy violations."
      + created_at                = (known after apply)
      + description               = (known after apply)
      + guardrail_arn             = (known after apply)
      + guardrail_id              = (known after apply)
      + name                      = "andes-cargo-manifest-extractor-guardrail"
      + region                    = "us-east-1"
      + status                    = (known after apply)
      + tags                      = {
          + "Environment" = "dev"
          + "ManagedBy"   = "terraform"
          + "Project"     = "andes-cargo"
        }
      ...
    }

Plan: 15 to add, 0 to change, 0 to destroy.

Saved the plan to: tfplan

Notice the provider version number: v6.60.0, not v5.100.0. The exact version installed in an environment depends on when terraform init ran against the ~> 6.0 constraint already fixed in andes-cargo-infra/'s versions.tf since terraform-and-iac-guide — the exact number changes over time; what matters, and gets confirmed here again, is that aws_bedrock_guardrail exists in the provider's schema and plans with no errors, regardless of the exact patch installed.


Step 3 — infracost scan, the real attempt

With tfplan generated, the next step — the one this entire module exists to test — is pointing Infracost at the full project:

infracost scan andes-cargo-infra/

Remember, from lesson 4: [path] is a positional argument, not a flag. This command ran for real, in this environment, while writing this lesson.

What to expect (literal — run against the real v2.16.1 binary, no session logged in, in this environment):

Port 26372 is already in use, falling back to device authentication.
Please go to the following URL to log in:
https://login.infracost.io/activate
And enter the code:
QSRS-QGBJ

The command never got to read bedrock.tf. Before analyzing a single resource, scan tries to confirm a logged-in session — the exact same finding finops-and-cost-guardrails-guide M2.2 already documented ("Please go to the following URL to log in," followed by a PKCE URL) —, but with a real variation specific to this environment that's worth pointing out precisely: port 26372, the same one that sibling guide's PKCE flow used for the local server receiving the browser callback, was already in use in this environment at the moment the command ran. Infracost detected that collision and, instead of failing, automatically fell back to the second mechanism lesson 4 had already anticipated as possible: device flow — a one-time code (QSRS-QGBJ in this specific run; yours will be different) entered manually at login.infracost.io/activate from any browser, on any device, with no need for that browser to be on the same machine that ran the command.


The honest answer to this lesson's question

This guide's design laid out three equally honest possible outcomes for this attempt: the Bedrock resource showing up priced, showing up listed as skipped (an "unsupported" or "no price" style output), or neither. The real answer, confirmed by this run, is the third — but with a precision worth stating in full: it's not that Infracost evaluated aws_bedrock_guardrail and decided it couldn't price it. It's that it never got to evaluate any resource in the plan at all. The authentication gate runs before any analysis of the HCL or the tfplan.json — exactly as finops-and-cost-guardrails-guide M2.2 already warned: "scan's first step is confirming the session, and if it can't find one, [...] it waits there, indefinitely."

   THE ATTEMPT AGAINST BEDROCK.TF, STEP BY STEP

   terraform init / validate / plan
              │
              ▼
        Success! -- with no need for LocalStack, AWS account, or token
        (confirmed in this lesson's Step 2)
              │
              ▼
   infracost scan andes-cargo-infra/
              │
              ▼
   Infracost session logged in?
              │
      NO ─────┴───── (the YES branch was never actually tested in this environment)
      │
      ▼
   Port 26372 busy -> falls back to device flow
   "Please go to the following URL to log in..."
   -- waits there, indefinitely --
              │
              ▼
   bedrock.tf was NEVER read. Not priced, not --include-warnings,
   not "not supported" -- the authentication gate blocks BEFORE
   an answer to that question can even exist.

An honest correction about a flag that doesn't exist. Before writing this lesson, running infracost scan andes-cargo-infra/ --show-skipped was considered, to see whether that flag would reveal unsupported resources without needing a session. That flag doesn't exist in scan's real --help for this version — confirmed in lesson 4: scan's only flags are --currency, --include-warnings, -o/--option, and --ssh-key-file. --show-skipped belongs to documentation or tutorials for an earlier CLI version, probably the already-deprecated breakdown command. The closest current flag to that function is --include-warnings ("Also show warning-severity diagnostics in the summary") — but, since the authentication gate blocks before any analysis, not even that flag could be tested in this environment.


Common mistakes

Concluding, from this lesson, that Infracost "doesn't support Bedrock" (over-generalization-without-evidence mistake). What happens: someone reads that bedrock.tf was never analyzed and concludes Infracost lacks support for Bedrock resources. How to spot it: if your summary of this lesson is "Infracost doesn't have Bedrock prices." How to fix it: this lesson can't prove that claim — the authentication gate prevented the analysis from even starting. Lesson 6 investigates that specific question through a different route (Infracost's public documentation of supported resources), separate from this execution attempt.

Thinking a second attempt, with a session logged in, would necessarily resolve the Bedrock pricing question (forward-looking expectation mistake). What happens: someone assumes that, with an authenticated Infracost account, scan would automatically show a complete price for aws_bedrock_guardrail. How to spot it: if your plan is "I just need to log in and the number is going to appear." How to fix it: even with a session logged in, a resource can show up unpriced for two completely different reasons — either Infracost doesn't have that resource type in its catalog (lesson 6's question), or it does have it but it's usage-based with no declared volume assumption (the same gap finops-and-cost-guardrails-guide M2.5 already found with DynamoDB and Lambda). Logging in resolves this lesson's first obstacle, it doesn't guarantee a priced answer.

Copying this lesson's one-time code (QSRS-QGBJ) expecting it to work (literal reading of an ephemeral value mistake). What happens: someone, following this lesson to the letter, tries entering the exact code shown above at login.infracost.io/activate. How to spot it: if you copied the code from this document instead of generating your own. How to fix it: that code is one-time-use, generated for this specific run, and has already expired. Every infracost scan run without a session generates its own URL and its own code — the part that repeats, always, is the mechanism (device flow, with the PKCE port in use), not the exact value.


Exercises

Exercise 1 — Explain why this lesson's terraform plan finished completely, but infracost scan didn't. A colleague, watching both commands run against the same bedrock.tf, asks why one finished its job and the other sat waiting for a login. Explain the difference to them.

See solution

terraform plan on a resource that doesn't exist yet in the state never needs to read any external API — it builds the complete diff from the locally declared HCL, exactly as this guide's Module 1 confirmed. infracost scan, on the other hand, needs two things before it can show a number: confirming a session against the Infracost platform (an external service, with its own account system) and, only after, querying its pricing catalog. The first step — the session — is a barrier completely independent of the HCL being analyzed; it blocks before the second step, the one that actually involves bedrock.tf's content, can even begin.

Exercise 2 — Predict what would happen if you ran this exact command on your own machine, with port 26372 available (not busy). Based on what finops-and-cost-guardrails-guide M2.2 already documented about the PKCE flow, what would you see instead of this lesson's device flow message?

See solution

You'd see a long PKCE authorization URL — something like https://login.infracost.io/authorize?audience=...&client_id=...&code_challenge=...&redirect_uri=http%3A%2F%2Flocalhost%3A26372%2Fcallback... —, instead of the short device flow message with an activation code. The command would open (or wait for you to manually open) that URL in a browser, and infracost scan's own process would spin up a temporary local server on port 26372 to receive the browser's response once you complete the login — the same mechanism this guide's lesson 4 anticipated as the "normal path," reserving device flow for when that specific port isn't available.

Exercise 3 — Explain why this lesson explicitly corrects the --show-skipped flag instead of simply omitting it. Why is it worth it, for this guide, to point out a flag that doesn't exist, instead of only talking about the ones that do?

See solution

Because this guide's original design considered that flag as a possible route to reveal unsupported resources, and verifying against the real --help (lesson 4) revealed it doesn't exist in the current CLI version — it's exactly the same kind of correction finops-and-cost-guardrails-guide M2.2 already made with breakdown --path and infracost diff. Staying silent about that finding would have let an assumption pass uncorrected — one an outdated tutorial, or this very guide if it hadn't been verified, could have propagated as if it were true. Naming the correction, with the exact source (the real --help, run in lesson 4), is the same honesty discipline that carries this entire guide: verify before citing, and correct out loud when a reasonable assumption turns out to be wrong.


Summary and next step

This lesson wrote this guide's first real bedrock.tf — minimal, a single resource, deliberately incomplete — and confirmed, with terraform init/validate/plan run for real, that it declares and plans with no errors, no LocalStack, no AWS account, no token. It then pointed infracost scan at that same project, and documented the real result: the command never got to read bedrock.tf — Infracost's authentication gate stopped it first, falling back to device flow because of a port collision specific to this environment. The question of whether Infracost can price a Bedrock resource remains, through this route, unanswered in this lab — not as a failure, but as the honest result of a real attempt.

Before moving on you should be able to: explain why terraform plan finished and infracost scan didn't, on the same file; describe, from memory, the device flow mechanism versus PKCE; and explain why the authentication gate means this lesson can't, on its own, confirm whether Infracost supports Bedrock resources or not.

Lesson 6 answers that pending question through a different route — not running scan again, but reading directly from Infracost's public documentation of which resources it supports — and explains, with that evidence, why even a "yes" answer to that question wouldn't fully solve the problem of costing extract-shipment-manifest-fields.

Resources

  1. Infracost — CLI commands — the official reference for scan, confirmed again against this lesson's real behavior.
  2. finops-and-cost-guardrails-guide, Module 2, lesson 2 — the original source for the PKCE authentication finding, here confirmed with a real variant (device flow) not documented in that sibling guide.
  3. Infracost — Authentication — official documentation of both authentication mechanisms, PKCE and device flow.
  4. This guide's Module 1, lesson 8 (ADR-001-llm-as-escalation-path.md) — the source of the "neither does bedrock.tf exist" claim this lesson resolves for the first time.