<memvid-mind-context>
# 🧠 Claude Mind Active

📁 Project: **vector**
💾 Memory: `.claude/mind.mv2` (51272 KB)

**Commands:**
- `/mind:search <query>` - Search memories
- `/mind:ask <question>` - Ask your memory
- `/mind:recent` - View timeline
- `/mind:stats` - View statistics

_Memories are captured automatically from your tool use._
</memvid-mind-context>


<EXTREMELY_IMPORTANT>
You have superpowers.

**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'Skill' tool:**

---
name: using-superpowers
description: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
---

<SUBAGENT-STOP>
If you were dispatched as a subagent to execute a specific task, skip this skill.
</SUBAGENT-STOP>

<EXTREMELY-IMPORTANT>
If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.

IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.

This is not negotiable. This is not optional. You cannot rationalize your way out of this.
</EXTREMELY-IMPORTANT>

## Instruction Priority

Superpowers skills override default system prompt behavior, but **user instructions always take precedence**:

1. **User's explicit instructions** (CLAUDE.md, GEMINI.md, AGENTS.md, direct requests) — highest priority
2. **Superpowers skills** — override default system behavior where they conflict
3. **Default system prompt** — lowest priority

If CLAUDE.md, GEMINI.md, or AGENTS.md says "don't use TDD" and a skill says "always use TDD," follow the user's instructions. The user is in control.

## How to Access Skills

**In Claude Code:** Use the `Skill` tool. When you invoke a skill, its content is loaded and presented to you—follow it directly. Never use the Read tool on skill files.

**In Copilot CLI:** Use the `skill` tool. Skills are auto-discovered from installed plugins. The `skill` tool works the same as Claude Code's `Skill` tool.

**In Gemini CLI:** Skills activate via the `activate_skill` tool. Gemini loads skill metadata at session start and activates the full content on demand.

**In other environments:** Check your platform's documentation for how skills are loaded.

## Platform Adaptation

Skills use Claude Code tool names. Non-CC platforms: see `references/copilot-tools.md` (Copilot CLI), `references/codex-tools.md` (Codex) for tool equivalents. Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.

# Using Skills

## The Rule

**Invoke relevant or requested skills BEFORE any response or action.** Even a 1% chance a skill might apply means that you should invoke the skill to check. If an invoked skill turns out to be wrong for the situation, you don't need to use it.

```dot
digraph skill_flow {
    "User message received" [shape=doublecircle];
    "About to EnterPlanMode?" [shape=doublecircle];
    "Already brainstormed?" [shape=diamond];
    "Invoke brainstorming skill" [shape=box];
    "Might any skill apply?" [shape=diamond];
    "Invoke Skill tool" [shape=box];
    "Announce: 'Using [skill] to [purpose]'" [shape=box];
    "Has checklist?" [shape=diamond];
    "Create TodoWrite todo per item" [shape=box];
    "Follow skill exactly" [shape=box];
    "Respond (including clarifications)" [shape=doublecircle];

    "About to EnterPlanMode?" -> "Already brainstormed?";
    "Already brainstormed?" -> "Invoke brainstorming skill" [label="no"];
    "Already brainstormed?" -> "Might any skill apply?" [label="yes"];
    "Invoke brainstorming skill" -> "Might any skill apply?";

    "User message received" -> "Might any skill apply?";
    "Might any skill apply?" -> "Invoke Skill tool" [label="yes, even 1%"];
    "Might any skill apply?" -> "Respond (including clarifications)" [label="definitely not"];
    "Invoke Skill tool" -> "Announce: 'Using [skill] to [purpose]'";
    "Announce: 'Using [skill] to [purpose]'" -> "Has checklist?";
    "Has checklist?" -> "Create TodoWrite todo per item" [label="yes"];
    "Has checklist?" -> "Follow skill exactly" [label="no"];
    "Create TodoWrite todo per item" -> "Follow skill exactly";
}
```

## Red Flags

These thoughts mean STOP—you're rationalizing:

| Thought | Reality |
|---------|---------|
| "This is just a simple question" | Questions are tasks. Check for skills. |
| "I need more context first" | Skill check comes BEFORE clarifying questions. |
| "Let me explore the codebase first" | Skills tell you HOW to explore. Check first. |
| "I can check git/files quickly" | Files lack conversation context. Check for skills. |
| "Let me gather information first" | Skills tell you HOW to gather information. |
| "This doesn't need a formal skill" | If a skill exists, use it. |
| "I remember this skill" | Skills evolve. Read current version. |
| "This doesn't count as a task" | Action = task. Check for skills. |
| "The skill is overkill" | Simple things become complex. Use it. |
| "I'll just do this one thing first" | Check BEFORE doing anything. |
| "This feels productive" | Undisciplined action wastes time. Skills prevent this. |
| "I know what that means" | Knowing the concept ≠ using the skill. Invoke it. |

## Skill Priority

When multiple skills could apply, use this order:

1. **Process skills first** (brainstorming, debugging) - these determine HOW to approach the task
2. **Implementation skills second** (frontend-design, mcp-builder) - these guide execution

"Let's build X" → brainstorming first, then implementation skills.
"Fix this bug" → debugging first, then domain-specific skills.

## Skill Types

**Rigid** (TDD, debugging): Follow exactly. Don't adapt away discipline.

**Flexible** (patterns): Adapt principles to context.

The skill itself tells you which.

## User Instructions

Instructions say WHAT, not HOW. "Add X" or "Fix Y" doesn't mean skip workflows.

</EXTREMELY_IMPORTANT>


<memvid-mind-context>
# 🧠 Claude Mind Active

📁 Project: **vector**
💾 Memory: `.claude/mind.mv2` (51272 KB)

**Commands:**
- `/mind:search <query>` - Search memories
- `/mind:ask <question>` - Ask your memory
- `/mind:recent` - View timeline
- `/mind:stats` - View statistics

_Memories are captured automatically from your tool use._
</memvid-mind-context>

<EXTREMELY_IMPORTANT>
You have superpowers.

**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'Skill' tool:**

---
name: using-superpowers
description: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
---

<SUBAGENT-STOP>
If you were dispatched as a subagent to execute a specific task, skip this skill.
</SUBAGENT-STOP>

<EXTREMELY-IMPORTANT>
If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.

IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.

This is not negotiable. This is not optional. You cannot rationalize your way out of this.
</EXTREMELY-IMPORTANT>

## Instruction Priority

Superpowers skills override default system prompt behavior, but **user instructions always take precedence**:

1. **User's explicit instructions** (CLAUDE.md, GEMINI.md, AGENTS.md, direct requests) — highest priority
2. **Superpowers skills** — override default system behavior where they conflict
3. **Default system prompt** — lowest priority

If CLAUDE.md, GEMINI.md, or AGENTS.md says "don't use TDD" and a skill says "always use TDD," follow the user's instructions. The user is in control.

## How to Access Skills

**In Claude Code:** Use the `Skill` tool. When you invoke a skill, its content is loaded and presented to you—follow it directly. Never use the Read tool on skill files.

**In Copilot CLI:** Use the `skill` tool. Skills are auto-discovered from installed plugins. The `skill` tool works the same as Claude Code's `Skill` tool.

**In Gemini CLI:** Skills activate via the `activate_skill` tool. Gemini loads skill metadata at session start and activates the full content on demand.

**In other environments:** Check your platform's documentation for how skills are loaded.

## Platform Adaptation

Skills use Claude Code tool names. Non-CC platforms: see `references/copilot-tools.md` (Copilot CLI), `references/codex-tools.md` (Codex) for tool equivalents. Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.

# Using Skills

## The Rule

**Invoke relevant or requested skills BEFORE any response or action.** Even a 1% chance a skill might apply means that you should invoke the skill to check. If an invoked skill turns out to be wrong for the situation, you don't need to use it.

```dot
digraph skill_flow {
    "User message received" [shape=doublecircle];
    "About to EnterPlanMode?" [shape=doublecircle];
    "Already brainstormed?" [shape=diamond];
    "Invoke brainstorming skill" [shape=box];
    "Might any skill apply?" [shape=diamond];
    "Invoke Skill tool" [shape=box];
    "Announce: 'Using [skill] to [purpose]'" [shape=box];
    "Has checklist?" [shape=diamond];
    "Create TodoWrite todo per item" [shape=box];
    "Follow skill exactly" [shape=box];
    "Respond (including clarifications)" [shape=doublecircle];

    "About to EnterPlanMode?" -> "Already brainstormed?";
    "Already brainstormed?" -> "Invoke brainstorming skill" [label="no"];
    "Already brainstormed?" -> "Might any skill apply?" [label="yes"];
    "Invoke brainstorming skill" -> "Might any skill apply?";

    "User message received" -> "Might any skill apply?";
    "Might any skill apply?" -> "Invoke Skill tool" [label="yes, even 1%"];
    "Might any skill apply?" -> "Respond (including clarifications)" [label="definitely not"];
    "Invoke Skill tool" -> "Announce: 'Using [skill] to [purpose]'";
    "Announce: 'Using [skill] to [purpose]'" -> "Has checklist?";
    "Has checklist?" -> "Create TodoWrite todo per item" [label="yes"];
    "Has checklist?" -> "Follow skill exactly" [label="no"];
    "Create TodoWrite todo per item" -> "Follow skill exactly";
}
```

## Red Flags

These thoughts mean STOP—you're rationalizing:

| Thought | Reality |
|---------|---------|
| "This is just a simple question" | Questions are tasks. Check for skills. |
| "I need more context first" | Skill check comes BEFORE clarifying questions. |
| "Let me explore the codebase first" | Skills tell you HOW to explore. Check first. |
| "I can check git/files quickly" | Files lack conversation context. Check for skills. |
| "Let me gather information first" | Skills tell you HOW to gather information. |
| "This doesn't need a formal skill" | If a skill exists, use it. |
| "I remember this skill" | Skills evolve. Read current version. |
| "This doesn't count as a task" | Action = task. Check for skills. |
| "The skill is overkill" | Simple things become complex. Use it. |
| "I'll just do this one thing first" | Check BEFORE doing anything. |
| "This feels productive" | Undisciplined action wastes time. Skills prevent this. |
| "I know what that means" | Knowing the concept ≠ using the skill. Invoke it. |

## Skill Priority

When multiple skills could apply, use this order:

1. **Process skills first** (brainstorming, debugging) - these determine HOW to approach the task
2. **Implementation skills second** (frontend-design, mcp-builder) - these guide execution

"Let's build X" → brainstorming first, then implementation skills.
"Fix this bug" → debugging first, then domain-specific skills.

## Skill Types

**Rigid** (TDD, debugging): Follow exactly. Don't adapt away discipline.

**Flexible** (patterns): Adapt principles to context.

The skill itself tells you which.

## User Instructions

Instructions say WHAT, not HOW. "Add X" or "Fix Y" doesn't mean skip workflows.

</EXTREMELY_IMPORTANT>


bugbot says this Consecutive template strip markers merge YAML entries onto one line

High Severity

