Skip to content

feat: add Terraform solution (CX-43) - #240

Merged
jeff-schnitter merged 40 commits into
mainfrom
cx-43-terraform-solution
Sep 4, 2026
Merged

feat: add Terraform solution (CX-43)#240
jeff-schnitter merged 40 commits into
mainfrom
cx-43-terraform-solution

Conversation

@jeff-schnitter

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a new terraform solution that demonstrates cataloging infrastructure using the Cortex Terraform provider
  • Provisions a full demo environment: 2 domains, 7 services, 4 teams, and a Production Readiness scorecard with Bronze/Silver/Gold ladder
  • Includes a delta variant showing how to evolve Terraform configs (e.g. adding a new team member via PR)
  • Post-install interactive menu with ASCII data model diagram, clickable entity links, and import report

Key fixes made during development

  • type = "team" required on team entities for ownership.teams() CQL to work (without it, teams aren't recognized as owners)
  • Member roles must be letters/digits/hyphens only ("Team-Lead" not "Team Lead")
  • Link type must be "documentation" not "source"
  • All entity tags prefixed with terraform-demo- to avoid collisions with customer entities
  • Scorecard scoped to terraform-demo group only

Test plan

  • cortex solutions install -s terraform — runs setup, creates all 13 entities + scorecard
  • Post-install menu shows data model diagram with clickable entity links
  • terraform plan shows no drift after install
  • terraform apply with delta directory applies Sarah Connor addition cleanly
  • Scorecard Bronze rules pass for services with owners and git configured

🤖 Generated with Claude Code

jeff-schnitter and others added 30 commits September 2, 2026 14:01
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… (CX-43)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bronze: owners.size() > 0 → ownership.teams().length > 0
- Gold: links.size() >= 2 → ownership.teams().length >= 2 (shared ownership restored)
- Delta phoenix: restore Architecture Docs link (Gold is now ownership-gated, not link-gated)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removing the already_done guard from _copy_files — file copying is
idempotent and guarding it meant re-running post-install never propagated
updated templates to the working directory.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ion (CX-43)

- type=\"team\" is not a valid Cortex API type value — teams are identified
  by the presence of the team block, not the type field. Sending type=team
  in the create POST causes 400 from the API.
- customData.exists() is not valid syntax; correct form is custom("key") != null

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cortex expression language does not support lambda-style exists() on links.
Replace with a size comparison which is confirmed valid syntax.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Correct Cortex expression syntax for filtering links by type is a
function call links("type"), not a lambda predicate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cortex expression language uses links().length (function call) not
links.size() (method call). Consistent with links("type").length pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
links() without args is invalid; links.length (raw list) may be the
correct form for checking any link exists.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
links() requires a type argument. Silver uses links("runbook").length > 0,
Gold uses links("documentation").length > 0 — both types added to phoenix
in the delta, keeping Bronze->Silver promotion intact.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Cortex API returns levels sorted Bronze->Silver->Gold (rank 1,2,3).
Provider compares by position, so defining them descending caused a
"inconsistent result after apply" error. Reorder to match API response.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-43)

Tag: production-readiness -> terraform-demo-production-readiness
Name: Production Readiness -> Terraform Demo Production Readiness

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…services (CX-43)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-43)

The solutions framework reads ## After Installing to populate the
"Next steps" option in the post-install What next? menu.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-43)

Replaces HCL snippet as the first code block so _show_diagram() renders
the flow from Terraform repo -> terraform apply -> Cortex entities.
Entity tags appear verbatim for terminal hyperlink support.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Terraform solution creates entities via terraform apply, not YAML import,
so the import report is always empty. Only show the option when there's
something to report.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Ls (CX-43)

_apply_file_hyperlinks() wraps recognized template filenames in OSC 8
terminal links pointing to the file's location in the cortexapps/cli
GitHub repo. Threaded through _post_install_menu -> _show_diagram via
solution_tag parameter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…on (CX-43)

- _extract_tf_entity_tags() scans _templates/*.tf for tag = "..." values
  so entity tags are linked even when no catalog/ YAML files exist
- Import report hidden when total imported = 0 and total failed = 0
  (was showing for "TOTAL: 0 imported, 0 failed" despite empty result)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jeff-schnitter and others added 10 commits September 2, 2026 16:29
…L in github-actions-deploy template; update cortex-app-urls skill
- Add _fetch_scorecard_id_map() to look up scorecard numeric IDs via
  GET /api/v1/scorecards/{tag}; falls back to tag if lookup fails
- Thread scorecard_id_map (tag→id) through _show_diagram and
  _post_install_menu, replacing scorecard_tags: set[str]
- _apply_scorecard_hyperlinks now builds /admin/scorecards/<id> URLs
- README scorecard links changed to /admin/scorecards list page
  (static files cannot know the numeric ID)
- Update cortex-app-urls skill: entity URL confirmed as
  /admin/resources?tag=<tag> for all types; scorecard requires numeric ID

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Avoids tag conflicts with existing demo entities (e.g. geography demo
has a "phoenix" location entity). All service, domain, and team tags
now follow the terraform-demo-* convention, consistent with the
existing scorecard tag.

Updated: all .tf templates (initial + delta), README diagram and
HCL example, setup.py delta walkthrough output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Cortex scorecard API does not return a numeric id field; the URL
format uses the tag directly. Remove the API call in _fetch_scorecard_id_map
to avoid a printed 404 error that leaked through the exception handler.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cortex scorecard page URLs require a numeric ID that is not available
from the public API. Remove the scorecard hyperlink entirely; the
scorecard tag now appears as plain text in the diagram.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-43)

- Add type = "team" to all four team entities in teams.tf and terraform-delta/teams.tf
- Change link type from "source" to "documentation" across all .tf files
- Hyphenate member role values (e.g. "Team Lead" -> "Team-Lead") to satisfy
  Cortex API validation (roles must be letters, digits, and hyphens only)
- Fix scorecard CQL ownership expressions to chain null-safe access:
  ownership?.teams()?.length > 0 (previously .length on null result crashed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ownership.teams().length > 0 works correctly once team entities have
type = "team" set. The null access error was caused by teams not being
recognized as teams, not by a syntax issue with the CQL expression.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@jeff-schnitter
jeff-schnitter merged commit 3bb384d into main Sep 4, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant