Skip to content

Commit 807bd9c

Browse files
thirasCopilot
andauthored
fix(ansible): authenticate the OTLP gateway with its own instance ID (#62)
* fix(ansible): authenticate the OTLP gateway with its own instance ID grafana_alloy rendered otelcol.auth.basic with the PROMETHEUS instance ID: username = ga_endpoint(grafana_alloy_prom_username, 'GC_PROM_USERNAME') Grafana Cloud's OTLP gateway authenticates with the STACK instance ID instead, which is a separate number in the portal ("OTLP Endpoint" -> "Instance ID") and on many orgs differs from the Prometheus one. Where it does, every trace 401s while remote-write and Loki keep flowing — a silent half-outage that reads as "tracing is broken" rather than "auth is wrong", and one the operator had no variable to correct. Add grafana_alloy_otlp_username, plus a GC_OTLP_USERNAME env key for the host-provisioned half. Inventory wins outright; left empty, the choice is deferred to RUNTIME via coalesce(), because whether the host's env file carries GC_OTLP_USERNAME is not knowable at render time: username = coalesce(sys.env("GC_OTLP_USERNAME"), <prom literal or sys.env>) sys.env yields "" for an unset variable and coalesce returns the first non-empty argument, so an existing deployment — or an org whose IDs coincide — resolves exactly the value it resolved before. No new required key: preflight still only demands what nothing else supplies. The new variable joins both inventory gates (instance-ID charset, token-smuggling), and the ID is no longer described as one of "two". Tested with the real pinned Alloy 1.19.2 (make lint-alloy), which accepts coalesce() — a ninth render combination covers the upgrade shape (Prometheus ID in inventory, nothing for OTLP), the inventory combination now sets a third distinct ID, and the legacy combination asserts the full fallback chain. The grafana-cloud molecule scenario asserts the chain end-to-end and validation gains a negative case for a malformed OTLP instance ID. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(ansible): shape-check an optional host-provided GC_OTLP_USERNAME GC_OTLP_USERNAME stays optional — absent means traces fall back to the Prometheus instance ID as before — but a present-and-malformed value outranks that fallback via coalesce() and 401s the whole trace pipeline. Add a present-then-shaped preflight gate (same charset as the inventory instance-ID gate plus the systemd-strippable quotes), cover it with a validation-scenario rejection case, feed a well-formed value into the grafana-cloud scenario env fixture, and document the behavior. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent c0ddee3 commit 807bd9c

13 files changed

Lines changed: 229 additions & 19 deletions

File tree

‎AGENTS.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ charts/
8787
the daemon's OTLP spans. Host metrics and logs carry `job="integrations/node_exporter"`
8888
so Grafana Cloud's prebuilt Linux Server dashboards work unmodified. Only the API token
8989
is host-provisioned (`0600 /etc/grafana-alloy.env`, read via `sys.env` — Alloy has no
90-
`--config.expand-env`); the non-secret endpoints/instance IDs are inventory variables
91-
that fall back to their `GC_…` env key when empty, and preflight refuses a token in any
92-
of them. Two hardening relaxations are conditional on the signals being on
90+
`--config.expand-env`); the non-secret endpoints and the three per-service instance IDs
91+
are inventory variables that fall back to their `GC_…` env key when empty, and preflight
92+
refuses a token in any of them. Two hardening relaxations are conditional on the signals being on
9393
(`ProtectHome=read-only` for correct filesystem metrics, `SupplementaryGroups=
9494
systemd-journal adm` for journal access — without which collection is silently empty);
9595
teardown is gated on the managed-by marker in the unit, so a foreign Alloy is never

‎ansible/README.md‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,10 @@ dashboards and alert rules work unmodified.
205205

206206
Only the API token is a secret. Put the non-secret connection settings in inventory
207207
once for the fleet (`grafana_alloy_prom_url`, `_prom_username`, `_otlp_endpoint`,
208-
`_loki_url`, `_loki_username` — note the Loki instance ID differs from the Prometheus
209-
one), and provision just the token **on each target host** (it never transits this repo
210-
or the control machine):
208+
`_otlp_username`, `_loki_url`, `_loki_username` — Prometheus, OTLP and Loki each have
209+
their **own** instance ID, so copy each from the portal page that names it), and
210+
provision just the token **on each target host** (it never transits this repo or the
211+
control machine):
211212