When both limits_cpu and limits_memory are non-null, the %{ endif ~} strip marker on the cpu line consumes the newline and indentation preceding the memory line. This causes both key-value pairs to render on a single line (e.g. cpu: "2"memory: 1Gi), producing invalid YAML that will fail Helm parsing. The inner module's default for resources_limits has both values set, so direct callers are immediately affected.


## claude.ai Context7
Use this server to fetch current documentation whenever the user asks about a library, framework, SDK, API, CLI tool, or cloud service -- even well-known ones like React, Next.js, Prisma, Express, Tailwind, Django, or Spring Boot. This includes API syntax, configuration, version migration, library-specific debugging, setup instructions, and CLI tool usage. Use even when you think you know the answer -- your training data may not reflect recent changes. Prefer this over web search for library docs.

Do not use for: refactoring, writing scripts from scratch, debugging business logic, code review, or general programming concepts.


- migrate-flux-app: Use when migrating a Flux CD application from the infrastructure/kubernetes/application-deployments repo to the application-deployment repo. Covers copying app folders, updating kustomizations, committing, pushing, and creating merge requests.
- app-infra-generate: Generate an application-infrastructure Terraform module from a YAML spec produced by app-infra-spec. Reads pattern files and a versions manifest to produce main.tf, variables.tf, outputs.tf, overlay guidance, and a triage list.
- app-infra-spec: Structured questionnaire to gather requirements for an application-infrastructure Terraform module. Produces a YAML spec for the app-infra-generate skill.
- obsidian-load: Load context from Obsidian notes in the `Claude/` directory into the current conversation.
- obsidian-save: Save current conversation context to an Obsidian note under the `Claude/` directory.
- mind:stats: Show memory statistics and storage information
- mind:search: Search memories for specific content or patterns
- mind:recent: Show recent memories and activity timeline
- mind:ask: Ask questions about memories and get context-aware answers
- superpowers:test-driven-development: Use when implementing any feature or bugfix, before writing implementation code
- superpowers:using-git-worktrees: Use when starting feature work that needs isolation from current workspace or before executing implementation plans - ensures an isolated workspace exists via native tools or git worktree fallback
- superpowers:requesting-code-review: Use when completing tasks, implementing major features, or before merging to verify work meets requirements
- superpowers:writing-plans: Use when you have a spec or requirements for a multi-step task, before touching code
- superpowers:using-superpowers: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
- superpowers:executing-plans: Use when you have a written implementation plan to execute in a separate session with review checkpoints
- superpowers:finishing-a-development-branch: Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
- superpowers:verification-before-completion: Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always
- superpowers:dispatching-parallel-agents: Use when facing 2+ independent tasks that can be worked on without shared state or sequential dependencies
- superpowers:receiving-code-review: Use when receiving code review feedback, before implementing suggestions, especially if feedback seems unclear or technically questionable - requires technical rigor and verification, not performative agreement or blind implementation
- superpowers:brainstorming: You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.
- superpowers:systematic-debugging: Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes
- superpowers:writing-skills: Use when creating new skills, editing existing skills, or verifying skills work before deployment
- superpowers:subagent-driven-development: Use when executing implementation plans with independent tasks in the current session
- mind:memory: Claude Mind - Search and manage Claude's persistent memory stored in a single portable .mv2 file
- mind:mind: Claude Mind - Search and manage Claude's persistent memory stored in a single portable .mv2 file
- update-config: Use this skill to configure the Claude Code harness via settings.json. Automated behaviors ("from now on when X", "each time X", "whenever X", "before/after X") require hooks configured in settings.json - the harness executes these, not Claude, so memory/preferences cannot fulfill them. Also use for: permissions ("allow X", "add permission", "move permission to"), env vars ("set X=Y"), hook troubleshooting, or any changes to settings.json/settings.local.json files. Examples: "allow npm commands", "add bq permission to global settings", "move permission to user settings", "set DEBUG=true", "when claude stops show X". For simple settings like theme/model, suggest the /config command.
- keybindings-help: Use when the user wants to customize keyboard shortcuts, rebind keys, add chord bindings, or modify ~/.claude/keybindings.json. Examples: "rebind ctrl+s", "add a chord shortcut", "change the submit key", "customize keybindings".
- simplify: Review changed code for reuse, quality, and efficiency, then fix any issues found.
- fewer-permission-prompts: Scan your transcripts for common read-only Bash and MCP tool calls, then add a prioritized allowlist to project .claude/settings.json to reduce permission prompts.
- loop: Run a prompt or slash command on a recurring interval (e.g. /loop 5m /foo). Omit the interval to let the model self-pace. - When the user wants to set up a recurring task, poll for status, or run something repeatedly on an interval (e.g. "check the deploy every 5 minutes", "keep running /babysit-prs"). Do NOT invoke for one-off tasks.
- schedule: Create, update, list, or run scheduled remote agents (routines) that execute on a cron schedule. - When the user wants to schedule a recurring remote agent, set up automated tasks, create a cron job for Claude Code, or manage their scheduled agents/routines. Also use when the user wants a one-time scheduled run ("run this once at 3pm", "remind me to check X tomorrow").
- claude-api: Build, debug, and optimize Claude API / Anthropic SDK apps. Apps built with this skill should include prompt caching. Also handles migrating existing Claude API code between Claude model versions (4.5 → 4.6, 4.6 → 4.7, retired-model replacements).
TRIGGER when: code imports `anthropic`/`@anthropic-ai/sdk`; user asks for the Claude API, Anthropic SDK, or Managed Agents; user adds/modifies/tunes a Claude feature (caching, thinking, compaction, tool use, batch, files, citations, memory) or model (Opus/Sonnet/Haiku) in a file; questions about prompt caching / cache hit rate in an Anthropic SDK project.
SKIP: file imports `openai`/other-provider SDK, filename like `*-openai.py`/`*-generic.py`, provider-neutral code, general programming/ML.
- init: Initialize a new CLAUDE.md file with codebase documentation
- review: Review a pull request
- security-review: Complete a security review of the pending changes on the current branch


Fix YAML template strip marker merging entries


Let me look at the relevant template files to understand the issue.


Bash

find /Users/steve.briggs/projects/operations/terraform/internal-modules/kubernetes/monitoring/vector -type f | sort

List all files in vector module


bugbot says this Consecutive template strip markers merge YAML entries onto one line  High Severity  When both limits_cpu and limits_memory are non-null, the %{ endif ~} strip marker on the cpu line c…


