Skip to content

Commit 828d7ca

Browse files
committed
migrate chart to use CWA json
1 parent 222cc32 commit 828d7ca

12 files changed

Lines changed: 139 additions & 220 deletions

File tree

charts/amazon-cloudwatch-observability/templates/_helpers.tpl

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,17 @@ Logic:
130130
{{- $needsLogs = true -}}
131131
{{- $_ := set $metricsCollected "kubernetes" (dict "enhanced_container_insights" true) -}}
132132
{{- end -}}
133+
{{/* OTEL CI: emit JSON config; the agent generates the pipeline (node=targetAgent, cluster=clusterScraperAgent). */}}
134+
{{- if $ctx.Values.otelContainerInsights.enabled -}}
135+
{{- $interval := atoi (trimSuffix "s" $ctx.Values.otelContainerInsights.metricResolution) -}}
136+
{{- if eq $ctx.Values.otelContainerInsights.targetAgent $agentName -}}
137+
{{- $ci := dict "collection_interval" $interval "role" "node" "logs" (dict "enabled" $ctx.Values.otelContainerInsights.logs.enabled) -}}
138+
{{- $_ := set $config "opentelemetry" (dict "cluster_name" $ctx.Values.clusterName "collect" (dict "container_insights" $ci)) -}}
139+
{{- else if eq $ctx.Values.otelContainerInsights.clusterScraperAgent $agentName -}}
140+
{{- $ci := dict "collection_interval" $interval "role" "cluster" -}}
141+
{{- $_ := set $config "opentelemetry" (dict "cluster_name" $ctx.Values.clusterName "collect" (dict "container_insights" $ci)) -}}
142+
{{- end -}}
143+
{{- end -}}
133144
{{- if $needsLogs -}}
134145
{{- $_ := set $config "logs" (dict "metrics_collected" $metricsCollected) -}}
135146
{{- end -}}
@@ -180,15 +191,8 @@ Logic:
180191
{{- $agentName := .agentName -}}
181192
{{- $ctx := .context -}}
182193
{{- include "cloudwatch-agent.validate-flags" $ctx -}}
183-
{{- if not $ctx.Values.otelContainerInsights.enabled -}}
194+
{{/* OTEL CI now comes from the JSON config; the agent generates the pipeline, so no otelConfig here. */}}
184195
{}
185-
{{- else if eq $ctx.Values.otelContainerInsights.targetAgent $agentName -}}
186-
{{- include "otel-container-insights.config" $ctx -}}
187-
{{- else if eq $ctx.Values.otelContainerInsights.clusterScraperAgent $agentName -}}
188-
{{- include "otel-container-insights-cluster-scraper.config" $ctx -}}
189-
{{- else -}}
190-
{}
191-
{{- end -}}
192196
{{- end -}}
193197

