diff --git a/.ci-operator.yaml b/.ci-operator.yaml index 894b52c3..188626d7 100644 --- a/.ci-operator.yaml +++ b/.ci-operator.yaml @@ -1,4 +1,4 @@ build_root_image: name: boilerplate namespace: openshift - tag: image-v8.3.5 + tag: image-v8.3.6 diff --git a/.claude/commands/pre-commit.md b/.claude/commands/pre-commit.md new file mode 100644 index 00000000..56c2d481 --- /dev/null +++ b/.claude/commands/pre-commit.md @@ -0,0 +1,94 @@ +Run pre-commit hooks on this repository following the agentic SDLC golden rules (SREP-4450). + +## Usage +- `/pre-commit` — run on staged files (default, fastest) +- `/pre-commit --all-files` — run on all files (first-time setup, CI equivalent) +- `/pre-commit ` — run a single hook by ID (targeted debugging) + +## What you must do + +### Step 1 — Preflight checks + +1. Confirm `.pre-commit-config.yaml` exists in the repo root. If not, tell the user and stop. +2. Confirm `pre-commit` is installed: run `which pre-commit`. If not found, run `pip install pre-commit` or `pip3 install pre-commit`. +3. Confirm hooks are installed: check if `.git/hooks/pre-commit` exists. If not, run `pre-commit install`. + +### Step 2 — Run hooks + +Determine the run mode from `$ARGUMENTS`: +- `--all-files` → run `pre-commit run --all-files` +- `` (a word that is not a flag) → run `pre-commit run ` +- empty or default → run `pre-commit run` (staged files only) + +Capture the full stdout and stderr output. + +### Step 3 — Parse and categorise results + +For each hook in the output, classify it as one of: +- **Passed** — hook exited 0, no changes +- **Auto-fixed** — hook exited non-zero but modified files (trailing-whitespace, end-of-file-fixer) +- **Failed** — hook exited non-zero, no auto-fix + +Extract for each failure: +- Hook ID and name +- Affected files and line numbers if present +- The error message +- Whether it is a security hook (gitleaks, rbac-wildcard-check) + +### Step 4 — Handle auto-fixes (idempotency loop, golden rule 9) + +If any hooks auto-fixed files: +1. Stage the modified files: `git add ` +2. Re-run the hooks on staged files +3. Report what was fixed + +### Step 5 — Retry on failure (golden rule 19, max 2 iterations) + +Track `attempt_count` starting at 1. + +For each non-security failure with an identifiable fix: +1. Apply the fix (edit the file, run the suggested command) +2. Stage the changes +3. Re-run `pre-commit run` +4. Increment `attempt_count` + +**Stop retrying when:** +- All hooks pass → report success +- `attempt_count` reaches 3 → stop, escalate to human (see Step 6) +- A security hook fails → stop immediately, escalate to human (see Step 6) + +### Step 6 — Escalate to human when required + +Escalate (do not retry further) when: +- A **security hook** fires (gitleaks, rbac-wildcard-check) — these require human judgment +- Hooks still fail after **2 fix-and-retry attempts** +- A hook **timed out** — this indicates a systemic issue, not a fixable code problem + +When escalating, report: +- Which hook is failing +- The exact error output +- What was already attempted +- The recommended next action for the human + +### Step 7 — Final report + +Always end with a structured summary: + +``` +PRE-COMMIT SUMMARY +================== +Passed: +Auto-fixed: → files staged +Fixed: → changes applied +Failed: → escalated to human +Attempts: of 2 maximum +``` + +## Rules you must never break + +- **Never run `git commit --no-verify`** — bypassing all hooks is not permitted +- **Never modify `.pre-commit-config.yaml`** to suppress a failing hook +- **Never retry more than twice** — escalate on the third failure +- **Never auto-fix a security hook failure** — always escalate to human +- **Always stage auto-fixed files** before re-running — do not leave unstaged modifications +- **Always report what changed** — the human must be able to review every fix you applied diff --git a/.codecov.yml b/.codecov.yml index ba05647a..20cbf543 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -8,8 +8,14 @@ coverage: range: "20...100" status: - project: no - patch: no + project: + default: + target: 35% + threshold: 1% + patch: + default: + target: 50% + threshold: 1% changes: no parsers: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..14ecdd9b --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,134 @@ +# ============================================================================= +# Tier 1 — Common Pre-Commit Hooks for OSD Operators +# SREP-4485 | Golden rules: SREP-4450 +# ============================================================================= +# +# INSTALL +# pip install pre-commit +# pre-commit install +# +# USAGE +# pre-commit run # staged files only (developer / agent workflow) +# pre-commit run --all-files # full repo (CI / first-time setup) +# +# BYPASS (golden rule 16) +# Skip one hook: SKIP=hook-id git commit +# Never use: git commit --no-verify +# Agents: never bypass any hook +# Security hooks: never bypassable under any circumstances +# +# CI RELATIONSHIP (golden rule 17) +# These hooks mirror ci/prow/lint. CI remains the authoritative gate. +# Every check here also runs in CI. Pre-commit is developer convenience. +# +# AGENT USAGE (golden rule 1, 7, 19) +# Agents run: pre-commit run +# Output: PRE_COMMIT=1 is set automatically — hooks emit structured output +# Retry: max 2 fix-and-retry iterations before escalating to human +# +# TIMING TARGETS (golden rule 2, 3) +# Total run: <= 10s target / <= 60s hard limit on a 10-file changeset +# Hooks run fastest-first (golden rule 13). Each hook has a timeout guard. +# +# FIRST RUN NOTE +# Auto-fix hooks (trailing-whitespace, end-of-file-fixer) will correct +# pre-existing violations on the first run. Stage and commit those fixes +# separately before day-to-day use. +# +# ============================================================================= + +repos: + + # --------------------------------------------------------------------------- + # 1. FILE HYGIENE + YAML SYNTAX | target < 2s | auto-fix + error + # - check-merge-conflict: detects unresolved merge markers + # - trailing-whitespace: removes trailing spaces (auto-fix) + # - end-of-file-fixer: ensures single EOF newline (auto-fix) + # - check-yaml: validates YAML syntax in deploy/ manifests; + # mirrors ci/prow/lint: olm-deploy-yaml-validate + # --------------------------------------------------------------------------- + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 # pinned immutable tag + hooks: + - id: check-merge-conflict + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + - id: end-of-file-fixer + - id: check-yaml + name: YAML syntax (deploy/) + files: ^deploy/.*\.ya?ml$ + args: [--allow-multiple-documents] + + # --------------------------------------------------------------------------- + # 2. SECRETS DETECTION | target < 5s | always blocking + # Scans all file types (YAML, shell, config) — gosec covers Go only. + # High-confidence findings block; configure .gitleaks.toml for allowlist. + # --------------------------------------------------------------------------- + - repo: https://github.com/gitleaks/gitleaks + rev: v8.18.0 # pinned immutable tag (golden rule 15) + hooks: + - id: gitleaks + + # --------------------------------------------------------------------------- + # 3. STATIC ANALYSIS | target < 15s cached | error + # Mirrors ci/prow/lint: go-check exactly (same version + config as CI). + # Linter config: boilerplate/openshift/golang-osd-operator/golangci.yml + # --------------------------------------------------------------------------- + - repo: https://github.com/golangci/golangci-lint + rev: v2.0.2 # pinned immutable tag — must match CI (golden rule 15) + hooks: + - id: golangci-lint + args: + - --config=boilerplate/openshift/golang-osd-operator/golangci.yml + - --timeout=120s # graceful timeout (golden rule 3) + + # --------------------------------------------------------------------------- + # Local hooks — compile, dependency, security + # + # TIMEOUT NOTE (golden rule 3) + # Uses portable timeout detection: 'timeout' on Linux, 'gtimeout' on macOS. + # macOS: brew install coreutils + # Linux: timeout is available by default (GNU coreutils) + # --------------------------------------------------------------------------- + - repo: local + hooks: + + # ----------------------------------------------------------------------- + # 4. COMPILE CHECK | target < 10s cached | error + # Catches import cycles and type errors before golangci-lint runs. + # Note: go build ./... writes no binary to the repo (compile check only). + # Fix: resolve compilation errors reported by go build. + # ----------------------------------------------------------------------- + - id: go-build + name: go build + language: system + entry: bash -c 'T=$(command -v timeout || command -v gtimeout || echo); ${T:+$T 30s} go build ./...' + types: [go] + pass_filenames: false + + # ----------------------------------------------------------------------- + # 5. DEPENDENCY DRIFT | target < 10s | error + # Detects uncommitted go.mod/go.sum changes after go mod tidy. + # Fix: run 'go mod tidy' and stage go.mod and go.sum. + # ----------------------------------------------------------------------- + - id: go-mod-tidy + name: go mod tidy + language: system + entry: bash -c 'T=$(command -v timeout || command -v gtimeout || echo); ${T:+$T 60s} go mod tidy && git diff --exit-code go.mod go.sum' + files: '(\.go$|go\.(mod|sum)$)' + exclude: '^vendor/' + pass_filenames: false + + # ----------------------------------------------------------------------- + # 6. RBAC WILDCARD CHECK | target < 5s | warn-only (blocking after cleanup) + # Rejects wildcard RBAC in deploy/ manifests (verbs/resources: ["*"] + # or multi-line - '*' format). Logic lives in standard.mk target + # 'rbac-wildcard-check' for readability and reuse. + # Fix: replace wildcards with explicit verbs and resource names. + # ----------------------------------------------------------------------- + - id: rbac-wildcard-check + name: RBAC wildcard permissions + language: system + entry: bash -c 'make rbac-wildcard-check' + files: ^deploy/.*\.ya?ml$ + pass_filenames: false diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES index 80ff8d3c..2c4fa259 100644 --- a/OWNERS_ALIASES +++ b/OWNERS_ALIASES @@ -4,7 +4,6 @@ # ============================================================================= aliases: srep-functional-team-aurora: - - abyrne55 - AlexSmithGH - dakotalongRH - eth1030 @@ -73,7 +72,6 @@ aliases: - yiqinzhang - varunraokadaparthi srep-functional-leads: - - abyrne55 - clcollins - bergmannf - theautoroboto diff --git a/boilerplate/_data/backing-image-tag b/boilerplate/_data/backing-image-tag index 51bd32ed..ca21d244 100644 --- a/boilerplate/_data/backing-image-tag +++ b/boilerplate/_data/backing-image-tag @@ -1 +1 @@ -image-v8.3.5 +image-v8.3.6 diff --git a/boilerplate/_data/last-boilerplate-commit b/boilerplate/_data/last-boilerplate-commit index a60dbfd6..ff503d84 100644 --- a/boilerplate/_data/last-boilerplate-commit +++ b/boilerplate/_data/last-boilerplate-commit @@ -1 +1 @@ -294b1978042a939fb940bc8ae89e498991a7ca43 +b6e7575196e8c17274c85d2c22178ad51290c237 diff --git a/boilerplate/openshift/golang-osd-operator/.codecov.yml b/boilerplate/openshift/golang-osd-operator/.codecov.yml index ba05647a..20cbf543 100644 --- a/boilerplate/openshift/golang-osd-operator/.codecov.yml +++ b/boilerplate/openshift/golang-osd-operator/.codecov.yml @@ -8,8 +8,14 @@ coverage: range: "20...100" status: - project: no - patch: no + project: + default: + target: 35% + threshold: 1% + patch: + default: + target: 50% + threshold: 1% changes: no parsers: diff --git a/boilerplate/openshift/golang-osd-operator/OWNERS_ALIASES b/boilerplate/openshift/golang-osd-operator/OWNERS_ALIASES index 80ff8d3c..2c4fa259 100644 --- a/boilerplate/openshift/golang-osd-operator/OWNERS_ALIASES +++ b/boilerplate/openshift/golang-osd-operator/OWNERS_ALIASES @@ -4,7 +4,6 @@ # ============================================================================= aliases: srep-functional-team-aurora: - - abyrne55 - AlexSmithGH - dakotalongRH - eth1030 @@ -73,7 +72,6 @@ aliases: - yiqinzhang - varunraokadaparthi srep-functional-leads: - - abyrne55 - clcollins - bergmannf - theautoroboto diff --git a/boilerplate/openshift/golang-osd-operator/golangci.yml b/boilerplate/openshift/golang-osd-operator/golangci.yml index 46fec035..ced20910 100644 --- a/boilerplate/openshift/golang-osd-operator/golangci.yml +++ b/boilerplate/openshift/golang-osd-operator/golangci.yml @@ -1,39 +1,78 @@ version: "2" -run: - concurrency: 10 + linters: - default: none enable: + # Error Handling & Security - errcheck - - gosec - govet - - ineffassign - - misspell - staticcheck + - gosec + - bodyclose + - sqlclosecheck + - contextcheck + - noctx + + # Error Prevention + - errorlint + - nilerr + - nilnil + - revive + + # Code Quality + - ineffassign + - unconvert + - unparam - unused + - misspell + + # Maintainability + - prealloc + - nolintlint + - gocyclo + - exhaustive + - makezero + - containedctx + settings: + revive: + rules: + - name: package-comments + disabled: true + + errcheck: + check-type-assertions: true + check-blank: false + + exclusions: + presets: + - std-error-handling + + gocyclo: + min-complexity: 15 + + errorlint: + errorf: true + asserts: true + comparison: true + misspell: extra-words: - typo: openshit correction: OpenShift - exclusions: - generated: lax - presets: - - comments - - common-false-positives - - legacy - - std-error-handling - paths: - - third_party/ - - builtin/ - - examples/ + +run: + timeout: 5m + # Only check new code, not existing issues + # This uses git to compare against the base branch (typically master/main) + new: true + # Alternatively, you can specify a specific revision: + # new-from-rev: origin/master + +formatters: + enable: + - gofmt + - goimports + issues: max-issues-per-linter: 0 max-same-issues: 0 -formatters: - exclusions: - generated: lax - paths: - - third_party/ - - builtin/ - - examples/ diff --git a/boilerplate/openshift/golang-osd-operator/pre-commit-config.yaml b/boilerplate/openshift/golang-osd-operator/pre-commit-config.yaml new file mode 100644 index 00000000..14ecdd9b --- /dev/null +++ b/boilerplate/openshift/golang-osd-operator/pre-commit-config.yaml @@ -0,0 +1,134 @@ +# ============================================================================= +# Tier 1 — Common Pre-Commit Hooks for OSD Operators +# SREP-4485 | Golden rules: SREP-4450 +# ============================================================================= +# +# INSTALL +# pip install pre-commit +# pre-commit install +# +# USAGE +# pre-commit run # staged files only (developer / agent workflow) +# pre-commit run --all-files # full repo (CI / first-time setup) +# +# BYPASS (golden rule 16) +# Skip one hook: SKIP=hook-id git commit +# Never use: git commit --no-verify +# Agents: never bypass any hook +# Security hooks: never bypassable under any circumstances +# +# CI RELATIONSHIP (golden rule 17) +# These hooks mirror ci/prow/lint. CI remains the authoritative gate. +# Every check here also runs in CI. Pre-commit is developer convenience. +# +# AGENT USAGE (golden rule 1, 7, 19) +# Agents run: pre-commit run +# Output: PRE_COMMIT=1 is set automatically — hooks emit structured output +# Retry: max 2 fix-and-retry iterations before escalating to human +# +# TIMING TARGETS (golden rule 2, 3) +# Total run: <= 10s target / <= 60s hard limit on a 10-file changeset +# Hooks run fastest-first (golden rule 13). Each hook has a timeout guard. +# +# FIRST RUN NOTE +# Auto-fix hooks (trailing-whitespace, end-of-file-fixer) will correct +# pre-existing violations on the first run. Stage and commit those fixes +# separately before day-to-day use. +# +# ============================================================================= + +repos: + + # --------------------------------------------------------------------------- + # 1. FILE HYGIENE + YAML SYNTAX | target < 2s | auto-fix + error + # - check-merge-conflict: detects unresolved merge markers + # - trailing-whitespace: removes trailing spaces (auto-fix) + # - end-of-file-fixer: ensures single EOF newline (auto-fix) + # - check-yaml: validates YAML syntax in deploy/ manifests; + # mirrors ci/prow/lint: olm-deploy-yaml-validate + # --------------------------------------------------------------------------- + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 # pinned immutable tag + hooks: + - id: check-merge-conflict + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + - id: end-of-file-fixer + - id: check-yaml + name: YAML syntax (deploy/) + files: ^deploy/.*\.ya?ml$ + args: [--allow-multiple-documents] + + # --------------------------------------------------------------------------- + # 2. SECRETS DETECTION | target < 5s | always blocking + # Scans all file types (YAML, shell, config) — gosec covers Go only. + # High-confidence findings block; configure .gitleaks.toml for allowlist. + # --------------------------------------------------------------------------- + - repo: https://github.com/gitleaks/gitleaks + rev: v8.18.0 # pinned immutable tag (golden rule 15) + hooks: + - id: gitleaks + + # --------------------------------------------------------------------------- + # 3. STATIC ANALYSIS | target < 15s cached | error + # Mirrors ci/prow/lint: go-check exactly (same version + config as CI). + # Linter config: boilerplate/openshift/golang-osd-operator/golangci.yml + # --------------------------------------------------------------------------- + - repo: https://github.com/golangci/golangci-lint + rev: v2.0.2 # pinned immutable tag — must match CI (golden rule 15) + hooks: + - id: golangci-lint + args: + - --config=boilerplate/openshift/golang-osd-operator/golangci.yml + - --timeout=120s # graceful timeout (golden rule 3) + + # --------------------------------------------------------------------------- + # Local hooks — compile, dependency, security + # + # TIMEOUT NOTE (golden rule 3) + # Uses portable timeout detection: 'timeout' on Linux, 'gtimeout' on macOS. + # macOS: brew install coreutils + # Linux: timeout is available by default (GNU coreutils) + # --------------------------------------------------------------------------- + - repo: local + hooks: + + # ----------------------------------------------------------------------- + # 4. COMPILE CHECK | target < 10s cached | error + # Catches import cycles and type errors before golangci-lint runs. + # Note: go build ./... writes no binary to the repo (compile check only). + # Fix: resolve compilation errors reported by go build. + # ----------------------------------------------------------------------- + - id: go-build + name: go build + language: system + entry: bash -c 'T=$(command -v timeout || command -v gtimeout || echo); ${T:+$T 30s} go build ./...' + types: [go] + pass_filenames: false + + # ----------------------------------------------------------------------- + # 5. DEPENDENCY DRIFT | target < 10s | error + # Detects uncommitted go.mod/go.sum changes after go mod tidy. + # Fix: run 'go mod tidy' and stage go.mod and go.sum. + # ----------------------------------------------------------------------- + - id: go-mod-tidy + name: go mod tidy + language: system + entry: bash -c 'T=$(command -v timeout || command -v gtimeout || echo); ${T:+$T 60s} go mod tidy && git diff --exit-code go.mod go.sum' + files: '(\.go$|go\.(mod|sum)$)' + exclude: '^vendor/' + pass_filenames: false + + # ----------------------------------------------------------------------- + # 6. RBAC WILDCARD CHECK | target < 5s | warn-only (blocking after cleanup) + # Rejects wildcard RBAC in deploy/ manifests (verbs/resources: ["*"] + # or multi-line - '*' format). Logic lives in standard.mk target + # 'rbac-wildcard-check' for readability and reuse. + # Fix: replace wildcards with explicit verbs and resource names. + # ----------------------------------------------------------------------- + - id: rbac-wildcard-check + name: RBAC wildcard permissions + language: system + entry: bash -c 'make rbac-wildcard-check' + files: ^deploy/.*\.ya?ml$ + pass_filenames: false diff --git a/boilerplate/openshift/golang-osd-operator/standard.mk b/boilerplate/openshift/golang-osd-operator/standard.mk index cebc4505..3a0b3db4 100644 --- a/boilerplate/openshift/golang-osd-operator/standard.mk +++ b/boilerplate/openshift/golang-osd-operator/standard.mk @@ -380,6 +380,23 @@ validate: boilerplate-freeze-check generate-check validate-pko-fixtures .PHONY: lint lint: olm-deploy-yaml-validate go-check +# rbac-wildcard-check: Detect wildcard RBAC permissions in deploy/ manifests. +# Checks both inline (verbs: ["*"]) and multi-line (- '*' under verbs/resources:) +# formats. Called by the pre-commit rbac-wildcard-check hook. +# Currently warn-only (exits 0) to avoid breaking repos with pre-existing wildcards. +# Will become blocking once existing violations are resolved across the fleet. +.PHONY: rbac-wildcard-check +rbac-wildcard-check: + @python3 -c "\ +import sys,glob;\ +violations=[(f,n,l.rstrip()) for f in glob.glob('deploy/*.yaml')+glob.glob('deploy/*.yml') \ +for lines in [list(enumerate(open(f),1))] \ +for i,(n,l) in enumerate(lines) \ +if l.strip().lstrip('- ').strip(chr(39)+chr(34))=='*' \ +and any(lines[j][1].strip() in ('verbs:','resources:') for j in range(max(0,i-5),i))];\ +[print('WARNING: wildcard RBAC found: '+v[0]+'|'+str(v[1])+'|'+v[2]) for v in violations];\ +sys.exit(0)" + # test: "Local" unit and functional testing. .PHONY: test test: go-test diff --git a/boilerplate/openshift/golang-osd-operator/update b/boilerplate/openshift/golang-osd-operator/update index 7f2c702f..5fe4a385 100755 --- a/boilerplate/openshift/golang-osd-operator/update +++ b/boilerplate/openshift/golang-osd-operator/update @@ -110,6 +110,10 @@ echo " name: $IMAGE_NAME" echo " tag: $LATEST_IMAGE_TAG" ${SED?} "s/__NAMESPACE__/$IMAGE_NAMESPACE/; s/__NAME__/$IMAGE_NAME/; s/__TAG__/$LATEST_IMAGE_TAG/" ${HERE}/.ci-operator.yaml >$REPO_ROOT/.ci-operator.yaml +# Add pre-commit hooks configuration (SREP-4485) +echo "Copying pre-commit-config.yaml to .pre-commit-config.yaml" +cp ${HERE}/pre-commit-config.yaml $REPO_ROOT/.pre-commit-config.yaml + # Check for pipeline files in .tekton directory and centralize them TEKTON_DIR="${REPO_ROOT}/.tekton" if [ -d "$TEKTON_DIR" ]; then diff --git a/build/Dockerfile b/build/Dockerfile index 66ed3a5c..17ec742c 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,4 +1,4 @@ -FROM quay.io/redhat-services-prod/openshift/boilerplate:image-v8.3.5 AS builder +FROM quay.io/redhat-services-prod/openshift/boilerplate:image-v8.3.6 AS builder WORKDIR /workspace # Copy the Go Modules manifests @@ -18,7 +18,7 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -mod=mod -a -o # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details -FROM registry.access.redhat.com/ubi9/ubi-minimal:9.7-1776833838 +FROM registry.access.redhat.com/ubi9/ubi-minimal:9.7-1778072020 WORKDIR / COPY --from=builder /workspace/manager . USER nonroot:nonroot diff --git a/build/Dockerfile.olm-registry b/build/Dockerfile.olm-registry index d74386a1..ab3a27c8 100644 --- a/build/Dockerfile.olm-registry +++ b/build/Dockerfile.olm-registry @@ -4,7 +4,7 @@ COPY ${SAAS_OPERATOR_DIR} manifests RUN initializer --permissive # ubi-micro does not work for clusters with fips enabled unless we make OpenSSL available -FROM registry.access.redhat.com/ubi9/ubi-minimal:9.7-1776833838 +FROM registry.access.redhat.com/ubi9/ubi-minimal:9.7-1778072020 COPY --from=builder /bin/registry-server /bin/registry-server COPY --from=builder /bin/grpc_health_probe /bin/grpc_health_probe diff --git a/pkg/metrics/metrics_test.go b/pkg/metrics/metrics_test.go index 0af07fef..a9bb60c7 100644 --- a/pkg/metrics/metrics_test.go +++ b/pkg/metrics/metrics_test.go @@ -135,8 +135,7 @@ mnmo_node_reconciliation_failure{node="%s"} 1 `) for i := 0; i < numGoroutines; i++ { - expectedMetric.WriteString(fmt.Sprintf(`mnmo_node_reconciliation_failure{node="node-%d"} %d -`, i, incrementsPerGoroutine)) + fmt.Fprintf(&expectedMetric, "mnmo_node_reconciliation_failure{node=\"node-%d\"} %d\n", i, incrementsPerGoroutine) } err := testutil.CollectAndCompare(NodeReconciliationFailure, strings.NewReader(expectedMetric.String()))