1	role: Aggregator
2	replicas: ${replicas}
3	podAnnotations:
4	  reloader.stakater.com/auto: "true"
5	service:
6	  enabled: true
7	  type: ClusterIP
8	  ports:
9	    - name: otlp-grpc
10	      port: 4317
11	      protocol: TCP
12	    - name: otlp-http
13	      port: 4318
14	      protocol: TCP
15	    - name: dd-agent
16	      port: 8282
17	      protocol: TCP
18	env:
19	  - name: DATADOG_API_KEY
20	    valueFrom:
21	      secretKeyRef:
22	        name: vector-datadog
23	        key: api_key
24	resources:
25	  requests:
26	    cpu: ${requests_cpu}
27	    memory: ${requests_memory}
28	%{ if limits_cpu != null || limits_memory != null ~}
29	  limits:
30	    %{ if limits_cpu != null ~}cpu: "${limits_cpu}"%{ endif ~}
31	    %{ if limits_memory != null ~}memory: ${limits_memory}%{ endif ~}
32	%{ endif ~}
33	podDisruptionBudget:
34	  enabled: true
35	  minAvailable: 2
36
37	topologySpreadConstraints:
38	  - maxSkew: 1
39	    topologyKey: topology.kubernetes.io/zone
40	    whenUnsatisfiable: ScheduleAnyway
41	    labelSelector:
42	      matchLabels:
43	        app.kubernetes.io/name: vector
44
45	affinity:
46	  podAntiAffinity:
47	    preferredDuringSchedulingIgnoredDuringExecution:
48	      - weight: 100
49	        podAffinityTerm:
50	          topologyKey: kubernetes.io/hostname
51	          labelSelector:
52	            matchLabels:
53	              app.kubernetes.io/name: vector
54
55	%{ if service_monitor_enabled ~}
56	serviceMonitor:
57	  enabled: true
58	%{ endif ~}
59	customConfig:
60	  data_dir: /vector-data-dir
61	  api:
62	    enabled: true
63	    address: 0.0.0.0:8686
64	  sources:
65	    otlp_in:
66	      type: opentelemetry
67	      grpc:
68	        address: 0.0.0.0:4317
69	      http:
70	        address: 0.0.0.0:4318
71	    dd_agent_in:
72	      type: datadog_agent
73	      address: 0.0.0.0:8282
74	      multiple_outputs: true
75	      disable_metrics: true
76	      disable_traces: true
77	      store_api_key: false
78	      acknowledgements:
79	        enabled: true
80	  transforms:
81	    drop_audit:
82	      type: filter
83	      inputs:
84	        - otlp_in.logs
85	        - dd_agent_in.logs
86	      condition: |
87	        msg = if is_string(.message) { string!(.message) } else { "" }
88	        ${audit_drop_condition}
89	    normalize_logs:
90	      type: remap
91	      inputs:
92	        - drop_audit
93	      source: |
94	        if exists(.severity) {
95	          .status = downcase(to_string!(.severity))
96	          del(.severity)
97	        }
98	%{ if email_do_redact ~}
99	    redact_emails_logs:
100	      type: remap
101	      inputs:
102	        - normalize_logs
103	      source: |
104	        email_re = r'[\w._%+\-]+@[\w.-]+\.[A-Za-z]{2,}'
105	        if is_string(.message) {
106	          .message = replace(string!(.message), email_re, "${email_redaction_replacement}")
107	        }
108	        if is_object(.attributes) {
109	          attrs, _err = object(.attributes)
110	          if is_object(attrs) {
111	            .attributes = map_values(attrs) -> |v| {
112	              if is_string(v) { replace(string!(v), email_re, "${email_redaction_replacement}") } else { v }
113	            }
114	          }
115	        }
116	%{ endif ~}
117	%{ if email_do_tag ~}
118	    tag_emails_logs:
119	      type: remap
120	      inputs:
121	        - normalize_logs
122	      source: |
123	        email_re = r'[\w._%+\-]+@[\w.-]+\.[A-Za-z]{2,}'
124	        detected = false
125	        if is_string(.message) {
126	          detected = match(string!(.message), email_re)
127	        }
128	        if !detected && is_object(.attributes) {
129	          detected = match(encode_json(.attributes), email_re)
130	        }
131	        if detected {
132	          .ddtags = if is_string(.ddtags) { string!(.ddtags) + ",vector_redaction:true" } else { "vector_redaction:true" }
133	        }
134	%{ endif ~}
135	%{ if bearer_do_redact ~}
136	    redact_bearer_tokens_logs:
137	      type: remap
138	      inputs:
139	        - ${bearer_token_logs_input}
140	      source: |
141	        bearer_re = r'(?i)Bearer\s+[A-Za-z0-9+/._~-]+=*'
142	        svc = to_string(.service) ?? ""
143	        ddtags = to_string(.ddtags) ?? ""
144	%{ if bearer_needs_combined ~}
145	        msg = if is_string(.message) { string!(.message) } else { "" }
146	        combined = msg + " " + ddtags
147	%{ endif ~}
148	        target_services = ${bearer_token_services_arr}
149	        should_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${bearer_token_exclusion_condition}
150	        if should_redact && is_string(.message) {
151	          .message = replace(string!(.message), bearer_re, "${bearer_token_redaction_replacement}")
152	        }
153	%{ endif ~}
154	%{ if bearer_do_tag ~}
155	    tag_bearer_tokens_logs:
156	      type: remap
157	      inputs:
158	        - ${bearer_token_logs_input}
159	      source: |
160	        bearer_re = r'(?i)Bearer\s+[A-Za-z0-9+/._~-]+=*'
161	        svc = to_string(.service) ?? ""
162	        ddtags = to_string(.ddtags) ?? ""
163	%{ if bearer_needs_combined ~}
164	        msg = if is_string(.message) { string!(.message) } else { "" }
165	        combined = msg + " " + ddtags
166	%{ endif ~}
167	        target_services = ${bearer_token_services_arr}
168	        would_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${bearer_token_exclusion_condition}
169	        if would_redact && is_string(.message) && match(string!(.message), bearer_re) {
170	          .ddtags = if is_string(.ddtags) { string!(.ddtags) + ",vector_redaction:true" } else { "vector_redaction:true" }
171	        }
172	%{ endif ~}
173	%{ if aws_key_do_redact ~}
174	    redact_aws_access_keys_logs:
175	      type: remap
176	      inputs:
177	        - ${aws_key_logs_input}
178	      source: |
179	        aws_key_re = r'(AKIA|ASIA|AROA|AIPA|ANPA|ANVA|APKA)[A-Z0-9]{16}'
180	        svc = to_string(.service) ?? ""
181	        ddtags = to_string(.ddtags) ?? ""
182	%{ if aws_key_needs_combined ~}
183	        msg = if is_string(.message) { string!(.message) } else { "" }
184	        combined = msg + " " + ddtags
185	%{ endif ~}
186	        target_services = ${aws_key_services_arr}
187	        attr_excluded = ${aws_key_attr_exclusion_condition}
188	        should_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${aws_key_string_exclusion_condition} && !attr_excluded
189	        if should_redact && is_string(.message) {
190	          .message = replace(string!(.message), aws_key_re, "${aws_access_key_redaction_replacement}")
191	        }
192	%{ endif ~}
193	%{ if aws_key_do_tag ~}
194	    tag_aws_access_keys_logs:
195	      type: remap
196	      inputs:
197	        - ${aws_key_logs_input}
198	      source: |
199	        aws_key_re = r'(AKIA|ASIA|AROA|AIPA|ANPA|ANVA|APKA)[A-Z0-9]{16}'
200	        svc = to_string(.service) ?? ""
201	        ddtags = to_string(.ddtags) ?? ""
202	%{ if aws_key_needs_combined ~}
203	        msg = if is_string(.message) { string!(.message) } else { "" }
204	        combined = msg + " " + ddtags
205	%{ endif ~}
206	        target_services = ${aws_key_services_arr}
207	        attr_excluded = ${aws_key_attr_exclusion_condition}
208	        would_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${aws_key_string_exclusion_condition} && !attr_excluded
209	        if would_redact && is_string(.message) && match(string!(.message), aws_key_re) {
210	          .ddtags = if is_string(.ddtags) { string!(.ddtags) + ",vector_redaction:true" } else { "vector_redaction:true" }
211	        }
212	%{ endif ~}
213	%{ if jwt_do_redact ~}
214	    redact_jwts_logs:
215	      type: remap
216	      inputs:
217	        - ${jwt_logs_input}
218	      source: |
219	        jwt_re = r'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+'
220	        svc = to_string(.service) ?? ""
221	        ddtags = to_string(.ddtags) ?? ""
222	%{ if jwt_needs_combined ~}
223	        msg = if is_string(.message) { string!(.message) } else { "" }
224	        combined = msg + " " + ddtags
225	%{ endif ~}
226	        target_services = ${jwt_services_arr}
227	        should_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${jwt_exclusion_condition}
228	        if should_redact && is_string(.message) {
229	          .message = replace(string!(.message), jwt_re, "${jwt_redaction_replacement}")
230	        }
231	%{ endif ~}
232	%{ if jwt_do_tag ~}
233	    tag_jwts_logs:
234	      type: remap
235	      inputs:
236	        - ${jwt_logs_input}
237	      source: |
238	        jwt_re = r'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+'
239	        svc = to_string(.service) ?? ""
240	        ddtags = to_string(.ddtags) ?? ""
241	%{ if jwt_needs_combined ~}
242	        msg = if is_string(.message) { string!(.message) } else { "" }
243	        combined = msg + " " + ddtags
244	%{ endif ~}
245	        target_services = ${jwt_services_arr}
246	        would_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${jwt_exclusion_condition}
247	        if would_redact && is_string(.message) && match(string!(.message), jwt_re) {
248	          .ddtags = if is_string(.ddtags) { string!(.ddtags) + ",vector_redaction:true" } else { "vector_redaction:true" }
249	        }
250	%{ endif ~}
251	  sinks:
252	    datadog_logs:
253	      type: datadog_logs
254	      inputs:
255	        - ${logs_sink_input}
256	      default_api_key: $${DATADOG_API_KEY}
257	      site: ${datadog_site}
258

role: Aggregator
replicas: ${replicas}
podAnnotations:
  reloader.stakater.com/auto: "true"
service:
  enabled: true
  type: ClusterIP
  ports:
    - name: otlp-grpc
      port: 4317
      protocol: TCP
    - name: otlp-http
      port: 4318
      protocol: TCP
    - name: dd-agent
      port: 8282
      protocol: TCP
env:
  - name: DATADOG_API_KEY
    valueFrom:
      secretKeyRef:
        name: vector-datadog
        key: api_key
resources:
  requests:
    cpu: ${requests_cpu}
    memory: ${requests_memory}
%{ if limits_cpu != null || limits_memory != null ~}
  limits:
    %{ if limits_cpu != null ~}cpu: "${limits_cpu}"%{ endif ~}
    %{ if limits_memory != null ~}memory: ${limits_memory}%{ endif ~}
%{ endif ~}
podDisruptionBudget:
  enabled: true
  minAvailable: 2

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: ScheduleAnyway
    labelSelector:
      matchLabels:
        app.kubernetes.io/name: vector

affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          topologyKey: kubernetes.io/hostname
          labelSelector:
            matchLabels:
              app.kubernetes.io/name: vector

%{ if service_monitor_enabled ~}
serviceMonitor:
  enabled: true
%{ endif ~}
customConfig:
  data_dir: /vector-data-dir
  api:
    enabled: true
    address: 0.0.0.0:8686
  sources:
    otlp_in:
      type: opentelemetry
      grpc:
        address: 0.0.0.0:4317
      http:
        address: 0.0.0.0:4318
    dd_agent_in:
      type: datadog_agent
      address: 0.0.0.0:8282
      multiple_outputs: true
      disable_metrics: true
      disable_traces: true
      store_api_key: false
      acknowledgements:
        enabled: true
  transforms:
    drop_audit:
      type: filter
      inputs:
        - otlp_in.logs
        - dd_agent_in.logs
      condition: |
        msg = if is_string(.message) { string!(.message) } else { "" }
        ${audit_drop_condition}
    normalize_logs:
      type: remap
      inputs:
        - drop_audit
      source: |
        if exists(.severity) {
          .status = downcase(to_string!(.severity))
          del(.severity)
        }
%{ if email_do_redact ~}
    redact_emails_logs:
      type: remap
      inputs:
        - normalize_logs
      source: |
        email_re = r'[\w._%+\-]+@[\w.-]+\.[A-Za-z]{2,}'
        if is_string(.message) {
          .message = replace(string!(.message), email_re, "${email_redaction_replacement}")
        }
        if is_object(.attributes) {
          attrs, _err = object(.attributes)
          if is_object(attrs) {
            .attributes = map_values(attrs) -> |v| {
              if is_string(v) { replace(string!(v), email_re, "${email_redaction_replacement}") } else { v }
            }
          }
        }
%{ endif ~}
%{ if email_do_tag ~}
    tag_emails_logs:
      type: remap
      inputs:
        - normalize_logs
      source: |
        email_re = r'[\w._%+\-]+@[\w.-]+\.[A-Za-z]{2,}'
        detected = false
        if is_string(.message) {
          detected = match(string!(.message), email_re)
        }
        if !detected && is_object(.attributes) {
          detected = match(encode_json(.attributes), email_re)
        }
        if detected {
          .ddtags = if is_string(.ddtags) { string!(.ddtags) + ",vector_redaction:true" } else { "vector_redaction:true" }
        }
%{ endif ~}
%{ if bearer_do_redact ~}
    redact_bearer_tokens_logs:
      type: remap
      inputs:
        - ${bearer_token_logs_input}
      source: |
        bearer_re = r'(?i)Bearer\s+[A-Za-z0-9+/._~-]+=*'
        svc = to_string(.service) ?? ""
        ddtags = to_string(.ddtags) ?? ""
%{ if bearer_needs_combined ~}
        msg = if is_string(.message) { string!(.message) } else { "" }
        combined = msg + " " + ddtags
%{ endif ~}
        target_services = ${bearer_token_services_arr}
        should_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${bearer_token_exclusion_condition}
        if should_redact && is_string(.message) {
          .message = replace(string!(.message), bearer_re, "${bearer_token_redaction_replacement}")
        }
%{ endif ~}
%{ if bearer_do_tag ~}
    tag_bearer_tokens_logs:
      type: remap
      inputs:
        - ${bearer_token_logs_input}
      source: |
        bearer_re = r'(?i)Bearer\s+[A-Za-z0-9+/._~-]+=*'
        svc = to_string(.service) ?? ""
        ddtags = to_string(.ddtags) ?? ""
%{ if bearer_needs_combined ~}
        msg = if is_string(.message) { string!(.message) } else { "" }
        combined = msg + " " + ddtags
%{ endif ~}
        target_services = ${bearer_token_services_arr}
        would_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${bearer_token_exclusion_condition}
        if would_redact && is_string(.message) && match(string!(.message), bearer_re) {
          .ddtags = if is_string(.ddtags) { string!(.ddtags) + ",vector_redaction:true" } else { "vector_redaction:true" }
        }
%{ endif ~}
%{ if aws_key_do_redact ~}
    redact_aws_access_keys_logs:
      type: remap
      inputs:
        - ${aws_key_logs_input}
      source: |
        aws_key_re = r'(AKIA|ASIA|AROA|AIPA|ANPA|ANVA|APKA)[A-Z0-9]{16}'
        svc = to_string(.service) ?? ""
        ddtags = to_string(.ddtags) ?? ""