194198
{{/*

charts/amazon-cloudwatch-observability/tests/flag_matrix.sh

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ run_case() {
8484
if [[ -n "$must_have" ]]; then
8585
IFS=',' read -ra fragments <<< "$must_have"
8686
for f in "${fragments[@]}"; do
87-
if ! grep -q "$f" <<< "$output"; then
87+
if ! grep -qF -- "$f" <<< "$output"; then
8888
echo -e " ${R}FAIL${N}: missing required fragment: $f"
8989
local_fail=1
9090
fi
@@ -94,7 +94,7 @@ run_case() {
9494
if [[ -n "$must_not" ]]; then
9595
IFS=',' read -ra fragments <<< "$must_not"
9696
for f in "${fragments[@]}"; do
97-
if grep -q "$f" <<< "$output"; then
97+
if grep -qF -- "$f" <<< "$output"; then
9898
echo -e " ${R}FAIL${N}: forbidden fragment present: $f"
9999
local_fail=1
100100
fi
@@ -109,22 +109,23 @@ run_case() {
109109
fi
110110
}
111111

112-
# Fragment shortcuts used across states.
113-
METRICS_EXPORTER="otlphttp/cw_k8s_ci_v0_metrics_dest"
114-
METRICS_SIGV4="sigv4auth/cw_k8s_ci_v0_metrics_dest"
115-
LOG_EXPORTER_APP="otlphttp/cw_k8s_ci_v0_app_logs_dest"
116-
LOG_EXPORTER_NODE="otlphttp/cw_k8s_ci_v0_node_logs_dest"
117-
LOG_SIGV4="sigv4auth/cw_k8s_ci_v0_logs_dest"
118-
LOG_PIPELINE_APP="logs/cw_k8s_ci_v0_app"
119-
FILELOG_APP="filelog/cw_k8s_ci_v0_app"
112+
# OTEL CI is delivered via the agent's JSON config
113+
# (opentelemetry.collect.container_insights); the agent generates the OTEL
114+
# pipeline at runtime, so we assert on the escaped JSON in the CR `config:`
115+
# field rather than on rendered collector fragments.
116+
OTEL_CI='collect\":{\"container_insights'
117+
OTEL_CI_NODE='\"role\":\"node\"'
118+
OTEL_CI_CLUSTER='\"role\":\"cluster\"'
119+
OTEL_CI_LOGS_ON='\"logs\":{\"enabled\":true}'
120+
OTEL_CI_LOGS_OFF='\"logs\":{\"enabled\":false}'
120121
# aws-for-fluent-bit is the container image string — unique to the FluentBit
121122
# DaemonSet. Using this instead of bare "fluent-bit" avoids false matches in
122123
# OTEL config paths like /var/log/containers/fluent-bit* (which exist in the
123124
# filelog exclude list regardless of the FB DaemonSet flag).
124125
FLUENT_BIT_IMAGE="aws-for-fluent-bit"
125126

126-
# All OTEL log pipeline fragments (app + host).
127-
ALL_LOG_FRAGMENTS="$LOG_EXPORTER_APP,$LOG_EXPORTER_NODE,$LOG_SIGV4,$LOG_PIPELINE_APP,$FILELOG_APP"
127+
# Both node (daemonset) and cluster (scraper) CI config present.
128+
OTEL_CI_BOTH_ROLES="$OTEL_CI,$OTEL_CI_NODE,$OTEL_CI_CLUSTER"
128129

129130
# ──────────────────────────────────────────────────────────────────────────
130131
# Run all 8 combinations.
@@ -136,43 +137,43 @@ echo "Chart: $CHART_DIR"
136137
# State #1: all false — no monitoring.
137138
run_case 1 false false false "ok" \
138139
"No monitoring — all flags off" \
139-
"" "$METRICS_EXPORTER,$FLUENT_BIT_IMAGE"
140+
"" "$OTEL_CI,$FLUENT_BIT_IMAGE"
140141

141142
# State #2: FluentBit only (pure v1 legacy).
142143
run_case 2 false false true "ok" \
143144
"FluentBit legacy only" \
144-
"$FLUENT_BIT_IMAGE" "$METRICS_EXPORTER,$ALL_LOG_FRAGMENTS"
145+
"$FLUENT_BIT_IMAGE" "$OTEL_CI"
145146

146147
# State #3: logs=true without enabled — silently ignored.
147148
run_case 3 false true false "ok" \
148149
"logs=true without enabled — no OTEL output" \
149-
"" "$METRICS_EXPORTER,$ALL_LOG_FRAGMENTS,$FLUENT_BIT_IMAGE"
150+
"" "$OTEL_CI,$FLUENT_BIT_IMAGE"
150151

151152
# State #4: same as #3 with FluentBit.
152153
run_case 4 false true true "ok" \
153154
"logs=true without enabled + FluentBit — only FluentBit" \
154-
"$FLUENT_BIT_IMAGE" "$METRICS_EXPORTER,$ALL_LOG_FRAGMENTS"
155+
"$FLUENT_BIT_IMAGE" "$OTEL_CI"
155156

156-
# State #5: OTEL metrics only.
157+
# State #5: OTEL metrics only (logs disabled).
157158
run_case 5 true false false "ok" \
158159
"OTEL metrics only, no logs" \
159-
"$METRICS_EXPORTER,$METRICS_SIGV4" "$ALL_LOG_FRAGMENTS,$FLUENT_BIT_IMAGE"
160+
"$OTEL_CI_BOTH_ROLES,$OTEL_CI_LOGS_OFF" "$OTEL_CI_LOGS_ON,$FLUENT_BIT_IMAGE"
160161

161162
# State #6: hybrid — OTEL metrics + FluentBit logs.
162163
run_case 6 true false true "ok" \
163164
"Hybrid — OTEL metrics + FluentBit logs" \
164-
"$METRICS_EXPORTER,$METRICS_SIGV4,$FLUENT_BIT_IMAGE" "$ALL_LOG_FRAGMENTS"
165+
"$OTEL_CI_BOTH_ROLES,$OTEL_CI_LOGS_OFF,$FLUENT_BIT_IMAGE" "$OTEL_CI_LOGS_ON"
165166

166167
# State #7: full OTEL (metrics + logs, no FluentBit).
167168
run_case 7 true true false "ok" \
168169
"Full OTEL (metrics + logs)" \
169-
"$METRICS_EXPORTER,$METRICS_SIGV4,$LOG_EXPORTER_APP,$LOG_EXPORTER_NODE,$LOG_SIGV4,$FILELOG_APP" \
170+
"$OTEL_CI_BOTH_ROLES,$OTEL_CI_LOGS_ON" \
170171
"$FLUENT_BIT_IMAGE"
171172

172173
# State #8: dual-publish (migration window — OTEL logs + FluentBit both active).
173174
run_case 8 true true true "ok" \
174175
"Dual-publish — OTEL logs + FluentBit both active" \
175-
"$METRICS_EXPORTER,$LOG_EXPORTER_APP,$LOG_EXPORTER_NODE,$FILELOG_APP,$FLUENT_BIT_IMAGE" \
176+
"$OTEL_CI_BOTH_ROLES,$OTEL_CI_LOGS_ON,$FLUENT_BIT_IMAGE" \
176177
""
177178

178179
# ──────────────────────────────────────────────────────────────────────────

charts/amazon-cloudwatch-observability/values.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,7 @@ agent:
726726
replicas: 1 # The total number non-terminated pods targeted by this AmazonCloudWatchAgent's deployment or statefulSet.
727727
image:
728728
repository: cloudwatch-agent
729-
tag: 1.300070.0b1586
729+
tag: 1.300071.0b1720
730730
repositoryDomainMap:
731731
public: public.ecr.aws/cloudwatch-agent
732732
cn-north-1: 934860584483.dkr.ecr.cn-north-1.amazonaws.com.cn
@@ -1161,8 +1161,10 @@ otelContainerInsights:
11611161
enabled: false
11621162
## OTEL-native CI log pipelines (application, host).
11631163
## Only takes effect when otelContainerInsights.enabled is true.
1164+
## OTEL CI logs turned off for now due to agent side fix
1165+
## logs are handled by FluentBit (containerLogs).
11641166
logs:
1165-
enabled: true
1167+
enabled: false
11661168
## The agent in the agents array that receives node-level OTEL Container Insights config.
11671169
targetAgent: "cloudwatch-agent"
11681170
## The agent in the agents array that receives cluster-level OTEL Container Insights config (apiserver, kube-state-metrics scraping).

integration-tests/amazon-cloudwatch-observability/validations/minikube/common.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,34 @@ func GetOperatorAutoInstrumentationConfig(t *testing.T) map[string]interface{} {
112112
t.Logf("auto-instrumentation-config: %s", autoInstrumentationArg)
113113
return config
114114
}
115+
116+
// OTEL CI assertions — CI is delivered via spec.config JSON (operator generates
117+
// the pipeline), so tests assert on spec.config, not spec.otelConfig.
118+
119+
// otelCIMarker matches the V2 container_insights block.
120+
const otelCIMarker = `"container_insights":{`
121+
122+
// AssertOtelContainerInsights asserts OTEL CI is enabled with the given role (node|cluster).
123+
func AssertOtelContainerInsights(t *testing.T, config, role string) {
124+
assert.Contains(t, config, otelCIMarker,
125+
"config must enable opentelemetry.collect.container_insights")
126+
assert.Contains(t, config, `"role":"`+role+`"`,
127+
"container_insights role must be %q", role)
128+
}
129+
130+
// AssertOtelCILogsEnabled asserts the container_insights logs toggle.
131+
func AssertOtelCILogsEnabled(t *testing.T, config string, enabled bool) {
132+
if enabled {
133+
assert.Contains(t, config, `"logs":{"enabled":true}`,
134+
"container_insights logs must be enabled")
135+
} else {
136+
assert.Contains(t, config, `"logs":{"enabled":false}`,
137+
"container_insights logs must be disabled")
138+
}
139+
}
140+
141+
// AssertNoOtelContainerInsights asserts OTEL CI is not configured.
142+
func AssertNoOtelContainerInsights(t *testing.T, config string) {
143+
assert.NotContains(t, config, otelCIMarker,
144+
"config must not enable opentelemetry.collect.container_insights")
145+
}

integration-tests/amazon-cloudwatch-observability/validations/minikube/scenarios/feature_targeted_default_test.go

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func validateClusterScraperCRExists(t *testing.T, agentMap map[string]unstructur
9595
// - cloudwatch-agent gets node-level pipelines (cadvisor, kubeletstats, node-exporter receivers)
9696
// - cloudwatch-agent-cluster-scraper gets cluster-level pipelines (apiserver, kube-state-metrics receivers)
9797
func validateOTELConfigRouting(t *testing.T, agentMap map[string]unstructured.Unstructured) {
98-
// Validate cloudwatch-agent has node-level OTEL config
98+
// Validate cloudwatch-agent has node-level OTEL CI config
9999
t.Run("CloudWatchAgentNodeLevel", func(t *testing.T) {
100100
agent, exists := agentMap["cloudwatch-agent"]
101101
if !assert.True(t, exists, "cloudwatch-agent CR should exist") {
@@ -107,24 +107,18 @@ func validateOTELConfigRouting(t *testing.T, agentMap map[string]unstructured.Un
107107
return
108108
}
109109

110-
otelConfig, ok := spec["otelConfig"].(string)
111-
if !assert.True(t, ok, "otelConfig should be a string") {
110+
config, ok := spec["config"].(string)
111+
if !assert.True(t, ok, "config should be a string") {
112112
return
113113
}
114-
assert.NotEmpty(t, otelConfig, "otelConfig should not be empty")
115114

116-
// Node-level config should contain node-exporter, cadvisor, kubeletstats receivers
117-
assert.True(t, strings.Contains(otelConfig, "kubeletstats"),
118-
"cloudwatch-agent otelConfig should contain kubeletstats receiver (node-level)")
119-
120-
// Node-level config should NOT contain cluster-level receivers
121-
assert.False(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_apiserver"),
122-
"cloudwatch-agent otelConfig should NOT contain apiserver receiver (cluster-level)")
123-
assert.False(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_kube_state_metrics"),
124-
"cloudwatch-agent otelConfig should NOT contain kube_state_metrics receiver (cluster-level)")
115+
// role=node; operator generates the node pipeline.
116+
minikube.AssertOtelContainerInsights(t, config, "node")
117+
assert.False(t, strings.Contains(config, `"role":"cluster"`),
118+
"cloudwatch-agent config should not request cluster role")
125119
})
126120

127-
// Validate cluster-scraper has cluster-level OTEL config
121+
// Validate cluster-scraper has cluster-level OTEL CI config
128122
t.Run("ClusterScraperClusterLevel", func(t *testing.T) {
129123
agent, exists := agentMap["cloudwatch-agent-cluster-scraper"]
130124
if !assert.True(t, exists, "cloudwatch-agent-cluster-scraper CR should exist") {
@@ -136,20 +130,14 @@ func validateOTELConfigRouting(t *testing.T, agentMap map[string]unstructured.Un
136130
return
137131
}
138132

139-
otelConfig, ok := spec["otelConfig"].(string)
140-
if !assert.True(t, ok, "otelConfig should be a string") {
133+
config, ok := spec["config"].(string)
134+
if !assert.True(t, ok, "config should be a string") {
141135
return
142136
}
143-
assert.NotEmpty(t, otelConfig, "otelConfig should not be empty")
144-
145-
// Cluster-level config should contain apiserver and kube-state-metrics receivers
146-
assert.True(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_apiserver"),
147-
"cluster-scraper otelConfig should contain apiserver receiver (cluster-level)")
148-
assert.True(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_kube_state_metrics"),
149-
"cluster-scraper otelConfig should contain kube_state_metrics receiver (cluster-level)")
150137

151-
// Cluster-level config should NOT contain node-level receivers
152-
assert.False(t, strings.Contains(otelConfig, "kubeletstats"),
153-
"cluster-scraper otelConfig should NOT contain kubeletstats receiver (node-level)")
138+
// role=cluster; operator generates the cluster pipeline.
139+
minikube.AssertOtelContainerInsights(t, config, "cluster")
140+
assert.False(t, strings.Contains(config, `"role":"node"`),
141+
"cluster-scraper config should not request node role")
154142
})
155143
}

integration-tests/amazon-cloudwatch-observability/validations/minikube/scenarios/feature_targeted_multi_agent_test.go

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -124,16 +124,15 @@ func validateCloudWatchAgentFullConfig(t *testing.T, agentMap map[string]unstruc
124124
}
125125
}
126126

127-
// Validate OTEL config has node-level pipelines
128-
otelConfig, ok := spec["otelConfig"].(string)
129-
if !assert.True(t, ok, "otelConfig should be a string") {
127+
// OTEL CI, node role.
128+
cfgStr, ok := spec["config"].(string)
129+
if !assert.True(t, ok, "config should be a string") {
130130
return
131131
}
132132

133-
assert.True(t, strings.Contains(otelConfig, "kubeletstats"),
134-
"cloudwatch-agent otelConfig should contain kubeletstats receiver (node-level)")
135-
assert.False(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_apiserver"),
136-
"cloudwatch-agent otelConfig should NOT contain apiserver receiver (cluster-level)")
133+
minikube.AssertOtelContainerInsights(t, cfgStr, "node")
134+
assert.False(t, strings.Contains(cfgStr, `"role":"cluster"`),
135+
"cloudwatch-agent config should not request cluster role")
137136
}
138137

139138
// validatePrometheusAgentMinimalConfig verifies prometheus-agent gets minimal config:
@@ -180,17 +179,8 @@ func validatePrometheusAgentMinimalConfig(t *testing.T, agentMap map[string]unst
180179
_, hasTraces := config["traces"]
181180
assert.False(t, hasTraces, "prometheus-agent config should NOT have traces section")
182181

183-
// Validate OTEL config is absent or empty (not targeted by any OTEL CI feature)
184-
otelConfig, ok := spec["otelConfig"].(string)
185-
if ok {
186-
assert.False(t, strings.Contains(otelConfig, "kubeletstats"),
187-
"prometheus-agent otelConfig should NOT contain kubeletstats receiver")
188-
assert.False(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_apiserver"),
189-
"prometheus-agent otelConfig should NOT contain apiserver receiver")
190-
assert.False(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_kube_state_metrics"),
191-
"prometheus-agent otelConfig should NOT contain kube_state_metrics receiver")
192-
}
193-
// otelConfig may be absent entirely when no OTEL CI features target this agent — that's valid
182+
// No OTEL CI for this agent.
183+
minikube.AssertNoOtelContainerInsights(t, configStr)
194184
}
195185

196186
// validateClusterScraperConfig verifies cluster-scraper gets cluster-level OTEL config
@@ -211,16 +201,13 @@ func validateClusterScraperConfig(t *testing.T, agentMap map[string]unstructured
211201
assert.True(t, ok, "hostNetwork should be a bool")
212202
assert.True(t, hostNetwork, "cluster-scraper should have hostNetwork=true")
213203

214-
// Validate OTEL config has cluster-level pipelines
215-
otelConfig, ok := spec["otelConfig"].(string)
216-
if !assert.True(t, ok, "otelConfig should be a string") {
204+
// OTEL CI, cluster role.
205+
cfgStr, ok := spec["config"].(string)
206+
if !assert.True(t, ok, "config should be a string") {
217207
return
218208
}
219209

220-
assert.True(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_apiserver"),
221-
"cluster-scraper otelConfig should contain apiserver receiver (cluster-level)")
222-
assert.True(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_kube_state_metrics"),
223-
"cluster-scraper otelConfig should contain kube_state_metrics receiver (cluster-level)")
224-
assert.False(t, strings.Contains(otelConfig, "kubeletstats"),
225-
"cluster-scraper otelConfig should NOT contain kubeletstats receiver (node-level)")
210+
minikube.AssertOtelContainerInsights(t, cfgStr, "cluster")
211+
assert.False(t, strings.Contains(cfgStr, `"role":"node"`),
212+
"cluster-scraper config should not request node role")
226213
}

integration-tests/amazon-cloudwatch-observability/validations/minikube/scenarios/feature_targeted_otlp_disabled_test.go

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package scenarios
66
import (
77
"context"
88
"encoding/json"
9-
"strings"
109
"testing"
1110

1211
"github.com/aws-observability/helm-charts/integration-tests/amazon-cloudwatch-observability/util"
@@ -96,19 +95,12 @@ func validateCloudWatchAgentNoOtelConfig(t *testing.T, agentMap map[string]unstr
9695
return
9796
}
9897

99-
otelConfig, ok := spec["otelConfig"].(string)
100-
if ok {
101-
// If otelConfig is present, it should NOT contain any CI receivers
102-
assert.False(t, strings.Contains(otelConfig, "kubeletstats"),
103-
"cloudwatch-agent otelConfig should NOT contain kubeletstats receiver when OTLP disabled")
104-
assert.False(t, strings.Contains(otelConfig, "cadvisor"),
105-
"cloudwatch-agent otelConfig should NOT contain cadvisor receiver when OTLP disabled")
106-
assert.False(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_apiserver"),
107-
"cloudwatch-agent otelConfig should NOT contain apiserver receiver when OTLP disabled")
108-
assert.False(t, strings.Contains(otelConfig, "cw_k8s_ci_v0_kube_state_metrics"),
109-
"cloudwatch-agent otelConfig should NOT contain kube_state_metrics receiver when OTLP disabled")
98+
config, ok := spec["config"].(string)
99+
if !assert.True(t, ok, "config should be a string") {
100+
return
110101
}
111-
// otelConfig may be absent entirely when no OTEL CI features target this agent — that's valid
102+
// OTEL CI disabled → no container_insights in config.
103+
minikube.AssertNoOtelContainerInsights(t, config)
112104
}
113105

114106
// validateNodeExporterNotDeployed verifies node-exporter resources are NOT present

0 commit comments

Comments
 (0)