212213
```bash
213214
umask 077
@@ -225,7 +226,10 @@ so preflight fails until you either set `grafana_alloy_loki_url` / `_loki_userna
225226
inventory, add `GC_LOKI_URL` / `GC_LOKI_USERNAME` to the env file, or set
226227
`grafana_alloy_logs_enabled: false`. The daemon's own metrics also gain an explicit
227228
`job="decdn-node"` label (previously the implicit `prometheus.scrape.decdn_node`) —
228-
`grafana_alloy_node_job: ""` restores the old identity.
229+
`grafana_alloy_node_job: ""` restores the old identity. If traces start returning 401
230+
while metrics still flow, the stack's OTLP instance ID is not its Prometheus one: set
231+
`grafana_alloy_otlp_username` (or `GC_OTLP_USERNAME`), which earlier versions had no way
232+
to express.
229233

230234
Setup, rotation, cost/cardinality guardrails, the two systemd-hardening relaxations
231235
machine monitoring requires, and rollback: see

‎ansible/galaxy/CHANGELOG.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ collection adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
3131

3232
### Fixed
3333

34+
- The OTLP gateway is authenticated with its own instance ID. The role reused
35+
the Prometheus one, so a Grafana Cloud org whose stack ID differs 401s every
36+
trace while metrics and logs keep flowing. New `grafana_alloy_otlp_username` /
37+
`GC_OTLP_USERNAME`; when both are empty the rendered config still resolves the
38+
Prometheus value, so an org where the IDs coincide is unaffected. The optional
39+
host key is shape-checked whenever it is present, so a malformed value cannot
40+
quietly outrank that fallback.
3441
- Grafana Alloy credentials now default to root-controlled
3542
`/etc/grafana-alloy.env`; preflight also rejects a non-root-owned or writable
3643
parent directory and a root service identity. Teardown requires the managed

‎ansible/molecule/grafana-cloud/prepare.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,6 @@
5656
GC_PROM_REMOTE_WRITE_URL=https://prometheus-prod-xx.molecule.invalid/api/prom/push
5757
GC_OTLP_ENDPOINT=https://otlp-gateway-prod-xx.molecule.invalid/otlp
5858
GC_PROM_USERNAME=999888777
59+
GC_OTLP_USERNAME=999888779
5960
GC_LOKI_USERNAME=999888778
6061
GC_API_TOKEN=molecule-test-token

‎ansible/molecule/grafana-cloud/verify.yml‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@
155155
'username = "1234567"' in ga_config
156156
- >-
157157
'sys.env("GC_PROM_USERNAME")' not in ga_config
158+
# The OTLP gateway takes the STACK instance ID, which converge leaves
159+
# unset: the config must defer to the host's GC_OTLP_USERNAME and only
160+
# then reuse the Prometheus ID, rather than binding the latter outright.
161+
- >-
162+
'username = coalesce(sys.env("GC_OTLP_USERNAME"), "1234567")' in ga_config
158163
fail_msg: >-
159164
The inventory-vs-env credential split did not render as expected:
160165
grafana_alloy_loki_url and the numeric grafana_alloy_prom_username were
@@ -181,6 +186,11 @@
181186
'GC_OTLP_ENDPOINT=https://' in ga_env_body
182187
- >-
183188
'GC_LOKI_USERNAME=999888778' in ga_env_body
189+
# Optional, and well-formed: proves preflight's present-then-shaped gate
190+
# for the OTLP instance ID accepts a valid host value rather than only
191+
# tolerating an absent key.
192+
- >-
193+
'GC_OTLP_USERNAME=999888779' in ga_env_body
184194
# Deliberately absent: converge supplies the Loki URL from inventory,
185195
# which is what makes preflight's conditional requirement meaningful.
186196
- >-