%{ if aws_key_needs_combined ~}
        msg = if is_string(.message) { string!(.message) } else { "" }
        combined = msg + " " + ddtags
%{ endif ~}
        target_services = ${aws_key_services_arr}
        attr_excluded = ${aws_key_attr_exclusion_condition}
        should_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${aws_key_string_exclusion_condition} && !attr_excluded
        if should_redact && is_string(.message) {
          .message = replace(string!(.message), aws_key_re, "${aws_access_key_redaction_replacement}")
        }
%{ endif ~}
%{ if aws_key_do_tag ~}
    tag_aws_access_keys_logs:
      type: remap
      inputs:
        - ${aws_key_logs_input}
      source: |
        aws_key_re = r'(AKIA|ASIA|AROA|AIPA|ANPA|ANVA|APKA)[A-Z0-9]{16}'
        svc = to_string(.service) ?? ""
        ddtags = to_string(.ddtags) ?? ""
%{ if aws_key_needs_combined ~}
        msg = if is_string(.message) { string!(.message) } else { "" }
        combined = msg + " " + ddtags
%{ endif ~}
        target_services = ${aws_key_services_arr}
        attr_excluded = ${aws_key_attr_exclusion_condition}
        would_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${aws_key_string_exclusion_condition} && !attr_excluded
        if would_redact && is_string(.message) && match(string!(.message), aws_key_re) {
          .ddtags = if is_string(.ddtags) { string!(.ddtags) + ",vector_redaction:true" } else { "vector_redaction:true" }
        }
%{ endif ~}
%{ if jwt_do_redact ~}
    redact_jwts_logs:
      type: remap
      inputs:
        - ${jwt_logs_input}
      source: |
        jwt_re = r'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+'
        svc = to_string(.service) ?? ""
        ddtags = to_string(.ddtags) ?? ""
%{ if jwt_needs_combined ~}
        msg = if is_string(.message) { string!(.message) } else { "" }
        combined = msg + " " + ddtags
%{ endif ~}
        target_services = ${jwt_services_arr}
        should_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${jwt_exclusion_condition}
        if should_redact && is_string(.message) {
          .message = replace(string!(.message), jwt_re, "${jwt_redaction_replacement}")
        }
%{ endif ~}
%{ if jwt_do_tag ~}
    tag_jwts_logs:
      type: remap
      inputs:
        - ${jwt_logs_input}
      source: |
        jwt_re = r'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+'
        svc = to_string(.service) ?? ""
        ddtags = to_string(.ddtags) ?? ""
%{ if jwt_needs_combined ~}
        msg = if is_string(.message) { string!(.message) } else { "" }
        combined = msg + " " + ddtags
%{ endif ~}
        target_services = ${jwt_services_arr}
        would_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${jwt_exclusion_condition}
        if would_redact && is_string(.message) && match(string!(.message), jwt_re) {
          .ddtags = if is_string(.ddtags) { string!(.ddtags) + ",vector_redaction:true" } else { "vector_redaction:true" }
        }
%{ endif ~}
  sinks:
    datadog_logs:
      type: datadog_logs
      inputs:
        - ${logs_sink_input}
      default_api_key: $${DATADOG_API_KEY}
      site: ${datadog_site}


I can see the bug at lines 30-31. The `%{ endif ~}` on the cpu line strips the trailing newline, causing the memory line's leading indentation to merge with the cpu value. Let me check the snapshot too.


1	role: Aggregator
2	replicas: 3
3	podAnnotations:
4	  reloader.stakater.com/auto: "true"
5	service:
6	  enabled: true
7	  type: ClusterIP
8	  ports:
9	    - name: otlp-grpc
10	      port: 4317
11	      protocol: TCP
12	    - name: otlp-http
13	      port: 4318
14	      protocol: TCP
15	env:
16	  - name: DATADOG_API_KEY
17	    valueFrom:
18	      secretKeyRef:
19	        name: vector-datadog
20	        key: api_key
21	resources:
22	  requests:
23	    cpu: 200m
24	    memory: 256Mi
25	  limits:
26	    cpu: "2"
27	    memory: 1Gi
28	customConfig:
29	  data_dir: /vector-data-dir
30	  api:
31	    enabled: true
32	    address: 127.0.0.1:8686
33	  sources:
34	    otlp_in:
35	      type: opentelemetry
36	      grpc:
37	        address: 0.0.0.0:4317
38	      http:
39	        address: 0.0.0.0:4318
40	  transforms: {}
41	  sinks:
42	    datadog_logs:
43	      type: datadog_logs
44	      inputs:
45	        - otlp_in.logs
46	      default_api_key: ${DATADOG_API_KEY}
47	      site: datadoghq.com
48	    datadog_traces:
49	      type: datadog_traces
50	      inputs:
51	        - otlp_in.traces
52	      default_api_key: ${DATADOG_API_KEY}
53	      site: datadoghq.com
54	    datadog_metrics:
55	      type: datadog_metrics
56	      inputs:
57	        - otlp_in.metrics
58	      default_api_key: ${DATADOG_API_KEY}
59	      site: datadoghq.com
60

role: Aggregator
replicas: 3
podAnnotations:
  reloader.stakater.com/auto: "true"
service:
  enabled: true
  type: ClusterIP
  ports:
    - name: otlp-grpc
      port: 4317
      protocol: TCP
    - name: otlp-http
      port: 4318
      protocol: TCP
env:
  - name: DATADOG_API_KEY
    valueFrom:
      secretKeyRef:
        name: vector-datadog
        key: api_key
resources:
  requests:
    cpu: 200m
    memory: 256Mi
  limits:
    cpu: "2"
    memory: 1Gi
customConfig:
  data_dir: /vector-data-dir
  api:
    enabled: true
    address: 127.0.0.1:8686
  sources:
    otlp_in:
      type: opentelemetry
      grpc:
        address: 0.0.0.0:4317
      http:
        address: 0.0.0.0:4318
  transforms: {}
  sinks:
    datadog_logs:
      type: datadog_logs
      inputs:
        - otlp_in.logs
      default_api_key: ${DATADOG_API_KEY}
      site: datadoghq.com
    datadog_traces:
      type: datadog_traces
      inputs:
        - otlp_in.traces
      default_api_key: ${DATADOG_API_KEY}
      site: datadoghq.com
    datadog_metrics:
      type: datadog_metrics
      inputs:
        - otlp_in.metrics
      default_api_key: ${DATADOG_API_KEY}
      site: datadoghq.com


Let me check the variables to understand the defaults.


grep -A 10 "limits" /Users/steve.briggs/projects/operations/terraform/internal-modules/kubernetes/monitoring/vector/variables.tf | head -40

Find limits variable defaults


variable "resources_limits" {
  description = "Pod resource limits"
  type        = map(string)
  default = {
    cpu    = "2"
    memory = "1Gi"
  }
}

variable "namespace" {
  description = "Kubernetes namespace for Vector"
  type        = string


Let me look at main.tf to understand how the template is called.


1	terraform {
2	  required_version = ">= 1.9.0"
3
4	  required_providers {
5	    kubernetes = {
6	      source  = "hashicorp/kubernetes"
7	      version = ">= 2.20"
8	    }
9	    helm = {
10	      source  = "hashicorp/helm"
11	      version = ">= 2.10"
12	    }
13	  }
14	}
15
16	locals {
17	  audit_drop_condition = length(var.audit_drop_patterns) == 0 ? "true" : join(
18	    " && ",
19	    [for p in var.audit_drop_patterns : "!match(msg, r'${p}')"],
20	  )
21
22	  bearer_token_exclusion_condition = length(var.bearer_token_redaction_exclusion_patterns) == 0 ? "true" : join(
23	    " && ",
24	    [for p in var.bearer_token_redaction_exclusion_patterns : "!contains(combined, \"${p}\")"],
25	  )
26
27	  bearer_token_services_arr = "[\"${join("\", \"", var.bearer_token_redaction_services)}\"]"
28
29	  # dry_run always wins over enabled for both scanner types
30	  email_do_redact = var.email_redaction_enabled && !var.email_redaction_dry_run
31	  email_do_tag    = var.email_redaction_dry_run
32
33	  bearer_do_redact = var.bearer_token_redaction_enabled && !var.bearer_token_redaction_dry_run
34	  bearer_do_tag    = var.bearer_token_redaction_dry_run
35
36	  # named output of each stage used to wire transform inputs and the final sink
37	  email_stage_output = (
38	    local.email_do_redact ? "redact_emails_logs" :
39	    local.email_do_tag    ? "tag_emails_logs" :
40	    "normalize_logs"
41	  )
42
43	  bearer_stage_active   = local.bearer_do_redact || local.bearer_do_tag
44	  bearer_stage_output   = local.bearer_do_redact ? "redact_bearer_tokens_logs" : "tag_bearer_tokens_logs"
45	  bearer_needs_combined = length(var.bearer_token_redaction_exclusion_patterns) > 0
46
47	  bearer_token_logs_input = local.email_stage_output
48
49	  # AWS Access Key ID scanner
50	  aws_key_do_redact = var.aws_access_key_redaction_enabled && !var.aws_access_key_redaction_dry_run
51	  aws_key_do_tag    = var.aws_access_key_redaction_dry_run
52
53	  aws_key_services_arr = "[\"${join("\", \"", var.aws_access_key_redaction_services)}\"]"
54
55	  aws_key_string_exclusion_condition = length(var.aws_access_key_redaction_exclusion_patterns) == 0 ? "true" : join(
56	    " && ",
57	    [for p in var.aws_access_key_redaction_exclusion_patterns : "!contains(combined, \"${p}\")"],
58	  )
59
60	  # Each entry generates a VRL boolean: true when the attribute matches and should be excluded.
61	  # Uses quoted path access (.attributes."dotted.key") which is infallible; returns null for missing paths.
62	  aws_key_attr_exclusion_condition = length(var.aws_access_key_redaction_exclusion_attributes) == 0 ? "false" : join(
63	    " || ",
64	    [for k, v in var.aws_access_key_redaction_exclusion_attributes :
65	      "(to_string(.attributes.\"${k}\") ?? \"\") == \"${v}\""
66	    ],
67	  )
68
69	  aws_key_stage_active   = local.aws_key_do_redact || local.aws_key_do_tag
70	  aws_key_stage_output   = local.aws_key_do_redact ? "redact_aws_access_keys_logs" : "tag_aws_access_keys_logs"
71	  aws_key_needs_combined = length(var.aws_access_key_redaction_exclusion_patterns) > 0
72
73	  aws_key_logs_input = local.bearer_stage_active ? local.bearer_stage_output : local.email_stage_output
74
75	  # JWT scanner
76	  jwt_do_redact = var.jwt_redaction_enabled && !var.jwt_redaction_dry_run
77	  jwt_do_tag    = var.jwt_redaction_dry_run
78
79	  jwt_services_arr = "[\"${join("\", \"", var.jwt_redaction_services)}\"]"
80
81	  jwt_exclusion_condition = length(var.jwt_redaction_exclusion_patterns) == 0 ? "true" : join(
82	    " && ",
83	    [for p in var.jwt_redaction_exclusion_patterns : "!contains(combined, \"${p}\")"],
84	  )
85
86	  jwt_stage_active   = local.jwt_do_redact || local.jwt_do_tag
87	  jwt_stage_output   = local.jwt_do_redact ? "redact_jwts_logs" : "tag_jwts_logs"
88	  jwt_needs_combined = length(var.jwt_redaction_exclusion_patterns) > 0
89
90	  jwt_logs_input = (
91	    local.aws_key_stage_active ? local.aws_key_stage_output :
92	    local.bearer_stage_active  ? local.bearer_stage_output :
93	    local.email_stage_output
94	  )
95
96	  logs_sink_input = (
97	    local.jwt_stage_active     ? local.jwt_stage_output :
98	    local.aws_key_stage_active ? local.aws_key_stage_output :
99	    local.bearer_stage_active  ? local.bearer_stage_output :
100	    local.email_stage_output
101	  )
102
103	  rendered_values = var.additional_config != null ? yamlencode({ customConfig = var.additional_config }) : templatefile(
104	    "${path.module}/templates/values.yaml.tpl",
105	    {
106	      replicas                              = var.replicas
107	      requests_cpu                          = var.resources_requests.cpu
108	      requests_memory                       = var.resources_requests.memory
109	      limits_cpu                            = var.resources_limits.cpu
110	      limits_memory                         = var.resources_limits.memory
111	      datadog_site                          = var.datadog_site
112	      audit_drop_condition                  = local.audit_drop_condition
113	      email_do_redact                       = local.email_do_redact
114	      email_do_tag                          = local.email_do_tag
115	      email_redaction_replacement           = var.email_redaction_replacement
116	      service_monitor_enabled               = var.service_monitor_enabled
117	      bearer_do_redact                      = local.bearer_do_redact
118	      bearer_do_tag                         = local.bearer_do_tag
119	      bearer_needs_combined                 = local.bearer_needs_combined
120	      bearer_token_redaction_replacement    = var.bearer_token_redaction_replacement
121	      bearer_token_services_arr             = local.bearer_token_services_arr
122	      bearer_token_exclusion_condition      = local.bearer_token_exclusion_condition
123	      bearer_token_logs_input               = local.bearer_token_logs_input
124	      aws_key_do_redact                     = local.aws_key_do_redact
125	      aws_key_do_tag                        = local.aws_key_do_tag
126	      aws_key_needs_combined                = local.aws_key_needs_combined
127	      aws_access_key_redaction_replacement  = var.aws_access_key_redaction_replacement
128	      aws_key_services_arr                  = local.aws_key_services_arr
129	      aws_key_string_exclusion_condition    = local.aws_key_string_exclusion_condition
130	      aws_key_attr_exclusion_condition      = local.aws_key_attr_exclusion_condition
131	      aws_key_logs_input                    = local.aws_key_logs_input
132	      jwt_do_redact                         = local.jwt_do_redact
133	      jwt_do_tag                            = local.jwt_do_tag
134	      jwt_needs_combined                    = local.jwt_needs_combined
135	      jwt_redaction_replacement             = var.jwt_redaction_replacement
136	      jwt_services_arr                      = local.jwt_services_arr
137	      jwt_exclusion_condition               = local.jwt_exclusion_condition
138	      jwt_logs_input                        = local.jwt_logs_input
139	      logs_sink_input                       = local.logs_sink_input
140	    },
141	  )
142	}
143
144	resource "kubernetes_namespace_v1" "vector" {
145	  metadata {
146	    name = var.namespace
147	    labels = {
148	      "app.kubernetes.io/name"      = "vector"
149	      "app.kubernetes.io/component" = "telemetry-aggregator"
150	    }
151	  }
152	}
153
154	resource "kubernetes_manifest" "datadog_external_secret" {
155	  manifest = {
156	    apiVersion = "external-secrets.io/v1"
157	    kind       = "ExternalSecret"
158	    metadata = {
159	      name      = "vector-datadog"
160	      namespace = kubernetes_namespace_v1.vector.metadata[0].name
161	    }
162	    spec = {
163	      refreshInterval = "1h"
164	      secretStoreRef = {
165	        kind = "ClusterSecretStore"
166	        name = var.external_secrets_cluster_store_name
167	      }
168	      target = {
169	        name           = "vector-datadog"
170	        creationPolicy = "Owner"
171	      }
172	      data = [{
173	        secretKey = "api_key"
174	        remoteRef = {
175	          key      = var.datadog_api_key_vault_path
176	          property = "apiKey"
177	        }
178	      }]
179	    }
180	  }
181
182	  wait {
183	    fields = {
184	      "status.conditions[0].type"   = "Ready"
185	      "status.conditions[0].status" = "True"
186	    }
187	  }
188
189	  timeouts {
190	    create = "5m"
191	    update = "5m"
192	  }
193	}
194
195	resource "helm_release" "vector" {
196	  name       = "vector"
197	  namespace  = kubernetes_namespace_v1.vector.metadata[0].name
198	  repository = "https://helm.vector.dev"
199	  chart      = "vector"
200	  version    = var.chart_version
201
202	  values = [local.rendered_values]
203
204	  set {
205	    name  = "image.repository"
206	    value = var.image
207	  }
208	  set {
209	    name  = "image.tag"
210	    value = var.image_tag_version
211	  }
212
213	  depends_on = [
214	    kubernetes_manifest.datadog_external_secret,
215	  ]
216	}
217
218	module "netpol" {
219	  source = "crow.infrastructure.immuta.io/internal/security-network-policy/kubernetes"
220	  version = "1.1.0"
221
222	  name             = "vector-netpol"
223	  namespace        = var.namespace
224	  network_policies = local.netpols
225
226	  depends_on = [
227	    kubernetes_namespace_v1.vector,
228	  ]
229	}
230

terraform {
  required_version = ">= 1.9.0"

  required_providers {
    kubernetes = {
      source  = "hashicorp/kubernetes"
      version = ">= 2.20"
    }
    helm = {
      source  = "hashicorp/helm"
      version = ">= 2.10"
    }
  }
}

locals {
  audit_drop_condition = length(var.audit_drop_patterns) == 0 ? "true" : join(
    " && ",
    [for p in var.audit_drop_patterns : "!match(msg, r'${p}')"],
  )

  bearer_token_exclusion_condition = length(var.bearer_token_redaction_exclusion_patterns) == 0 ? "true" : join(
    " && ",
    [for p in var.bearer_token_redaction_exclusion_patterns : "!contains(combined, \"${p}\")"],
  )

  bearer_token_services_arr = "[\"${join("\", \"", var.bearer_token_redaction_services)}\"]"

  # dry_run always wins over enabled for both scanner types
  email_do_redact = var.email_redaction_enabled && !var.email_redaction_dry_run
  email_do_tag    = var.email_redaction_dry_run

  bearer_do_redact = var.bearer_token_redaction_enabled && !var.bearer_token_redaction_dry_run
  bearer_do_tag    = var.bearer_token_redaction_dry_run

  # named output of each stage used to wire transform inputs and the final sink
  email_stage_output = (
    local.email_do_redact ? "redact_emails_logs" :
    local.email_do_tag    ? "tag_emails_logs" :
    "normalize_logs"
  )

  bearer_stage_active   = local.bearer_do_redact || local.bearer_do_tag
  bearer_stage_output   = local.bearer_do_redact ? "redact_bearer_tokens_logs" : "tag_bearer_tokens_logs"
  bearer_needs_combined = length(var.bearer_token_redaction_exclusion_patterns) > 0

  bearer_token_logs_input = local.email_stage_output

  # AWS Access Key ID scanner
  aws_key_do_redact = var.aws_access_key_redaction_enabled && !var.aws_access_key_redaction_dry_run
  aws_key_do_tag    = var.aws_access_key_redaction_dry_run

  aws_key_services_arr = "[\"${join("\", \"", var.aws_access_key_redaction_services)}\"]"

  aws_key_string_exclusion_condition = length(var.aws_access_key_redaction_exclusion_patterns) == 0 ? "true" : join(
    " && ",
    [for p in var.aws_access_key_redaction_exclusion_patterns : "!contains(combined, \"${p}\")"],
  )

  # Each entry generates a VRL boolean: true when the attribute matches and should be excluded.
  # Uses quoted path access (.attributes."dotted.key") which is infallible; returns null for missing paths.
  aws_key_attr_exclusion_condition = length(var.aws_access_key_redaction_exclusion_attributes) == 0 ? "false" : join(
    " || ",
    [for k, v in var.aws_access_key_redaction_exclusion_attributes :
      "(to_string(.attributes.\"${k}\") ?? \"\") == \"${v}\""
    ],
  )

  aws_key_stage_active   = local.aws_key_do_redact || local.aws_key_do_tag
  aws_key_stage_output   = local.aws_key_do_redact ? "redact_aws_access_keys_logs" : "tag_aws_access_keys_logs"
  aws_key_needs_combined = length(var.aws_access_key_redaction_exclusion_patterns) > 0

  aws_key_logs_input = local.bearer_stage_active ? local.bearer_stage_output : local.email_stage_output

  # JWT scanner
  jwt_do_redact = var.jwt_redaction_enabled && !var.jwt_redaction_dry_run
  jwt_do_tag    = var.jwt_redaction_dry_run

  jwt_services_arr = "[\"${join("\", \"", var.jwt_redaction_services)}\"]"

  jwt_exclusion_condition = length(var.jwt_redaction_exclusion_patterns) == 0 ? "true" : join(
    " && ",
    [for p in var.jwt_redaction_exclusion_patterns : "!contains(combined, \"${p}\")"],
  )

  jwt_stage_active   = local.jwt_do_redact || local.jwt_do_tag
  jwt_stage_output   = local.jwt_do_redact ? "redact_jwts_logs" : "tag_jwts_logs"
  jwt_needs_combined = length(var.jwt_redaction_exclusion_patterns) > 0

  jwt_logs_input = (
    local.aws_key_stage_active ? local.aws_key_stage_output :
    local.bearer_stage_active  ? local.bearer_stage_output :
    local.email_stage_output
  )

  logs_sink_input = (
    local.jwt_stage_active     ? local.jwt_stage_output :
    local.aws_key_stage_active ? local.aws_key_stage_output :
    local.bearer_stage_active  ? local.bearer_stage_output :
    local.email_stage_output
  )

  rendered_values = var.additional_config != null ? yamlencode({ customConfig = var.additional_config }) : templatefile(
    "${path.module}/templates/values.yaml.tpl",
    {
      replicas                              = var.replicas
      requests_cpu                          = var.resources_requests.cpu
      requests_memory                       = var.resources_requests.memory
      limits_cpu                            = var.resources_limits.cpu
      limits_memory                         = var.resources_limits.memory
      datadog_site                          = var.datadog_site
      audit_drop_condition                  = local.audit_drop_condition
      email_do_redact                       = local.email_do_redact
      email_do_tag                          = local.email_do_tag
      email_redaction_replacement           = var.email_redaction_replacement
      service_monitor_enabled               = var.service_monitor_enabled
      bearer_do_redact                      = local.bearer_do_redact
      bearer_do_tag                         = local.bearer_do_tag
      bearer_needs_combined                 = local.bearer_needs_combined
      bearer_token_redaction_replacement    = var.bearer_token_redaction_replacement
      bearer_token_services_arr             = local.bearer_token_services_arr
      bearer_token_exclusion_condition      = local.bearer_token_exclusion_condition
      bearer_token_logs_input               = local.bearer_token_logs_input
      aws_key_do_redact                     = local.aws_key_do_redact
      aws_key_do_tag                        = local.aws_key_do_tag
      aws_key_needs_combined                = local.aws_key_needs_combined
      aws_access_key_redaction_replacement  = var.aws_access_key_redaction_replacement
      aws_key_services_arr                  = local.aws_key_services_arr
      aws_key_string_exclusion_condition    = local.aws_key_string_exclusion_condition
      aws_key_attr_exclusion_condition      = local.aws_key_attr_exclusion_condition
      aws_key_logs_input                    = local.aws_key_logs_input
      jwt_do_redact                         = local.jwt_do_redact
      jwt_do_tag                            = local.jwt_do_tag
      jwt_needs_combined                    = local.jwt_needs_combined
      jwt_redaction_replacement             = var.jwt_redaction_replacement
      jwt_services_arr                      = local.jwt_services_arr
      jwt_exclusion_condition               = local.jwt_exclusion_condition
      jwt_logs_input                        = local.jwt_logs_input
      logs_sink_input                       = local.logs_sink_input
    },
  )
}