‎ansible/molecule/validation/converge.yml‎

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,23 @@
10261026
decdn_rejected: "{{ decdn_rejected + ['inventory-token'] }}"
10271027
when: ansible_failed_task.name is match('^Refuse a Grafana Cloud token smuggled into inventory')
10281028

1029+
- name: "Case ga-otlp-username-shape — malformed OTLP instance ID in inventory"
1030+
block:
1031+
# The OTLP gateway's username is the stack instance ID, not the
1032+
# Prometheus one, so it is its own variable — and must be covered by the
1033+
# same charset gate rather than silently reaching basic_auth.
1034+
- name: Run grafana_alloy with a whitespace-bearing OTLP instance ID
1035+
ansible.builtin.include_role:
1036+
name: grafana_alloy
1037+
vars:
1038+
decdn_grafana_cloud_enabled: true
1039+
grafana_alloy_otlp_username: "2345678 # stack id"
1040+
rescue:
1041+
- name: Record ga-otlp-username-shape rejection (only if the ID validator failed)
1042+
ansible.builtin.set_fact:
1043+
decdn_rejected: "{{ decdn_rejected + ['instance-id'] }}"
1044+
when: ansible_failed_task.name is match('^Validate any inventory-provided Grafana Cloud instance IDs')
1045+
10291046
- name: "Case ga-max-age — malformed journal catch-up window"
10301047
block:
10311048
- name: Run grafana_alloy with a bad max_age suffix
@@ -1073,6 +1090,43 @@
10731090
path: /etc/grafana-alloy.env
10741091
state: absent
10751092

1093+
- name: "Case ga-otlp-username-env — malformed GC_OTLP_USERNAME on the host"
1094+
block:
1095+
# The key is optional, so nothing above requires it — but a present,
1096+
# malformed value wins the rendered coalesce() over the valid Prometheus
1097+
# fallback and 401s every span, so it gets its own present-then-shaped
1098+
# gate.
1099+
- name: Stage a credential file whose OTLP instance ID carries a comment
1100+
ansible.builtin.copy:
1101+
dest: /etc/grafana-alloy.env
1102+
owner: root
1103+
group: root
1104+
mode: "0600"
1105+
content: |
1106+
GC_PROM_REMOTE_WRITE_URL=https://prometheus-prod-xx.molecule.invalid/api/prom/push
1107+
GC_OTLP_ENDPOINT=https://otlp-gateway-prod-xx.molecule.invalid/otlp
1108+
GC_PROM_USERNAME=999888777
1109+
GC_OTLP_USERNAME=1830557 # stack id
1110+
GC_LOKI_URL=https://logs-prod-xx.molecule.invalid/loki/api/v1/push
1111+
GC_LOKI_USERNAME=999888778
1112+
GC_API_TOKEN=molecule-test-token
1113+
1114+
- name: Run grafana_alloy with a malformed host OTLP instance ID
1115+
ansible.builtin.include_role:
1116+
name: grafana_alloy
1117+
vars:
1118+
decdn_grafana_cloud_enabled: true
1119+
rescue:
1120+
- name: Record ga-otlp-username-env rejection (only if the OTLP ID gate failed)
1121+
ansible.builtin.set_fact:
1122+
decdn_rejected: "{{ decdn_rejected + ['otlp-username-env'] }}"
1123+
when: ansible_failed_task.name is match('^Refuse a malformed host-provided OTLP instance ID')
1124+
always:
1125+
- name: Remove the malformed OTLP-username fixture
1126+
ansible.builtin.file:
1127+
path: /etc/grafana-alloy.env
1128+
state: absent
1129+
10761130
- name: Confirm every bad value was rejected by the role's own validation
10771131
ansible.builtin.assert:
10781132
that:
@@ -1097,5 +1151,6 @@
10971151
"alloy-root-user", "alloy-root-group",
10981152
"alloy-root-user-alias", "alloy-root-group-alias",
10991153
"host-collectors", "host-collectors", "host-collectors",
1100-
"endpoint-scheme", "inventory-token", "inventory-token",
1154+
"endpoint-scheme", "inventory-token", "inventory-token", "instance-id",
1155+
"otlp-username-env",
11011156
"managed-paths", "managed-paths", "managed-paths"]