resource "kubernetes_namespace_v1" "vector" {
  metadata {
    name = var.namespace
    labels = {
      "app.kubernetes.io/name"      = "vector"
      "app.kubernetes.io/component" = "telemetry-aggregator"
    }
  }
}

resource "kubernetes_manifest" "datadog_external_secret" {
  manifest = {
    apiVersion = "external-secrets.io/v1"
    kind       = "ExternalSecret"
    metadata = {
      name      = "vector-datadog"
      namespace = kubernetes_namespace_v1.vector.metadata[0].name
    }
    spec = {
      refreshInterval = "1h"
      secretStoreRef = {
        kind = "ClusterSecretStore"
        name = var.external_secrets_cluster_store_name
      }
      target = {
        name           = "vector-datadog"
        creationPolicy = "Owner"
      }
      data = [{
        secretKey = "api_key"
        remoteRef = {
          key      = var.datadog_api_key_vault_path
          property = "apiKey"
        }
      }]
    }
  }

  wait {
    fields = {
      "status.conditions[0].type"   = "Ready"
      "status.conditions[0].status" = "True"
    }
  }

  timeouts {
    create = "5m"
    update = "5m"
  }
}

resource "helm_release" "vector" {
  name       = "vector"
  namespace  = kubernetes_namespace_v1.vector.metadata[0].name
  repository = "https://helm.vector.dev"
  chart      = "vector"
  version    = var.chart_version

  values = [local.rendered_values]

  set {
    name  = "image.repository"
    value = var.image
  }
  set {
    name  = "image.tag"
    value = var.image_tag_version
  }

  depends_on = [
    kubernetes_manifest.datadog_external_secret,
  ]
}

module "netpol" {
  source = "crow.infrastructure.immuta.io/internal/security-network-policy/kubernetes"
  version = "1.1.0"

  name             = "vector-netpol"
  namespace        = var.namespace
  network_policies = local.netpols

  depends_on = [
    kubernetes_namespace_v1.vector,
  ]
}


Now I understand the full picture. Let me check the snapshot testing mechanism.


find /Users/steve.briggs/projects/operations/terraform/internal-modules/kubernetes/monitoring/vector -name "*.tf" | xargs grep -l "snapshot\|test" 2>/dev/null | grep -v .terraform

Find test-related terraform files


find /Users/steve.briggs/projects/operations/terraform/internal-modules/kubernetes/monitoring/vector/tests -type f -name "*.tf" 2>/dev/null | head -20

Find test tf files


(Bash completed with no output)


The snapshot is a reference file, not an automated test. The fix is clear. The bug is on lines 30-31 — the `%{ endif ~}` after the cpu value strip-consumes the `\n    ` separator before the memory line.

Here's the fix: restructure the inner conditionals so each field's content is part of the if-block content (including the trailing newline), and put `%{ endif }%{ if }` on the same line to avoid blank-line artifacts.


Edit

%{ if limits_cpu != null || limits_memory != null ~}
  limits:
    %{ if limits_cpu != null ~}cpu: "${limits_cpu}"%{ endif ~}
    %{ if limits_memory != null ~}memory: ${limits_memory}%{ endif ~}
%{ endif ~}

%{ if limits_cpu != null || limits_memory != null ~}
  limits:
%{ if limits_cpu != null }    cpu: "${limits_cpu}"
%{ endif }%{ if limits_memory != null }    memory: ${limits_memory}
%{ endif ~}
%{ endif ~}


The file /Users/steve.briggs/projects/operations/terraform/internal-modules/kubernetes/monitoring/vector/templates/values.yaml.tpl has been updated successfully. (file state is current in your context — no need to Read it back)

%{ if limits_cpu != null || limits_memory != null ~}
  limits:
%{ if limits_cpu != null }    cpu: "${limits_cpu}"
%{ endif }%{ if limits_memory != null }    memory: ${limits_memory}
%{ endif ~}
%{ endif ~}

role: Aggregator
replicas: ${replicas}
podAnnotations:
  reloader.stakater.com/auto: "true"
service:
  enabled: true
  type: ClusterIP
  ports:
    - name: otlp-grpc
      port: 4317
      protocol: TCP
    - name: otlp-http
      port: 4318
      protocol: TCP
    - name: dd-agent
      port: 8282
      protocol: TCP
env:
  - name: DATADOG_API_KEY
    valueFrom:
      secretKeyRef:
        name: vector-datadog
        key: api_key
resources:
  requests:
    cpu: ${requests_cpu}
    memory: ${requests_memory}
%{ if limits_cpu != null || limits_memory != null ~}
  limits:
    %{ if limits_cpu != null ~}cpu: "${limits_cpu}"%{ endif ~}
    %{ if limits_memory != null ~}memory: ${limits_memory}%{ endif ~}
%{ endif ~}
podDisruptionBudget:
  enabled: true
  minAvailable: 2

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: ScheduleAnyway
    labelSelector:
      matchLabels:
        app.kubernetes.io/name: vector

affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          topologyKey: kubernetes.io/hostname
          labelSelector:
            matchLabels:
              app.kubernetes.io/name: vector

%{ if service_monitor_enabled ~}
serviceMonitor:
  enabled: true
%{ endif ~}
customConfig:
  data_dir: /vector-data-dir
  api:
    enabled: true
    address: 0.0.0.0:8686
  sources:
    otlp_in:
      type: opentelemetry
      grpc:
        address: 0.0.0.0:4317
      http:
        address: 0.0.0.0:4318
    dd_agent_in:
      type: datadog_agent
      address: 0.0.0.0:8282
      multiple_outputs: true
      disable_metrics: true
      disable_traces: true
      store_api_key: false
      acknowledgements:
        enabled: true
  transforms:
    drop_audit:
      type: filter
      inputs:
        - otlp_in.logs
        - dd_agent_in.logs
      condition: |
        msg = if is_string(.message) { string!(.message) } else { "" }
        ${audit_drop_condition}
    normalize_logs:
      type: remap
      inputs:
        - drop_audit
      source: |
        if exists(.severity) {
          .status = downcase(to_string!(.severity))
          del(.severity)
        }
%{ if email_do_redact ~}
    redact_emails_logs:
      type: remap
      inputs:
        - normalize_logs
      source: |
        email_re = r'[\w._%+\-]+@[\w.-]+\.[A-Za-z]{2,}'
        if is_string(.message) {
          .message = replace(string!(.message), email_re, "${email_redaction_replacement}")
        }
        if is_object(.attributes) {
          attrs, _err = object(.attributes)
          if is_object(attrs) {
            .attributes = map_values(attrs) -> |v| {
              if is_string(v) { replace(string!(v), email_re, "${email_redaction_replacement}") } else { v }
            }
          }
        }
%{ endif ~}
%{ if email_do_tag ~}
    tag_emails_logs:
      type: remap
      inputs:
        - normalize_logs
      source: |
        email_re = r'[\w._%+\-]+@[\w.-]+\.[A-Za-z]{2,}'
        detected = false
        if is_string(.message) {
          detected = match(string!(.message), email_re)
        }
        if !detected && is_object(.attributes) {
          detected = match(encode_json(.attributes), email_re)
        }
        if detected {
          .ddtags = if is_string(.ddtags) { string!(.ddtags) + ",vector_redaction:true" } else { "vector_redaction:true" }
        }
%{ endif ~}
%{ if bearer_do_redact ~}
    redact_bearer_tokens_logs:
      type: remap
      inputs:
        - ${bearer_token_logs_input}
      source: |
        bearer_re = r'(?i)Bearer\s+[A-Za-z0-9+/._~-]+=*'
        svc = to_string(.service) ?? ""
        ddtags = to_string(.ddtags) ?? ""
%{ if bearer_needs_combined ~}
        msg = if is_string(.message) { string!(.message) } else { "" }
        combined = msg + " " + ddtags
%{ endif ~}
        target_services = ${bearer_token_services_arr}
        should_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${bearer_token_exclusion_condition}
        if should_redact && is_string(.message) {
          .message = replace(string!(.message), bearer_re, "${bearer_token_redaction_replacement}")
        }
%{ endif ~}
%{ if bearer_do_tag ~}
    tag_bearer_tokens_logs:
      type: remap
      inputs:
        - ${bearer_token_logs_input}
      source: |
        bearer_re = r'(?i)Bearer\s+[A-Za-z0-9+/._~-]+=*'
        svc = to_string(.service) ?? ""
        ddtags = to_string(.ddtags) ?? ""
%{ if bearer_needs_combined ~}
        msg = if is_string(.message) { string!(.message) } else { "" }
        combined = msg + " " + ddtags
%{ endif ~}
        target_services = ${bearer_token_services_arr}
        would_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${bearer_token_exclusion_condition}
        if would_redact && is_string(.message) && match(string!(.message), bearer_re) {
          .ddtags = if is_string(.ddtags) { string!(.ddtags) + ",vector_redaction:true" } else { "vector_redaction:true" }
        }
%{ endif ~}
%{ if aws_key_do_redact ~}
    redact_aws_access_keys_logs:
      type: remap
      inputs:
        - ${aws_key_logs_input}
      source: |
        aws_key_re = r'(AKIA|ASIA|AROA|AIPA|ANPA|ANVA|APKA)[A-Z0-9]{16}'
        svc = to_string(.service) ?? ""
        ddtags = to_string(.ddtags) ?? ""