‎ansible/roles/grafana_alloy/README.md‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Machine metrics, agent self-metrics and journald come with it (each has its own
4242

4343
### Credentials: one secret per host, the rest in inventory
4444

45-
Only the **API token** is a secret. The endpoint URLs and the two numeric
45+
Only the **API token** is a secret. The endpoint URLs and the three numeric
4646
instance IDs are not, so they belong in inventory — written once for the fleet
4747
rather than typed on every host:
4848

@@ -51,10 +51,17 @@ rather than typed on every host:
5151
grafana_alloy_prom_url: https://prometheus-prod-13-prod-us-east-0.grafana.net/api/prom/push
5252
grafana_alloy_prom_username: "1234567" # Prometheus instance ID
5353
grafana_alloy_otlp_endpoint: https://otlp-gateway-prod-us-east-0.grafana.net/otlp
54+
grafana_alloy_otlp_username: "2345678" # STACK instance ID, shown on the OTLP page
5455
grafana_alloy_loki_url: https://logs-prod-006.grafana.net/loki/api/v1/push
5556
grafana_alloy_loki_username: "7654321" # Loki instance ID — a DIFFERENT number
5657
```
5758

59+
Each of those three IDs is its own number in the portal. Copy each from the page
60+
that names it (**Prometheus → Username / Instance ID**, **OTLP Endpoint →
61+
Instance ID**, **Loki → User**) rather than assuming one value covers all three:
62+
where the OTLP ID differs and is left unset, traces 401 while metrics and logs
63+
keep flowing, which reads as "tracing is broken" rather than "auth is wrong".
64+
5865
Then the only thing to provision **on the target host** is the token (it never
5966
transits the control machine):
6067

@@ -83,10 +90,18 @@ key only then. Any mix of the two halves is valid.
8390
> `job="decdn-node"` instead of the implicit `job="prometheus.scrape.decdn_node"`.
8491
> Set `grafana_alloy_node_job: ""` to keep the old value if dashboards or alert
8592
> rules already hard-code it.
93+
>
94+
> **Traces 401 after an upgrade?** The OTLP gateway wants the stack instance ID,
95+
> and earlier versions of this role reused the Prometheus one. Where your org's
96+
> two IDs differ, set `grafana_alloy_otlp_username` (or add `GC_OTLP_USERNAME` to
97+
> the env file); where they coincide, nothing changes and nothing is needed.
98+
> `GC_OTLP_USERNAME` is the one credential key that may be absent — but if it is
99+
> present, preflight checks its shape, because a malformed value outranks the
100+
> Prometheus fallback at runtime and 401s traces just the same.
86101

87102
`GC_API_TOKEN` has **no** inventory variable by design: the rendered
88103
`/etc/alloy/config.alloy` is world-readable, and preflight rejects a value that
89-
looks like a token (or a URL with embedded credentials) in any of the five
104+
looks like a token (or a URL with embedded credentials) in any of the six
90105
variables above.
91106

92107
The credential path is intentionally restricted to a **direct child of `/etc`**. A
@@ -184,7 +199,7 @@ upstream version/sha256). Highlights:
184199
| `grafana_alloy_logs_enabled` | `true` | journald → Grafana Cloud Loki |
185200
| `grafana_alloy_logs_max_age` | `12h` | Bounds the catch-up burst after an outage |
186201
| `grafana_alloy_self_metrics_enabled` | `true` | Alloy's own health |
187-
| `grafana_alloy_prom_url` / `_prom_username` / `_otlp_endpoint` / `_loki_url` / `_loki_username` | `""` | Non-secret connection settings; empty ⇒ read the matching `GC_…` env key |
202+
| `grafana_alloy_prom_url` / `_prom_username` / `_otlp_endpoint` / `_otlp_username` / `_loki_url` / `_loki_username` | `""` | Non-secret connection settings; empty ⇒ read the matching `GC_…` env key (`_otlp_username` then falls back to the Prometheus ID) |
188203
| `grafana_alloy_node_job` / `_host_job` / `_self_job` / `_logs_job` | see table above | Job labels; the `integrations/…` ones are what Grafana Cloud's dashboards match |
189204

190205
Label variables (`service_name`, `service_namespace`, `instance_id`,

‎ansible/roles/grafana_alloy/defaults/main.yml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ grafana_alloy_server_http_addr: 127.0.0.1:12345
6767
grafana_alloy_prom_url: "" # else sys.env("GC_PROM_REMOTE_WRITE_URL")
6868
grafana_alloy_prom_username: "" # else sys.env("GC_PROM_USERNAME")
6969
grafana_alloy_otlp_endpoint: "" # else sys.env("GC_OTLP_ENDPOINT")
70+
# The OTLP gateway authenticates with the STACK instance ID, which on many
71+
# Grafana Cloud orgs is a different number from the Prometheus one (portal:
72+
# "OTLP Endpoint" -> "Instance ID"). This role used to reuse the Prometheus
73+
# value, so a stack where the two differ 401s on traces while metrics keep
74+
# flowing — a silent half-outage. Left empty, the rendered config falls back at
75+
# RUNTIME to sys.env("GC_OTLP_USERNAME") and then to whatever the Prometheus
76+
# side resolves to, so an org where the IDs coincide keeps working untouched.
77+
grafana_alloy_otlp_username: "" # else sys.env("GC_OTLP_USERNAME"), else the Prometheus ID
7078
# Grafana Cloud's Loki instance ID is a DIFFERENT number from the Prometheus one
7179
# (the same access-policy token authenticates both).
7280
grafana_alloy_loki_url: "" # else sys.env("GC_LOKI_URL")

‎ansible/roles/grafana_alloy/files/grafana-alloy.env.example‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
#
2020
# Values come from your Grafana Cloud org: Home -> Stacks -> your stack, then the
2121
# "Details/Send Metrics|Logs|Traces" pages show each endpoint plus its numeric
22-
# instance ID. The role greps shapes only; it never reads values back onto the
23-
# control machine.
22+
# instance ID. Each service has ITS OWN instance ID — copy each from the page that
23+
# names it rather than reusing one value. The role greps shapes only; it never
24+
# reads values back onto the control machine.
2425

2526
# REQUIRED, always. An access-policy token with metrics:write + logs:write +
2627
# traces:write scope for this stack. This is the one value with no inventory
@@ -36,6 +37,13 @@ GC_API_TOKEN=glc_example_token_replace_me
3637

3738
# grafana_alloy_otlp_endpoint
3839
#GC_OTLP_ENDPOINT=https://otlp-gateway-prod-XX.grafana.net/otlp
40+
# grafana_alloy_otlp_username — the numeric STACK instance ID shown on the OTLP
41+
# page. It is NOT necessarily the Prometheus one; where your org's two differ and
42+
# neither this key nor the variable is set, the config falls back to the
43+
# Prometheus ID and the OTLP pipeline (traces) 401s while metrics keep flowing.
44+
# Optional — but if you do set it, preflight checks its shape: a malformed value
45+
# takes precedence over that fallback and 401s traces just the same.
46+
#GC_OTLP_USERNAME=2345678
3947

4048
# Logs (grafana_alloy_logs_enabled, on by default). NOTE the Loki instance ID is
4149
# a DIFFERENT number from the Prometheus one above; the token is shared.

‎ansible/roles/grafana_alloy/tasks/preflight.yml‎

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,12 @@
207207
instance ID on Grafana Cloud, a tenant name on a self-hosted
208208
Mimir/Loki — so it is restricted to letters, digits, dot, underscore and
209209
dash, and must not be a token or carry whitespace. Got "{{ item.value }}".
210-
NOTE the Loki instance ID differs from the Prometheus one; the
211-
access-policy token is shared and stays in
210+
NOTE the Loki and OTLP instance IDs are each their own number, distinct
211+
from the Prometheus one; the access-policy token is shared and stays in
212212
{{ grafana_alloy_secret_file }} as GC_API_TOKEN.
213213
loop:
214214
- {name: grafana_alloy_prom_username, value: "{{ grafana_alloy_prom_username | string }}"}
215+
- {name: grafana_alloy_otlp_username, value: "{{ grafana_alloy_otlp_username | string }}"}
215216
- {name: grafana_alloy_loki_username, value: "{{ grafana_alloy_loki_username | string }}"}
216217
loop_control:
217218
label: "{{ item.name }}"
@@ -233,6 +234,7 @@
233234
- "{{ grafana_alloy_prom_url | string }}"
234235
- "{{ grafana_alloy_prom_username | string }}"
235236
- "{{ grafana_alloy_otlp_endpoint | string }}"
237+
- "{{ grafana_alloy_otlp_username | string }}"
236238
- "{{ grafana_alloy_loki_url | string }}"
237239
- "{{ grafana_alloy_loki_username | string }}"
238240
loop_control:
@@ -402,3 +404,53 @@
402404
| rejectattr('rc', 'equalto', 0)
403405
| map(attribute='item') | list }}
404406
when: _ga_missing | length > 0
407+
408+
# GC_OTLP_USERNAME is the one credential key that is legitimately OPTIONAL: with
409+
# neither inventory nor env-file value, the rendered config resolves the OTLP
410+
# username at runtime to the Prometheus ID — exactly what every pre-existing
411+
# deployment used — so ABSENT is correct and must not be required. But
412+
# PRESENT-AND-MALFORMED is not harmless: coalesce() returns the first non-empty
413+
# argument, so a value carrying whitespace or a stray comment wins over the valid
414+
# Prometheus fallback and 401s the entire trace pipeline while metrics and logs
415+
# stay healthy. The required-key gate above cannot cover it (it only greps keys it
416+
# demands), hence this present-then-shaped pair. Skipped when inventory supplies
417+
# the ID, because the template then inlines it and emits no sys.env lookup at all.
418+
- name: Detect a host-provided OTLP instance ID
419+
ansible.builtin.command:
420+
cmd: grep -qE -- '^GC_OTLP_USERNAME=.*[^[:space:]]' {{ grafana_alloy_secret_file | quote }}
421+
changed_when: false
422+
check_mode: false # read-only gate: must also run under make check
423+
failed_when: false
424+
register: _ga_otlp_user_present
425+
when: grafana_alloy_otlp_username | string | length == 0
426+
427+
# Same charset as the inventory instance-ID gate, plus the optional double quotes
428+
# systemd strips off an EnvironmentFile value.
429+
- name: Check the shape of a host-provided OTLP instance ID
430+
ansible.builtin.command:
431+
cmd: grep -qE -- '^GC_OTLP_USERNAME="?[A-Za-z0-9._-]{1,64}"?$' {{ grafana_alloy_secret_file | quote }}
432+
changed_when: false
433+
check_mode: false # read-only gate: must also run under make check
434+
failed_when: false
435+
register: _ga_otlp_user_shape
436+
when:
437+
- grafana_alloy_otlp_username | string | length == 0
438+
- (_ga_otlp_user_present.rc | default(1)) == 0
439+
440+
- name: Refuse a malformed host-provided OTLP instance ID
441+
ansible.builtin.assert:
442+
that:
443+
- (_ga_otlp_user_shape.rc | default(0)) == 0
444+
fail_msg: >-
445+
{{ grafana_alloy_secret_file }} sets GC_OTLP_USERNAME to a value that is
446+
not an instance ID (letters, digits, dot, underscore, dash, ≤ 64 chars, no
447+
whitespace and no trailing comment). The key is optional — drop the line
448+
entirely and traces fall back to the Prometheus instance ID, which is what
449+
this role used before the OTLP gateway got its own username — but a
450+
malformed value is worse than none: it takes precedence over that fallback
451+
and every span is rejected with a 401 while remote-write and Loki keep
452+
flowing. Fix the line on the host, remove it, or set
453+
grafana_alloy_otlp_username in inventory instead.
454+
when:
455+
- grafana_alloy_otlp_username | string | length == 0
456+
- (_ga_otlp_user_present.rc | default(1)) == 0

0 commit comments

Comments
 (0)