%{ if aws_key_needs_combined ~}
        msg = if is_string(.message) { string!(.message) } else { "" }
        combined = msg + " " + ddtags
%{ endif ~}
        target_services = ${aws_key_services_arr}
        attr_excluded = ${aws_key_attr_exclusion_condition}
        should_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${aws_key_string_exclusion_condition} && !attr_excluded
        if should_redact && is_string(.message) {
          .message = replace(string!(.message), aws_key_re, "${aws_access_key_redaction_replacement}")
        }
%{ endif ~}
%{ if aws_key_do_tag ~}
    tag_aws_access_keys_logs:
      type: remap
      inputs:
        - ${aws_key_logs_input}
      source: |
        aws_key_re = r'(AKIA|ASIA|AROA|AIPA|ANPA|ANVA|APKA)[A-Z0-9]{16}'
        svc = to_string(.service) ?? ""
        ddtags = to_string(.ddtags) ?? ""
%{ if aws_key_needs_combined ~}
        msg = if is_string(.message) { string!(.message) } else { "" }
        combined = msg + " " + ddtags
%{ endif ~}
        target_services = ${aws_key_services_arr}
        attr_excluded = ${aws_key_attr_exclusion_condition}
        would_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${aws_key_string_exclusion_condition} && !attr_excluded
        if would_redact && is_string(.message) && match(string!(.message), aws_key_re) {
          .ddtags = if is_string(.ddtags) { string!(.ddtags) + ",vector_redaction:true" } else { "vector_redaction:true" }
        }
%{ endif ~}
%{ if jwt_do_redact ~}
    redact_jwts_logs:
      type: remap
      inputs:
        - ${jwt_logs_input}
      source: |
        jwt_re = r'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+'
        svc = to_string(.service) ?? ""
        ddtags = to_string(.ddtags) ?? ""
%{ if jwt_needs_combined ~}
        msg = if is_string(.message) { string!(.message) } else { "" }
        combined = msg + " " + ddtags
%{ endif ~}
        target_services = ${jwt_services_arr}
        should_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${jwt_exclusion_condition}
        if should_redact && is_string(.message) {
          .message = replace(string!(.message), jwt_re, "${jwt_redaction_replacement}")
        }
%{ endif ~}
%{ if jwt_do_tag ~}
    tag_jwts_logs:
      type: remap
      inputs:
        - ${jwt_logs_input}
      source: |
        jwt_re = r'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+'
        svc = to_string(.service) ?? ""
        ddtags = to_string(.ddtags) ?? ""
%{ if jwt_needs_combined ~}
        msg = if is_string(.message) { string!(.message) } else { "" }
        combined = msg + " " + ddtags
%{ endif ~}
        target_services = ${jwt_services_arr}
        would_redact = contains(ddtags, "env:prod") && includes(target_services, svc) && ${jwt_exclusion_condition}
        if would_redact && is_string(.message) && match(string!(.message), jwt_re) {
          .ddtags = if is_string(.ddtags) { string!(.ddtags) + ",vector_redaction:true" } else { "vector_redaction:true" }
        }
%{ endif ~}
  sinks:
    datadog_logs:
      type: datadog_logs
      inputs:
        - ${logs_sink_input}
      default_api_key: $${DATADOG_API_KEY}
      site: ${datadog_site}

-    %{ if limits_cpu != null ~}cpu: "${limits_cpu}"%{ endif ~}

-    %{ if limits_memory != null ~}memory: ${limits_memory}%{ endif ~}

+%{ if limits_cpu != null }    cpu: "${limits_cpu}"

+%{ endif }%{ if limits_memory != null }    memory: ${limits_memory}


Now let me verify the fix looks correct in context:


24	resources:
25	  requests:
26	    cpu: ${requests_cpu}
27	    memory: ${requests_memory}
28	%{ if limits_cpu != null || limits_memory != null ~}
29	  limits:
30	%{ if limits_cpu != null }    cpu: "${limits_cpu}"
31	%{ endif }%{ if limits_memory != null }    memory: ${limits_memory}
32	%{ endif ~}
33	%{ endif ~}
34	podDisruptionBudget:
35	  enabled: true
36	  minAvailable: 2
37
38	topologySpreadConstraints:

resources:
  requests:
    cpu: ${requests_cpu}
    memory: ${requests_memory}
%{ if limits_cpu != null || limits_memory != null ~}
  limits:
%{ if limits_cpu != null }    cpu: "${limits_cpu}"
%{ endif }%{ if limits_memory != null }    memory: ${limits_memory}
%{ endif ~}
%{ endif ~}
podDisruptionBudget:
  enabled: true
  minAvailable: 2

topologySpreadConstraints:


The fix is correct. Here's what changed and why it works:

**Before (buggy):**
```
    %{ if limits_cpu != null ~}cpu: "${limits_cpu}"%{ endif ~}
    %{ if limits_memory != null ~}memory: ${limits_memory}%{ endif ~}
```
The `%{ endif ~}` after the cpu value strip-consumed the `\n    ` (newline + 4-space indent) that preceded the memory line, merging them: `cpu: "2"    memory: 1Gi`.

**After (fixed):**
```
%{ if limits_cpu != null }    cpu: "${limits_cpu}"
%{ endif }%{ if limits_memory != null }    memory: ${limits_memory}
%{ endif ~}
```

Now each field's content — including its trailing `\n` — is inside the if-block. The `%{ endif }%{ if }` tags sit on the same line with no strip markers between them, so no whitespace is consumed. The only `~` is on the memory `%{ endif ~}` (line 32), which strips the blank line between the limits section and `podDisruptionBudget:`. All four cases render correctly:

| Condition | Output |
|-----------|--------|
| both set | `  limits:\n    cpu: "2"\n    memory: 1Gi\n` |
| cpu only | `  limits:\n    cpu: "2"\n` |
| memory only | `  limits:\n    memory: 1Gi\n` |
| neither | *(limits block omitted)* |


afplay ~/.claude/sounds/mario/stop/stomp.wav 2>/dev/null || true


Fixed a template bug where cpu and memory resource limits rendered on one line due to strip markers consuming the separator. The snapshot at tests/snapshots/values-base.yaml already shows the correct expected output, so no further changes are needed. (disable recaps in /config)


can we update the resource variables to be an object with the requests and limits instead of two separate variables like the following pattern? variable "argocd_application_controller_resources" {
  description = "Resource requests and limits for the ArgoCD application controller"
  type = object({
    limits = object({
      cpu    = optional(string, null)
      memory = optional(string, "")
    })
    requests = object({
      cpu    = optional(string, "")
      memory = optional(string, "")
    })
  })
  default = {
    limits = {
      cpu    = null
      memory = "16G"
    }
    requests = {
      cpu    = "2"
      memory = "8G"
    }
  }
}


Let me read the current variables and usages before making changes.


1	# ---------------------------------------------------------------------------------------------------------------------
2	# REQUIRED PARAMETERS
3	# These variables must be set.
4	# ---------------------------------------------------------------------------------------------------------------------
5
6	variable "chart_version" {
7	  description = "Vector helm chart version pin"
8	  type        = string
9	}
10
11	variable "image_tag_version" {
12	  description = "Vector image tag"
13	  type        = string
14	}
15
16	# ---------------------------------------------------------------------------------------------------------------------
17	# OPTIONAL PARAMETERS
18	# These variables are configurable, but have reasonable defaults set.
19	# ---------------------------------------------------------------------------------------------------------------------
20	variable "datadog_site" {
21	  description = "Datadog site (e.g. datadoghq.com, us3.datadoghq.com)"
22	  type        = string
23	  default     = "datadoghq.com"
24	}
25
26	variable "image" {
27	  description = "Vector image source"
28	  type        = string
29	  default     = "registry.ic-ops.net/cache-docker-hub/timberio/vector"
30	}
31
32	variable "replicas" {
33	  description = "Number of Vector aggregator replicas"
34	  type        = number
35	  default     = 3
36	}
37
38	variable "audit_drop_patterns" {
39	  description = "Regex patterns; matching log message bodies are dropped"
40	  type        = list(string)
41	  default = [
42	    "Audit\\s-\\s.+",
43	    "\"level\":\"audit\"",
44	  ]
45	}
46
47	variable "datadog_api_key_vault_path" {
48	  description = "Vault path for the Datadog API key, consumed by ExternalSecret"
49	  type        = string
50	  default     = "secret/data/saas/infrastructure/datadog-api-key"
51	}
52
53	variable "external_secrets_cluster_store_name" {
54	  description = "Name of the ClusterSecretStore created by external-secrets module"
55	  type        = string
56	  default     = "vault"
57	}
58
59	variable "email_redaction_enabled" {
60	  description = "Toggle the email-redact VRL transform across all signals"
61	  type        = bool
62	  default     = true
63	}
64
65	variable "email_redaction_dry_run" {
66	  description = "When true, tag logs that would have been redacted with vector_redaction:true instead of replacing. Takes priority over email_redaction_enabled."
67	  type        = bool
68	  default     = false
69	}
70
71	variable "email_redaction_replacement" {
72	  description = "String that replaces matched email addresses"
73	  type        = string
74	  default     = "[REDACTED-EMAIL]"
75	}
76
77	variable "additional_config" {
78	  description = "Escape hatch — overwrites helm customConfig wholesale (mirrors otel_collector_additional_config semantics)"
79	  type        = any
80	  default     = null
81	}
82
83	variable "resources_requests" {
84	  description = "Pod resource requests"
85	  type        = map(string)
86	  default = {
87	    cpu    = "200m"
88	    memory = "256Mi"
89	  }
90	}
91
92	variable "resources_limits" {
93	  description = "Pod resource limits"
94	  type        = map(string)
95	  default = {
96	    cpu    = "2"
97	    memory = "1Gi"
98	  }
99	}
100
101	variable "namespace" {
102	  description = "Kubernetes namespace for Vector"
103	  type        = string
104	  default     = "vector"
105	}
106
107	variable "service_monitor_enabled" {
108	  description = "Create a ServiceMonitor for Vector self-metrics. Requires kube-prometheus-stack CRDs in cluster."
109	  type        = bool
110	  default     = false
111	}
112
113	variable "bearer_token_redaction_enabled" {
114	  description = "Toggle bearer token redact VRL transform on logs"
115	  type        = bool
116	  default     = false
117	}
118
119	variable "bearer_token_redaction_dry_run" {
120	  description = "When true, tag logs that would have been redacted with vector_redaction:true instead of replacing. Takes priority over bearer_token_redaction_enabled."
121	  type        = bool
122	  default     = false
123	}
124
125	variable "aws_access_key_redaction_enabled" {
126	  description = "Toggle AWS Access Key ID redact VRL transform on logs"
127	  type        = bool
128	  default     = false
129	}
130
131	variable "aws_access_key_redaction_dry_run" {
132	  description = "When true, tag logs that would have been redacted with vector_redaction:true instead of replacing. Takes priority over aws_access_key_redaction_enabled."
133	  type        = bool
134	  default     = false
135	}
136
137	variable "aws_access_key_redaction_replacement" {
138	  description = "String that replaces matched AWS Access Key IDs"
139	  type        = string
140	  default     = "[REDACTED-AWS-ACCESS-KEY]"
141	}
142
143	variable "aws_access_key_redaction_services" {
144	  description = "Service names (.service field on DD agent logs) to apply AWS Access Key ID redaction to"
145	  type        = list(string)
146	  default     = ["marketplace-api"]
147	}
148
149	variable "aws_access_key_redaction_exclusion_patterns" {
150	  description = "Skip AWS Access Key ID redaction for any log whose message or ddtags contains one of these strings"
151	  type        = list(string)
152	  default     = []
153	}
154
155	variable "aws_access_key_redaction_exclusion_attributes" {
156	  description = "Skip AWS Access Key ID redaction for any log where the named attribute equals the given value (keys are attribute paths, values are string values to match)"
157	  type        = map(string)
158	  default = {
159	    "req.headers.x-synthetic-test" = "true"
160	  }
161	}
162
163	variable "jwt_redaction_enabled" {
164	  description = "Toggle JSON Web Token redact VRL transform on logs"
165	  type        = bool
166	  default     = false
167	}
168
169	variable "jwt_redaction_dry_run" {
170	  description = "When true, tag logs that would have been redacted with vector_redaction:true instead of replacing. Takes priority over jwt_redaction_enabled."
171	  type        = bool
172	  default     = false
173	}
174
175	variable "jwt_redaction_replacement" {
176	  description = "String that replaces matched JSON Web Tokens"
177	  type        = string
178	  default     = "[REDACTED-JWT]"
179	}
180
181	variable "jwt_redaction_services" {
182	  description = "Service names (.service field on DD agent logs) to apply JWT redaction to"
183	  type        = list(string)
184	  default     = ["service", "secure-metadata-database"]
185	}
186
187	variable "jwt_redaction_exclusion_patterns" {
188	  description = "Skip JWT redaction for any log whose message or ddtags contains one of these strings"
189	  type        = list(string)
190	  default = [
191	    "internal-webhook",
192	    "starting crawl",
193	    "Databricks automatic subscription",
194	    "audit/enrich",
195	    "Background Job",
196	    "community-organization-id:immuta",
197	    "pg - pool.connect",
198	  ]
199	}
200
201	variable "bearer_token_redaction_replacement" {
202	  description = "String that replaces matched bearer tokens (replaces the full 'Bearer <token>' match)"
203	  type        = string
204	  default     = "Bearer [REDACTED-BEARER-TOKEN]"
205	}
206
207	variable "bearer_token_redaction_services" {
208	  description = "Service names (.service field on DD agent logs) to apply bearer token redaction to"
209	  type        = list(string)
210	  default     = ["service", "secure-metadata-database"]
211	}
212
213	variable "bearer_token_redaction_exclusion_patterns" {
214	  description = "Skip bearer token redaction for any log whose message or ddtags contains one of these strings"
215	  type        = list(string)
216	  default = [
217	    "internal-webhook",
218	    "starting crawl",
219	    "Databricks automatic subscription",
220	    "audit/enrich",
221	    "Background Job",
222	    "community-organization-id:immuta",
223	    "pg - pool.connect",
224	  ]
225	}
226

# ---------------------------------------------------------------------------------------------------------------------
# REQUIRED PARAMETERS
# These variables must be set.
# ---------------------------------------------------------------------------------------------------------------------

variable "chart_version" {
  description = "Vector helm chart version pin"
  type        = string
}

variable "image_tag_version" {
  description = "Vector image tag"
  type        = string
}

# ---------------------------------------------------------------------------------------------------------------------
# OPTIONAL PARAMETERS
# These variables are configurable, but have reasonable defaults set.
# ---------------------------------------------------------------------------------------------------------------------
variable "datadog_site" {
  description = "Datadog site (e.g. datadoghq.com, us3.datadoghq.com)"
  type        = string
  default     = "datadoghq.com"
}

variable "image" {
  description = "Vector image source"
  type        = string
  default     = "registry.ic-ops.net/cache-docker-hub/timberio/vector"
}

variable "replicas" {
  description = "Number of Vector aggregator replicas"
  type        = number
  default     = 3
}

variable "audit_drop_patterns" {
  description = "Regex patterns; matching log message bodies are dropped"
  type        = list(string)
  default = [
    "Audit\\s-\\s.+",
    "\"level\":\"audit\"",
  ]
}

variable "datadog_api_key_vault_path" {
  description = "Vault path for the Datadog API key, consumed by ExternalSecret"
  type        = string
  default     = "secret/data/saas/infrastructure/datadog-api-key"
}

variable "external_secrets_cluster_store_name" {
  description = "Name of the ClusterSecretStore created by external-secrets module"
  type        = string
  default     = "vault"
}

variable "email_redaction_enabled" {
  description = "Toggle the email-redact VRL transform across all signals"
  type        = bool
  default     = true
}

variable "email_redaction_dry_run" {
  description = "When true, tag logs that would have been redacted with vector_redaction:true instead of replacing. Takes priority over email_redaction_enabled."
  type        = bool
  default     = false
}

variable "email_redaction_replacement" {
  description = "String that replaces matched email addresses"
  type        = string
  default     = "[REDACTED-EMAIL]"
}

variable "additional_config" {
  description = "Escape hatch — overwrites helm customConfig wholesale (mirrors otel_collector_additional_config semantics)"
  type        = any
  default     = null
}

variable "resources_requests" {
  description = "Pod resource requests"
  type        = map(string)
  default = {
    cpu    = "200m"
    memory = "256Mi"
  }
}

variable "resources_limits" {
  description = "Pod resource limits"
  type        = map(string)
  default = {
    cpu    = "2"
    memory = "1Gi"
  }
}

variable "namespace" {
  description = "Kubernetes namespace for Vector"
  type        = string
  default     = "vector"
}

variable "service_monitor_enabled" {
  description = "Create a ServiceMonitor for Vector self-metrics. Requires kube-prometheus-stack CRDs in cluster."
  type        = bool
  default     = false
}

variable "bearer_token_redaction_enabled" {
  description = "Toggle bearer token redact VRL transform on logs"
  type        = bool
  default     = false
}

variable "bearer_token_redaction_dry_run" {
  description = "When true, tag logs that would have been redacted with vector_redaction:true instead of replacing. Takes priority over bearer_token_redaction_enabled."
  type        = bool
  default     = false
}

variable "aws_access_key_redaction_enabled" {
  description = "Toggle AWS Access Key ID redact VRL transform on logs"
  type        = bool
  default     = false
}

variable "aws_access_key_redaction_dry_run" {
  description = "When true, tag logs that would have been redacted with vector_redaction:true instead of replacing. Takes priority over aws_access_key_redaction_enabled."
  type        = bool
  default     = false
}

variable "aws_access_key_redaction_replacement" {
  description = "String that replaces matched AWS Access Key IDs"
  type        = string
  default     = "[REDACTED-AWS-ACCESS-KEY]"
}

variable "aws_access_key_redaction_services" {
  description = "Service names (.service field on DD agent logs) to apply AWS Access Key ID redaction to"
  type        = list(string)
  default     = ["marketplace-api"]
}

variable "aws_access_key_redaction_exclusion_patterns" {
  description = "Skip AWS Access Key ID redaction for any log whose message or ddtags contains one of these strings"
  type        = list(string)
  default     = []
}

variable "aws_access_key_redaction_exclusion_attributes" {
  description = "Skip AWS Access Key ID redaction for any log where the named attribute equals the given value (keys are attribute paths, values are string values to match)"
  type        = map(string)
  default = {
    "req.headers.x-synthetic-test" = "true"
  }
}

variable "jwt_redaction_enabled" {
  description = "Toggle JSON Web Token redact VRL transform on logs"
  type        = bool
  default     = false
}

variable "jwt_redaction_dry_run" {
  description = "When true, tag logs that would have been redacted with vector_redaction:true instead of replacing. Takes priority over jwt_redaction_enabled."
  type        = bool
  default     = false
}

variable "jwt_redaction_replacement" {
  description = "String that replaces matched JSON Web Tokens"
  type        = string
  default     = "[REDACTED-JWT]"
}

variable "jwt_redaction_services" {
  description = "Service names (.service field on DD agent logs) to apply JWT redaction to"
  type        = list(string)
  default     = ["service", "secure-metadata-database"]
}

variable "jwt_redaction_exclusion_patterns" {
  description = "Skip JWT redaction for any log whose message or ddtags contains one of these strings"
  type        = list(string)
  default = [
    "internal-webhook",
    "starting crawl",
    "Databricks automatic subscription",
    "audit/enrich",
    "Background Job",
    "community-organization-id:immuta",
    "pg - pool.connect",
  ]
}

variable "bearer_token_redaction_replacement" {
  description = "String that replaces matched bearer tokens (replaces the full 'Bearer <token>' match)"
  type        = string
  default     = "Bearer [REDACTED-BEARER-TOKEN]"
}

variable "bearer_token_redaction_services" {
  description = "Service names (.service field on DD agent logs) to apply bearer token redaction to"
  type        = list(string)
  default     = ["service", "secure-metadata-database"]
}

variable "bearer_token_redaction_exclusion_patterns" {
  description = "Skip bearer token redaction for any log whose message or ddtags contains one of these strings"
  type        = list(string)
  default = [
    "internal-webhook",
    "starting crawl",
    "Databricks automatic subscription",
    "audit/enrich",
    "Background Job",
    "community-organization-id:immuta",
    "pg - pool.connect",
  ]
}


can we update the resource variables to be an object with the requests and limits instead of two separate variables like the following pattern? variable "argocd_application_controller_resources" {   d…


Now let me check the example and the caller in eks-cluster-applications:


1	module "vector" {
2	  source = "../.."
3
4	  chart_version              = "0.36.0"
5	  image_tag_version          = "0.40.0-distroless-libc"
6	  datadog_site               = "datadoghq.com"
7	  datadog_api_key_vault_path = "secret/data/datadog/api-key"
8
9	  external_secrets_cluster_store_name = "vault-cluster-store"
10	}
11

module "vector" {
  source = "../.."

  chart_version              = "0.36.0"
  image_tag_version          = "0.40.0-distroless-libc"
  datadog_site               = "datadoghq.com"
  datadog_api_key_vault_path = "secret/data/datadog/api-key"

  external_secrets_cluster_store_name = "vault-cluster-store"
}


Bash

grep -n "resources_requests\|resources_limits" /Users/steve.briggs/projects/operations/terraform/iac-library/aws/compute/eks-cluster-applications/*.tf 2>/dev/null | head -30

Find callers of the old resource variables


/Users/steve.briggs/projects/operations/terraform/iac-library/aws/compute/eks-cluster-applications/vector.tf:28:  resources_requests          = var.vector_resource_requests
/Users/steve.briggs/projects/operations/terraform/iac-library/aws/compute/eks-cluster-applications/vector.tf:29:  resources_limits            = var.vector_resource_limits