From 370d3448bd1d5ea08a47e4e0cb9a6176e5f706eb Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Thu, 10 Sep 2026 19:15:16 +0200 Subject: [PATCH 01/19] ci: adopt MegaLinter as an aggregate lint gate MegaLinter covers every language in this repository at once, which nothing else did. Adopting it against a tree that had never run it meant deciding what it is allowed to say, because the defaults reported roughly 25,400 findings in about 50 minutes of linter time and a gate nobody can read is a gate nobody reads. The configuration is tuned to roughly 1,880 findings in about 20 minutes, and every choice is recorded where it is made rather than here: - Excludes generated and published trees: the versioned chart directories and their indexes, the operator's kubebuilder output and consolidated installer, the exported OpenAPI document, the design documents the house style gate already owns, and the Helm templates that are not YAML until rendered. - Runs one tool per job. revive lints Go, ruff lints Python, and yamllint is the only YAML gate; golangci-lint, flake8, isort, pylint, pyright, prettier's YAML pass, and rubocop are disabled because something else already answers the same question, in some cases better. - Carries tool-native rule sets in revive.toml, .yamllint.yml, .markdownlint.yaml, .cspell.json, and .jscpd.json, each explaining which default it relaxes and what that default was reporting. - Reports and does not write. The apply-fixes bot the MegaLinter template ships with is absent, and DISABLE_ERRORS keeps the backlog from failing every pull request on day one. Removing that line is what promotes this to a gate. Two upstream bugs shaped the config. MegaLinter 9.4.0 advertises both a string and a list for FILTER_REGEX_EXCLUDE and accepts only a string: the global value is concatenated into a log message, and a per-linter value goes straight to re.compile. Both raise a TypeError on a list. cspell disagreed with the house style at the mechanism level. The house style exempts an identifier, path, command, or value by putting it in backticks, and cspell honors neither backticks nor fenced blocks, so 381 of its 482 findings were words already exempt. Its dictionary also held 59 words while the house style mandates 185 product spellings, 174 of which were missing. Both are fixed here, which takes cspell from 482 findings in 978 seconds to 41 in 5. Kubebuilder and OLM join the house style's terminology list. Both are spelled inconsistently in the existing prose, and nothing enforced either. --- .../house-style/scripts/check-terminology.py | 2 + .cspell.json | 277 ++++++++++++++++++ .github/workflows/mega_linter.yml | 91 ++++++ .gitignore | 5 + .jscpd.json | 26 ++ .markdownlint.yaml | 25 ++ .mega-linter.yml | 114 +++++++ .yamllint.yml | 32 ++ revive.toml | 57 ++++ 9 files changed, 629 insertions(+) create mode 100644 .cspell.json create mode 100644 .github/workflows/mega_linter.yml create mode 100644 .jscpd.json create mode 100644 .markdownlint.yaml create mode 100644 .mega-linter.yml create mode 100644 .yamllint.yml create mode 100644 revive.toml diff --git a/.claude/skills/house-style/scripts/check-terminology.py b/.claude/skills/house-style/scripts/check-terminology.py index 54dc96258..c6a39b8b8 100755 --- a/.claude/skills/house-style/scripts/check-terminology.py +++ b/.claude/skills/house-style/scripts/check-terminology.py @@ -144,6 +144,8 @@ def term(canonical, aliases=(), plural="", wrong=()): "kubeadm", "minikube", "Kustomize", + "Kubebuilder", + term("OLM", ("Operator Lifecycle Manager",)), "Karpenter", "Istio", "Argo CD", diff --git a/.cspell.json b/.cspell.json new file mode 100644 index 000000000..fd32df517 --- /dev/null +++ b/.cspell.json @@ -0,0 +1,277 @@ +{ + "ignorePaths": [ + "**/node_modules/**", + "**/vscode-extension/**", + "**/.git/**", + "**/.pnpm-lock.json", + ".vscode", + "megalinter", + "package-lock.json", + "report" + ], + "language": "en", + "noConfigSearch": true, + "words": [ + "AArch64", + "AKS", + "AlmaLinux", + "AMD", + "ANA", + "Ansible", + "API", + "ARM", + "ARM64", + "AWS", + "Azure", + "bdev", + "blkid", + "blockdev", + "btrfs", + "CentOS", + "Ceph", + "CIDR", + "Cinder", + "CLI", + "CNI", + "containerd", + "cordoned", + "CPU", + "CRD", + "crds", + "CRI-O", + "cryptsetup", + "CSI", + "CSV", + "Debian", + "DevStack", + "DHCHAP", + "DHCP", + "dmsetup", + "DNS", + "Docker", + "DPDK", + "drainable", + "EBS", + "EC2", + "EKS", + "Elasticsearch", + "endpointslice", + "engelbert", + "ESXi", + "exfat", + "exportfs", + "ext4", + "failback", + "Fedora", + "finalizer", + "FoundationDB", + "FreeBSD", + "frontends", + "FTT", + "FTT+1", + "GCP", + "GitHub", + "GitLab", + "GKE", + "goroutines", + "GPU", + "Grafana", + "Graylog", + "gRPC", + "GRUB", + "hashicorp", + "HashiCorp", + "HBA", + "HDD", + "Helm", + "hostid", + "hostnqn", + "HTTP", + "HTTPS", + "hugepage", + "hugepages", + "Hyper-V", + "IAM", + "InfiniBand", + "Intel", + "IO", + "IOPS", + "IP", + "iptables", + "IPv4", + "IPv6", + "iSCSI", + "Istio", + "Java", + "JavaScript", + "JBOD", + "Jira", + "JSON", + "JWT", + "Kafka", + "Karpenter", + "Keystone", + "kubeadm", + "kubectl", + "kubelet", + "Kubernetes", + "Kustomize", + "kvdo", + "KVM", + "labelone", + "LACP", + "LDAP", + "libvirt", + "Linux", + "Loki", + "LUN", + "lvcreate", + "LVM", + "lvol", + "lvols", + "lvrename", + "LXC", + "macOS", + "Markdown", + "megalinter", + "minikube", + "MinIO", + "MkDocs", + "mkswap", + "MLAG", + "MongoDB", + "mortems", + "mTLS", + "MTU", + "MySQL", + "nftables", + "NIC", + "noctarius", + "Node.js", + "Nova", + "NQN", + "nqn.2023-02.io.simplyblock", + "nsenter", + "NSID", + "numa", + "NVIDIA", + "nvme", + "NVMe-oF", + "nvme-tcp", + "NVMe/RDMA", + "NVMe/TCP", + "NVMeoF", + "nvmet", + "NVMf", + "OIDC", + "OpenBao", + "openbao", + "OpenSearch", + "OpenShift", + "OpenStack", + "openSUSE", + "oxsecurity", + "PCIe", + "pnfs", + "Podman", + "Postbrain", + "PostgreSQL", + "Prometheus", + "Proxmox", + "PVC", + "pvcreate", + "pvscan", + "Python", + "QEMU", + "QoS", + "RAID", + "RAM", + "Rancher", + "RBAC", + "RDMA", + "rebalances", + "reconstructable", + "Redis", + "relpair", + "relslot", + "reparent", + "reparented", + "reparenting", + "reparents", + "replops", + "repointed", + "rescan", + "RHEL", + "RoCE", + "RPO", + "RTO", + "Rust", + "SAML", + "SAS", + "SATA", + "scops", + "SCSI", + "SDK", + "sgdisk", + "simplyblock", + "Slack", + "snops", + "SPDK", + "spdkcsi", + "SSD", + "SSH", + "SSL", + "storageclusterops", + "storagenode", + "storagenodeops", + "storagenodeset", + "subresources", + "superblock", + "SUSE", + "sysfs", + "systemd", + "Talos", + "TCP", + "TCP/IP", + "Terraform", + "Thanos", + "TLS", + "TOML", + "TypeScript", + "Ubuntu", + "udev", + "UDP", + "uncordoned", + "Unix", + "unplaceable", + "unvalidated", + "URI", + "URL", + "UUID", + "Vault", + "vCPU", + "vdo", + "vfat", + "vgchange", + "vgcreate", + "vgimportclone", + "VLAN", + "vmig", + "vmnet", + "VMware", + "volstack", + "VPC", + "vSphere", + "XFS", + "XML", + "YAML", + "yamux", + "ZFS" + ], + "version": "0.2", + "ignoreRegExpList": [ + "/^ {0,3}```[\\s\\S]*?^ {0,3}```/gm", + "/^ {0,3}~~~[\\s\\S]*?^ {0,3}~~~/gm", + "/`[^`\\n]+`/g" + ] +} diff --git a/.github/workflows/mega_linter.yml b/.github/workflows/mega_linter.yml new file mode 100644 index 000000000..d3c500360 --- /dev/null +++ b/.github/workflows/mega_linter.yml @@ -0,0 +1,91 @@ +# Runs MegaLinter, the aggregate lint gate covering every language in this +# repository at once. What it enables and what it ignores lives in +# .mega-linter.yml at the root, not here. This file only says when it runs and +# what it is allowed to do with what it finds. +# +# It reports and does not write. The MegaLinter template ships with a bot that +# commits reformats back to the branch, and that bot is deliberately absent +# here, so a finding is fixed by whoever owns the code. +# More info at https://megalinter.io +--- +name: MegaLinter + +# A full-codebase run takes tens of minutes, so it is spent on the two places a +# result is acted on: the merge target, and the pull request proposing a merge +# into it. +on: + push: + branches: + - main + + pull_request: + branches: + - main + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +permissions: {} + +jobs: + megalinter: + name: MegaLinter + runs-on: ubuntu-latest + + # Read the code, and say what is wrong with it in the checks tab. Nothing + # here commits, comments, or opens a pull request, so nothing here needs + # write access. + permissions: + contents: read + + steps: + - name: Checkout Code + uses: actions/checkout@v6 + with: + persist-credentials: false + + # VALIDATE_ALL_CODEBASE is true below, so the history a diff would + # need is not fetched. + fetch-depth: 1 + + - name: MegaLinter + + # You can override MegaLinter flavor used to have faster performances + # More info at https://megalinter.io/latest/flavors/ + uses: oxsecurity/megalinter@v9 + + id: ml + + # All available variables are described in documentation + # https://megalinter.io/latest/config-file/ + env: + # Both triggers resolve to the same question. Is main clean, and + # would this pull request keep it clean? A partial answer to that is + # worth less than the minutes it saves. + VALIDATE_ALL_CODEBASE: true + + # Disable LLM Advisor for bot PRs (dependabot, renovate, etc.) + LLM_ADVISOR_ENABLED: >- + ${{ + github.event_name != 'pull_request' || + (github.event.pull_request.user.login != 'dependabot[bot]' && + github.event.pull_request.user.login != 'renovate[bot]' && + github.event.pull_request.user.login != 'github-actions[bot]' && + !startsWith(github.event.pull_request.user.login, 'dependabot') && + !startsWith(github.event.pull_request.user.login, 'renovate')) + }} + + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # The reports carry the detail the log summary elides: which file, which + # rule, and the SARIF a viewer can read. + - name: Archive production artifacts + uses: actions/upload-artifact@v7 + if: success() || failure() + with: + name: MegaLinter reports + include-hidden-files: "true" + path: | + megalinter-reports + mega-linter.log diff --git a/.gitignore b/.gitignore index a5821d427..dd41a144f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,8 @@ local.mk # An sbcli checkout for the OpenAPI export, where CI puts it. /.sbcli/ + +# MegaLinter's run output, written into the workspace by both the CI job and a +# local `docker run oxsecurity/megalinter` invocation. +megalinter-reports/ +mega-linter.log diff --git a/.jscpd.json b/.jscpd.json new file mode 100644 index 000000000..d69d630fb --- /dev/null +++ b/.jscpd.json @@ -0,0 +1,26 @@ +{ + "threshold": 3, + "reporters": [ + "html", + "markdown" + ], + "ignore": [ + "**/node_modules/**", + "**/.git/**", + "**/.rbenv/**", + "**/.venv/**", + "**/*cache*/**", + "**/.github/**", + "**/.idea/**", + "**/report/**", + "helm-charts/charts/**", + "csi-driver/charts/**", + "operator/config/**", + "operator/dist/**", + "**/*.svg", + "**/*.gen.go", + "**/*.deepcopy.go", + "**/*_types.go", + "**/*_test.go" + ] +} diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 000000000..e8815241d --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,25 @@ +# markdownlint rules for the Markdown this gate still reads: the READMEs, the +# architecture notes, and csi-driver/docs. The design documents and test plans +# under operator/docs are excluded in .mega-linter.yml and answer to the house +# style gate instead. +# +# The defaults produced 663 findings, and 470 of them were one rule objecting to +# the pipe alignment of a single Helm values table. +--- +default: true + +# 370 findings, 370 of them in csi-driver/charts/README.md. MD060 defaults to +# the "aligned" style, which asks that every pipe in a table line up in the +# source. It fires once per pipe, so one wide values table accounts for the +# lot. The rendered output is identical either way. +MD060: false + +# 69 findings. A tab inside a fenced code block is part of the sample, because +# shell and Makefile snippets need them, and reindenting it changes what the +# reader is being shown. Tabs in prose are still flagged. +MD010: + code_blocks: false + +# Long lines are how a table or a URL is written, and prose wrapping is not +# enforced anywhere else in this repository either. +MD013: false diff --git a/.mega-linter.yml b/.mega-linter.yml new file mode 100644 index 000000000..76864149c --- /dev/null +++ b/.mega-linter.yml @@ -0,0 +1,114 @@ +# Configuration file for MegaLinter, the aggregate lint gate this repository +# runs over every language it holds at once. What it enables, what it refuses to +# look at, and why each choice was made is recorded here. The per-linter rule +# sets live in the tool-native files beside it (revive.toml, .yamllint.yml, +# .markdownlint.yaml, .cspell.json, and .jscpd.json). +# +# See all available variables at https://megalinter.io/latest/config-file/ and in +# linters documentation +--- +# Report only. This gate is being adopted against a codebase that had never run +# it, so nothing here rewrites a file: findings are read, triaged, and fixed by +# hand. Revisit once the counts are down and the fixes are boring. +APPLY_FIXES: none + +# If you use ENABLE variable, all other languages/formats/tooling-formats will +# be disabled by default +# ENABLE: + +# If you use ENABLE_LINTERS variable, all other linters will be disabled by +# default +# ENABLE_LINTERS: + +DISABLE_LINTERS: + # Go is linted by revive here, and by nothing else. golangci-lint already runs + # per module in repo_lint.yaml, where each module's own .golangci.yml is in + # scope. MegaLinter would have to run it from the workspace root, where + # neither config applies. That run enables revive too, with comment-spacings + # and import-shadowing, so revive.toml is what this gate adds on top of it. + - GO_GOLANGCI_LINT + + # Python is linted once, formatted once, type-checked once, and scanned once. + # ruff subsumes flake8 and isort, and mypy answers the same question pyright + # does. black and bandit stay. Seven overlapping tools over seventeen files + # bought nothing but disagreement. + - PYTHON_FLAKE8 + - PYTHON_ISORT + - PYTHON_PYLINT + - PYTHON_PYRIGHT + + # yamllint is the single YAML gate. Prettier's YAML opinions contradict + # yamllint's indentation rules, so with both enabled one of them is always + # wrong, and it cost 323 seconds of the first full run to say so. + - YAML_PRETTIER + + # The only Ruby in this repository is csi-driver/scripts/ci/mdl_rules.rb, a + # rule DSL for mdl rather than a program. rubocop spent 133 seconds on it. + - RUBY_RUBOCOP + + # Vulnerability and secret scanning already have owners: operator_security.yaml + # and csi_security.yaml both run trivy and grype, and kingfisher, secretlint, + # and trufflehog stay enabled below to cover secrets. KICS overlaps checkov, + # which is kept. + - REPOSITORY_GRYPE + - REPOSITORY_TRIVY + - REPOSITORY_TRIVY_SBOM + - REPOSITORY_DUSTILOCK + - REPOSITORY_GITLEAKS + - REPOSITORY_KICS + +# DISABLE: +# - COPYPASTE # Uncomment to disable checks of excessive copy-pastes +# - SPELL # Uncomment to disable checks of spelling mistakes + +SHOW_ELAPSED_TIME: true + +# Paths no one edits by hand, and paths no general-purpose linter can read: +# +# helm-charts/charts// published release output +# helm-charts/charts/index.yaml the repository index beside it +# .../simplyblock-operator/crds/ regenerated by `make helm-sync` +# .../simplyblock-operator/charts/ vendored dependency charts +# .../simplyblock-operator/templates/ Helm templates, which are not YAML +# until they are rendered; helm_lint.yaml +# is their gate +# csi-driver/charts/spdk-csi/ excluded whole: it holds nothing but +# released versions, `latest`, and the +# index, and the chart source is elsewhere +# operator/config/ kubebuilder output, regenerated by +# `make -C operator manifests generate` +# operator/dist/ the consolidated installer, generated +# shared/openapi.json exported by shared/export-openapi.py and +# resynced by repo_openapi_sync.yaml +# operator/docs/ design documents and test plans, held to +# the house style gate instead, whose rules +# are this repository's own and disagree +# with markdownlint's defaults +# .claude/ agent tooling, not shipped code, and +# .claude/worktrees/ holds whole checkouts +# of this repository that would otherwise be +# scanned again as if they were source +# +# One regex, not a list. The configuration schema advertises both forms, but +# MegaLinter 9.4.0 accepts only a string: it concatenates the global value into +# a log message, and passes a per-linter value straight to re.compile. Both +# raise a TypeError on a list. Matched with re.search against each path, so the +# trailing slashes anchor a pattern to a directory rather than to a prefix of a +# sibling's name. +FILTER_REGEX_EXCLUDE: '(helm-charts/charts/([0-9]+\.[0-9]+\.[0-9]+/|index\.yaml|simplyblock-operator/(crds|charts|templates)/)|csi-driver/charts/spdk-csi/|operator/(config|dist|docs)/|shared/openapi\.json|\.claude/)' + +# cspell reads prose. Pointed at Go it reports the import aliases the Kubernetes +# ecosystem is built from (metav, corev, simplyblockv, apimachinery) by the +# thousand, which is a dictionary of this repository's own vocabulary rather +# than a list of mistakes. Identifiers are covered by revive's naming rules, and +# repo_codespell.yaml spell-checks the whole tree independently. +SPELL_CSPELL_FILE_EXTENSIONS: + - .md + - .txt + +# Report without blocking, for now. The first clean run left roughly 1,880 +# findings across the tree, so a gate that failed on them would fail every pull +# request from the day it landed and teach everyone to ignore it. It reports +# into the checks tab and the uploaded artifact instead. Remove this line once +# the backlog is worked down. That is the point at which this becomes a gate. +DISABLE_ERRORS: true diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 000000000..234a6ec56 --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,32 @@ +# yamllint rules for this repository, read by MegaLinter's YAML_YAMLLINT, which +# is the only YAML gate here. Prettier is disabled in .mega-linter.yml because +# its indentation opinions contradict these. +# +# The defaults produced 885 findings over the tree, and 749 of them came from +# three rules that encode a house style this repository does not hold. Those +# three are relaxed below. Everything else is left at its default, including +# the whitespace and syntax rules that catch real breakage. +--- +extends: default + +rules: + # 624 findings. yamllint's default insists a sequence be indented under its + # key, which Kubernetes manifests conventionally do not do. `consistent` still + # catches a file that changes its mind halfway through. + indentation: + spaces: consistent + indent-sequences: consistent + + # 101 findings. A leading `---` matters in a multi-document stream and is + # noise in the single-document files that make up nearly all of this tree. + document-start: disable + + # 24 findings, every one of them a GitHub workflow's `on:` key, which YAML 1.1 + # reads as the boolean true. The values are still checked. + truthy: + check-keys: false + + # 20 findings. Generated and hand-written manifests both carry long image + # references and annotations that no wrapping improves. + line-length: + max: 160 diff --git a/revive.toml b/revive.toml new file mode 100644 index 000000000..183bfd3f5 --- /dev/null +++ b/revive.toml @@ -0,0 +1,57 @@ +# revive rules for the Go in this repository, read by MegaLinter's GO_REVIVE. +# +# revive's default set is golint's, and golint's central demand is that every +# package and every exported symbol carry a doc comment. Against this tree that +# is 713 of 917 findings, almost all of them in internal packages whose readers +# are the four people who work here. The rules kept below are the ones that +# catch a mistake rather than a missing sentence. +# +# What is deliberately absent, and why: +# +# package-comments 340 findings, plus 26 on the comment's exact wording +# exported 347 findings, on symbols in internal packages +# unused-parameter 101 findings, most of them on interface implementations +# that must accept an argument they have no use for +# +# operator/.golangci.yml and csi-driver/.golangci.yml run revive too, with +# comment-spacings and import-shadowing. Those two are not repeated here. + +ignoreGeneratedHeader = false +severity = "error" +confidence = 0.8 + +# Naming. These are the findings worth having: Id where ID is meant, ALL_CAPS +# carried over from another language, and package names that shadow the +# standard library. +[rule.var-naming] +[rule.receiver-naming] +[rule.time-naming] +[rule.error-naming] + +# Correctness and clarity. +[rule.blank-imports] +[rule.context-as-argument] +[rule.context-keys-type] +[rule.empty-block] +[rule.error-return] +[rule.error-strings] +[rule.errorf] +[rule.increment-decrement] +[rule.indent-error-flow] +[rule.range] +[rule.superfluous-else] +[rule.unexported-return] +[rule.unreachable-code] +[rule.var-declaration] + +# min and max became builtins in Go 1.21, and a package-level helper of either +# name now shadows one. Ten of those are live in this tree. +[rule.redefines-builtin-id] + +# Ginkgo and Gomega are dot-imported by design: a spec reads as prose only when +# Describe, It, and Expect are unqualified. Every other dot import is a finding. +[rule.dot-imports] +arguments = [{ allowedPackages = [ + "github.com/onsi/ginkgo/v2", + "github.com/onsi/gomega", +] }] From 30d174a5c3aaeb9f42da7db9537b6e4cd2bbc53f Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Thu, 10 Sep 2026 19:19:26 +0200 Subject: [PATCH 02/19] docs: bring the READMEs to the house style The findings MegaLinter and the house style gate reported between them, in the six files they reported them in. Both gates were run against these files for the first time, so all of this predates them. What the house style gate rewrote, most of it in the CSI chart's parameter table, where a description column had been written in lowercase throughout: - Product names get the spelling their owner uses: Docker, CSI, RBAC, API, URI, RAID, CPU, Kubernetes, SPDK, NVMe PCIe, Kubebuilder, and Kustomize. - The brand is lowercase mid-sentence, which is fifteen occurrences of "Simplyblock" in prose that is not a product name. - American English: "labelled" becomes "labeled", "authorisation" becomes "authorization". - "e.g." takes a comma, "low latency" is hyphenated before the noun it describes, and "NVMe-over-Fabrics" is written "NVMe over Fabrics". Two came from cspell rather than the house style gate, which is the argument for keeping both: - "snasphot" was a plain typo, and the house style gate's misspelling list does not carry it. - "a externallyManagedConfigmap" is now "an `externallyManagedConfigmap`". The backticks are the fix: the house style exempts an identifier written in them, and this one is the parameter the row documents. The Helm value `externallyManagedConfigmap` keeps its spelling. Kubernetes writes ConfigMap, so the name is wrong, but it is a chart parameter users have in their values files and renaming it is a migration rather than a typo fix. Table alignment and the list-item dashes in these files are left alone. Forty findings of that kind predate this change, none of them on a line it touches, and reflowing tables nobody is editing would bury the change above. --- AGENTS.md | 2 +- csi-driver/README.md | 4 +- csi-driver/charts/README.md | 132 ++++++++++++++++++------------------ helm-charts/README.md | 6 +- operator/ARCHITECTURE.md | 22 +++--- operator/README.md | 12 ++-- 6 files changed, 89 insertions(+), 89 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a024ba484..cc27f470c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,7 +7,7 @@ version: | Component | Directory | Role | |-------------|----------------|----------------------------------------------------------------------------| | atlas-lib | `atlas-lib/` | Shared library: node-level storage primitives and the control-plane client | -| operator | `operator/` | The kubebuilder operator: CRDs, reconcilers, webhooks | +| operator | `operator/` | The Kubebuilder operator: CRDs, reconcilers, webhooks | | csi-driver | `csi-driver/` | The `spdkcsi` CSI driver | | helm-charts | `helm-charts/` | Chart sources; `charts/simplyblock-operator` is the development chart | diff --git a/csi-driver/README.md b/csi-driver/README.md index f6a4d2591..67a95de58 100644 --- a/csi-driver/README.md +++ b/csi-driver/README.md @@ -1,6 +1,6 @@ # Simplyblock CSI Driver -**High-performance NVMe/TCP (NVMe-over-Fabrics) CSI driver for Kubernetes** +**High-performance NVMe/TCP (NVMe over Fabrics) CSI driver for Kubernetes** ![](../assets/simplyblock-logo.svg) @@ -34,7 +34,7 @@ features like: | Feature | Benefit | |----------------------------------|-------------------------------------------------------------------------| | **Dynamic Volume Provisioning** | Dynamically provision and lifecycle-manage persistent volumes in Kubernetes | -| **NVMe/TCP Support** | High throughput, low latency storage over standard Ethernet | +| **NVMe/TCP Support** | High throughput, low-latency storage over standard Ethernet | | **Snapshots & Clones** | Efficient data protection and instant provisioning | | **Erasure Coding** | Fault-tolerant, space-efficient redundancy | | **Multi-tenancy & QoS** | Isolated tenants with guaranteed IOPS, bandwidth, and latency | diff --git a/csi-driver/charts/README.md b/csi-driver/charts/README.md index b739c98c7..b07751e4d 100644 --- a/csi-driver/charts/README.md +++ b/csi-driver/charts/README.md @@ -62,55 +62,55 @@ The following table lists the configurable parameters of the latest Simplyblock | `image.csi.repository` | simplyblock-csi-driver image | `simplyblock/spdkcsi` | | `image.csi.tag` | simplyblock-csi-driver image tag | `v0.1.0` | | `image.csi.pullPolicy` | simplyblock-csi-driver image pull policy | `Always` | -| `image.csiProvisioner.repository` | csi-provisioner docker image | `registry.k8s.io/sig-storage/csi-provisioner` | -| `image.csiProvisioner.tag` | csi-provisioner docker image tag | `v4.0.1` | +| `image.csiProvisioner.repository` | csi-provisioner Docker image | `registry.k8s.io/sig-storage/csi-provisioner` | +| `image.csiProvisioner.tag` | csi-provisioner Docker image tag | `v4.0.1` | | `image.csiProvisioner.pullPolicy` | csi-provisioner image pull policy | `Always` | -| `image.csiAttacher.repository` | csi-attacher docker image | `gcr.io/k8s-staging-sig-storage/csi-attacher` | -| `image.csiAttacher.tag` | csi-attacher docker image tag | `v4.5.1` | +| `image.csiAttacher.repository` | csi-attacher Docker image | `gcr.io/k8s-staging-sig-storage/csi-attacher` | +| `image.csiAttacher.tag` | csi-attacher Docker image tag | `v4.5.1` | | `image.csiAttacher.pullPolicy` | csi-attacher image pull policy | `Always` | -| `image.nodeDriverRegistrar.repository` | csi-node-driver-registrar docker image | `registry.k8s.io/sig-storage/csi-node-driver-registrar` | -| `image.nodeDriverRegistrar.tag` | csi-node-driver-registrar docker image tag | `v2.10.1` | +| `image.nodeDriverRegistrar.repository` | csi-node-driver-registrar Docker image | `registry.k8s.io/sig-storage/csi-node-driver-registrar` | +| `image.nodeDriverRegistrar.tag` | csi-node-driver-registrar Docker image tag | `v2.10.1` | | `image.nodeDriverRegistrar.pullPolicy` | csi-node-driver-registrar image pull policy | `Always` | -| `image.csiSnapshotter.repository` | csi-snapshotter docker image | `registry.k8s.io/sig-storage/csi-snapshotter` | -| `image.csiSnapshotter.tag` | csi-snapshotter docker image tag | `v8.2.0` | +| `image.csiSnapshotter.repository` | csi-snapshotter Docker image | `registry.k8s.io/sig-storage/csi-snapshotter` | +| `image.csiSnapshotter.tag` | csi-snapshotter Docker image tag | `v8.2.0` | | `image.csiSnapshotter.pullPolicy` | csi-snapshotter image pull policy | `Always` | -| `image.csiSnapshotterController.repository` | csi-snapshotter-controller docker image | `registry.k8s.io/sig-storage/snapshot-controller` | -| `image.csiSnapshotterController.tag` | csi-snapshotter-controller docker image tag | `v8.2.0` | +| `image.csiSnapshotterController.repository` | csi-snapshotter-controller Docker image | `registry.k8s.io/sig-storage/snapshot-controller` | +| `image.csiSnapshotterController.tag` | csi-snapshotter-controller Docker image tag | `v8.2.0` | | `image.csiSnapshotterController.pullPolicy` | csi-snapshotter-controller image pull policy | `Always` | -| `image.csiResizer.repository` | csi-resizer docker image | `gcr.io/k8s-staging-sig-storage/csi-resizer` | -| `image.csiResizer.tag` | csi-resizer docker image tag | `v1.10.1` | +| `image.csiResizer.repository` | csi-resizer Docker image | `gcr.io/k8s-staging-sig-storage/csi-resizer` | +| `image.csiResizer.tag` | csi-resizer Docker image tag | `v1.10.1` | | `image.csiResizer.pullPolicy` | csi-resizer image pull policy | `Always` | -| `image.csiHealthMonitor.repository` | csi-external-health-monitor-controller docker image | `gcr.io/k8s-staging-sig-storage/csi-external-health-monitor-controller` | -| `image.csiHealthMonitor.tag` | csi-external-health-monitor-controller docker image tag | `v0.11.0` | +| `image.csiHealthMonitor.repository` | csi-external-health-monitor-controller Docker image | `gcr.io/k8s-staging-sig-storage/csi-external-health-monitor-controller` | +| `image.csiHealthMonitor.tag` | csi-external-health-monitor-controller Docker image tag | `v0.11.0` | | `image.csiHealthMonitor.pullPolicy` | csi-external-health-monitor-controller image pull policy | `Always` | -| `image.simplyblock.repository` | simplyblock mgmt docker image | `simplyblock/simplyblock` | -| `image.simplyblock.tag` | simplyblock mgmt docker image tag | `R25.5-Hotfix` | +| `image.simplyblock.repository` | simplyblock mgmt Docker image | `simplyblock/simplyblock` | +| `image.simplyblock.tag` | simplyblock mgmt Docker image tag | `R25.5-Hotfix` | | `image.simplyblock.pullPolicy` | csi-snapshotter image pull policy | `Always` | -| `image.storageNode.repository` | simplyblock storage-node controller docker image | `simplyblock/simplyblock` | -| `image.storageNode.tag` | simplyblock storage-node controller docker image tag | `v0.1.0` | +| `image.storageNode.repository` | simplyblock storage-node controller Docker image | `simplyblock/simplyblock` | +| `image.storageNode.tag` | simplyblock storage-node controller Docker image tag | `v0.1.0` | | `image.storageNode.pullPolicy` | simplyblock storage-node controller image pull policy | `Always` | -| `image.mgmtAPI.repository` | simplyblock mgmt api image | `python` | -| `image.mgmtAPI.tag` | simplyblock mgmt api image tag | `3.10` | -| `image.mgmtAPI.pullPolicy` | simplyblock mgmt api image pull policy | `Always` | +| `image.mgmtAPI.repository` | simplyblock mgmt API image | `python` | +| `image.mgmtAPI.tag` | simplyblock mgmt API image tag | `3.10` | +| `image.mgmtAPI.pullPolicy` | simplyblock mgmt API image pull policy | `Always` | | `serviceAccount.create` | whether to create service account of spdkcsi-controller | `true` | -| `rbac.create` | whether to create rbac of spdkcsi-controller | `true` | +| `rbac.create` | whether to create RBAC of spdkcsi-controller | `true` | | `controller.replicas` | replica number of spdkcsi-controller | `1` | -| `serviceAccount.create` | whether to create service account of csi controller | `true` | -| `rbac.create` | whether to create rbac of csi controller | `true` | -| `controller.replicas` | replica number of csi controller | `1` | -| `controller.tolerations.create` | Whether to create tolerations for the csi controller | `false` | | -| `controller.tolerations.list[0].effect` | The effect of tolerations on the csi controller | `` | | -| `controller.tolerations.list[0].key ` | The key of tolerations for the csi controller | `` | | -| `controller.tolerations.list[0].operator ` | The operator for the csi controller tolerations | `Exists` | | -| `controller.tolerations.list[0].value ` | The value of tolerations for the csi controller | `` | | -| `controller.nodeSelector.create` | Whether to create nodeSelector for the csi controller | `false` | | -| `controller.nodeSelector.key ` | The key of nodeSelector for the csi controller | `` | | -| `controller.nodeSelector.value ` | The value of nodeSelector for the csi controller | `` | | +| `serviceAccount.create` | whether to create service account of CSI controller | `true` | +| `rbac.create` | whether to create RBAC of CSI controller | `true` | +| `controller.replicas` | replica number of CSI controller | `1` | +| `controller.tolerations.create` | Whether to create tolerations for the CSI controller | `false` | | +| `controller.tolerations.list[0].effect` | The effect of tolerations on the CSI controller | `` | | +| `controller.tolerations.list[0].key ` | The key of tolerations for the CSI controller | `` | | +| `controller.tolerations.list[0].operator ` | The operator for the CSI controller tolerations | `Exists` | | +| `controller.tolerations.list[0].value ` | The value of tolerations for the CSI controller | `` | | +| `controller.nodeSelector.create` | Whether to create nodeSelector for the CSI controller | `false` | | +| `controller.nodeSelector.key ` | The key of nodeSelector for the CSI controller | `` | | +| `controller.nodeSelector.value ` | The value of nodeSelector for the CSI controller | `` | | | `storageclass.create` | create storageclass | `true` | | | `storageclass.annotations` | Annotations attached to the created StorageClass. If simplyblock.io/auto-restart-on-pathloss: "true" is set, pods using PVCs from this StorageClass will be automatically restarted when the storage paths to the volume are lost and later restored. {} | `{}` | | | `snapshotclass.create` | create snapshotclass | `true` | | -| `snapshotcontroller.create` | create snapshot controller and CRD for snasphot support it | `true` | | -| `externallyManagedConfigmap.create` | Specifies whether a externallyManagedConfigmap should be created | `true` | | +| `snapshotcontroller.create` | create snapshot controller and CRD for snapshot support it | `true` | | +| `externallyManagedConfigmap.create` | Specifies whether an `externallyManagedConfigmap` should be created | `true` | | | `externallyManagedSecret.create` | Specifies whether a externallyManagedSecret should be created | `true` | | | `csiConfig.simplybk.uuid` | the simplyblock cluster UUID on which the volumes are provisioned | `` | | | `csiConfig.simplybk.ip` | the HTTPS API Gateway endpoint connected to the management node | `https://o5ls1ykzbb.execute-api.eu-central-1.amazonaws.com` | | @@ -121,20 +121,20 @@ The following table lists the configurable parameters of the latest Simplyblock | `logicalVolume.qos_r_mbytes` | the value of lvol parameter qos_r_mbytes | `0` | | | `logicalVolume.qos_w_mbytes` | the value of lvol parameter qos_w_mbytes | `0` | | | `logicalVolume.encryption` | set to `True` if encryption needs be enabled on lvols. | `False` | | -| `logicalVolume.numDataChunks` | The number of Erasure coding schema parameter k (distributed raid) | `1` | | -| `logicalVolume.numParityChunks` | The number of Erasure coding schema parameter n (distributed raid) | `1` | | +| `logicalVolume.numDataChunks` | The number of Erasure coding schema parameter k (distributed RAID) | `1` | | +| `logicalVolume.numParityChunks` | The number of Erasure coding schema parameter n (distributed RAID) | `1` | | | `logicalVolume.max_namespace_per_subsys` | the maximum namespace per subsystem | `1` | | | `podAnnotations` | Annotations to apply to all pods in the chart | `{}` | | -| `simplyBlockAnnotations` | Annotations to apply to Simplyblock kubernetes resources like DaemonSets, Deployments, or StatefulSets | `{}` | | +| `simplyBlockAnnotations` | Annotations to apply to simplyblock Kubernetes resources like DaemonSets, Deployments, or StatefulSets | `{}` | | | `benchmarks` | the number of benchmarks to run | `0` | | -| `node.tolerations.create` | Whether to create tolerations for the csi node | `false` | | -| `node.tolerations.list[0].effect` | The effect of tolerations on the csi node | `` | | -| `node.tolerations.list[0].key ` | The key of tolerations for the csi node | `` | | -| `node.tolerations.list[0].operator ` | The operator for the csi node tolerations | `Exists` | | -| `node.tolerations.list[0].value ` | The value of tolerations for the csi node | `` | | -| `node.nodeSelector.create` | Whether to create nodeSelector for the csi node | `false` | | -| `node.nodeSelector.key ` | The key of nodeSelector for the csi node | `` | | -| `node.nodeSelector.value ` | The value of nodeSelector for the csi node | `` | | +| `node.tolerations.create` | Whether to create tolerations for the CSI node | `false` | | +| `node.tolerations.list[0].effect` | The effect of tolerations on the CSI node | `` | | +| `node.tolerations.list[0].key ` | The key of tolerations for the CSI node | `` | | +| `node.tolerations.list[0].operator ` | The operator for the CSI node tolerations | `Exists` | | +| `node.tolerations.list[0].value ` | The value of tolerations for the CSI node | `` | | +| `node.nodeSelector.create` | Whether to create nodeSelector for the CSI node | `false` | | +| `node.nodeSelector.key ` | The key of nodeSelector for the CSI node | `` | | +| `node.nodeSelector.value ` | The value of nodeSelector for the CSI node | `` | | | `storagenode.daemonsets[0].name` | The name of the storage node DaemonSet | `storage-node-ds` | | | `storagenode.daemonsets[0].appLabel` | The label applied to the storage node DaemonSet for identification | `storage-node` | | | `storagenode.daemonsets[0].nodeSelector.key` | The key used in the nodeSelector to constrain which nodes the DaemonSet should run on | `io.simplyblock.node-type` | | @@ -153,10 +153,10 @@ The following table lists the configurable parameters of the latest Simplyblock | `storagenode.daemonsets[1].tolerations.list[0].key ` | the key of tolerations for the restart storage node | `` | | | `storagenode.daemonsets[1].tolerations.list[0].operator ` | the operator for the restart storage node tolerations | `Exists` | | | `storagenode.daemonsets[1].tolerations.list[0].value ` | the value of tolerations for the restart storage node | `` | | -| `storagenode.create` | Whether to create storage node on kubernetes worker node | `false` | | +| `storagenode.create` | Whether to create storage node on Kubernetes worker node | `false` | | | `storagenode.ifname` | the default interface to be used for binding the storage node to host interface | `eth0` | | -| `storagenode.spdkImage` | SPDK image uri for storage node | `` | | -| `storagenode.spdkProxyImage` | SPDK Proxy image uri for storage node | `` | | +| `storagenode.spdkImage` | SPDK image URI for storage node | `` | | +| `storagenode.spdkProxyImage` | SPDK Proxy image URI for storage node | `` | | | `storagenode.maxLogicalVolumes` | the default max lvol per storage node | `10` | | | `storagenode.maxSnapshots` | the default max snapshot per storage node | `10` | | | `storagenode.maxSize` | the max provisioning size of all storage nodes | `` | | @@ -166,15 +166,15 @@ The following table lists the configurable parameters of the latest Simplyblock | `storagenode.isolateCores` | Enable core Isolation | `false` | | | `storagenode.haJMCount` | the number of ha Journal managers | `` | | | `storagenode.dataNic` | Data interface name | `` | | -| `storagenode.pciAllowed` | the list of allowed nvme pcie addresses | `` | | -| `storagenode.pciBlocked` | the list of blocked nvme pcie addresses | `` | | +| `storagenode.pciAllowed` | the list of allowed NVMe PCIe addresses | `` | | +| `storagenode.pciBlocked` | the list of blocked NVMe PCIe addresses | `` | | | `storagenode.socketsToUse` | the list of sockets to use | `` | | | `storagenode.nodesPerSocket` | The number of nodes to use per socket | `` | | | `storagenode.deviceModel` | The NVMe SSD model to use (must be set together with `storagenode.sizeRange` ) | `` | | | `storagenode.sizeRange` | The NVMe SSD device size range separated by - (e.g: `500G-1T`) | `` | | -| `storagenode.coresPercentage` | The percentage of cores to be used for spdk | `` | | -| `storagenode.enableCpuTopology` | Whether to enable cpu topology for storage node on kubernetes worker node | `false` | | -| `storagenode.enableDevicePlugin` | Whether to enable Simplyblock NUMA resource device plugin (numa-resource-plugin) on Kubernetes cluster | `true` | | +| `storagenode.coresPercentage` | The percentage of cores to be used for SPDK | `` | | +| `storagenode.enableCpuTopology` | Whether to enable CPU topology for storage node on Kubernetes worker node | `false` | | +| `storagenode.enableDevicePlugin` | Whether to enable simplyblock NUMA resource device plugin (numa-resource-plugin) on Kubernetes cluster | `true` | | | `storagenode.skipKubeletConfiguration` | Skip configuring CPU topology in kubelet if it has already been configured manually | `false` | | | `storagenode.reservedSystemCpu` | the list of CPU cores reserved for the host/system (excluded from SPDK usage) | `` | | | `storagenode.numDevices` | the number of devices per storage node | `1` | | @@ -183,8 +183,8 @@ The following table lists the configurable parameters of the latest Simplyblock | `storagenode.enableTestDevice` | Enable creation of test device | `false` | | | `storagenode.ubuntuHost` | Set to true if the worker node runs Ubuntu and needs the nvme-tcp kernel module installed | `false` | | | `storagenode.multiCluster.enable` | Enable multi-cluster storage node support | `false` | | -| `storagenode.multiCluster.clusters[].cluster_id` | UUID of the Simplyblock cluster | `` | | -| `storagenode.multiCluster.clusters[].secret` | Secret of the Simplyblock cluster | `` | | +| `storagenode.multiCluster.clusters[].cluster_id` | UUID of the simplyblock cluster | `` | | +| `storagenode.multiCluster.clusters[].secret` | Secret of the simplyblock cluster | `` | | | `storagenode.multiCluster.clusters[].workers` | List of Kubernetes worker node names assigned to this cluster | `` | | | `storagenode.openShiftCluster` | Set to true if the worker node runs OpenShift and needs core isolation | `false` | | @@ -223,21 +223,21 @@ kubectl delete namespace simplyblk ## Controller parameters -The following table lists the configurable parameters of the latest Simplyblock Storage Controller chart and default values. +The following table lists the configurable parameters of the latest simplyblock Storage Controller chart and default values. | Parameter | Description | Default | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- | -| `image.storageNode.repository` | simplyblock storage-node controller docker image | `simplyblock/simplyblock` | -| `image.storageNode.tag` | simplyblock storage-node controller docker image tag | `v0.1.0` | +| `image.storageNode.repository` | simplyblock storage-node controller Docker image | `simplyblock/simplyblock` | +| `image.storageNode.tag` | simplyblock storage-node controller Docker image tag | `v0.1.0` | | `image.storageNode.pullPolicy` | simplyblock storage-node controller image pull policy | `Always` | -| `image.mgmtAPI.repository` | simplyblock mgmt api image | `python` | -| `image.mgmtAPI.tag` | simplyblock mgmt api image tag | `3.10` | -| `image.mgmtAPI.pullPolicy` | simplyblock mgmt api image pull policy | `Always` | +| `image.mgmtAPI.repository` | simplyblock mgmt API image | `python` | +| `image.mgmtAPI.tag` | simplyblock mgmt API image tag | `3.10` | +| `image.mgmtAPI.pullPolicy` | simplyblock mgmt API image pull policy | `Always` | | `serviceAccount.create` | whether to create service account of spdkcsi-controller | `true` | -| `rbac.create` | whether to create rbac of spdkcsi-controller | `true` | | -| `storagenode.create` | Whether to create storage node on kubernetes worker node | `false` | | -| `storagenode.ifname` | The interface(s) used for binding the storage node to the host network. Can be a single value (e.g. `eth0`) or a list of interfaces (e.g. `{eth0,eth1}`), in which case the order defines the priority. | `eth0` | | -| `storagenode.spdkImage` | SPDK image uri for storage node | `` | | +| `rbac.create` | whether to create RBAC of spdkcsi-controller | `true` | | +| `storagenode.create` | Whether to create storage node on Kubernetes worker node | `false` | | +| `storagenode.ifname` | The interface(s) used for binding the storage node to the host network. Can be a single value (e.g., `eth0`) or a list of interfaces (e.g., `{eth0,eth1}`), in which case the order defines the priority. | `eth0` | | +| `storagenode.spdkImage` | SPDK image URI for storage node | `` | | | `storagenode.maxSnap` | the default max snapshot per storage node | `10` | | | `storagenode.jmPercent` | the number in percent to use for JM from each device | `3` | | | `storagenode.numPartitions` | the number of partitions to create per device | `0` | | diff --git a/helm-charts/README.md b/helm-charts/README.md index 4c4acbaaa..077f9487d 100644 --- a/helm-charts/README.md +++ b/helm-charts/README.md @@ -1,6 +1,6 @@ # Simplyblock Helm Charts -The official Helm charts for deploying Simplyblock on Kubernetes. +The official Helm charts for deploying simplyblock on Kubernetes. ![](../assets/simplyblock-logo.svg) @@ -17,7 +17,7 @@ The official Helm charts for deploying Simplyblock on Kubernetes. ### Install the Simplyblock Operator -Add the Simplyblock Helm repository and install: +Add the simplyblock Helm repository and install: ```bash helm repo add simplyblock https://simplyblock.github.io/helm-charts/charts @@ -28,7 +28,7 @@ helm install simplyblock-operator simplyblock/simplyblock-operator \ --create-namespace ``` -After the Helm installation completes, wait for the Simplyblock control plane to be ready before creating custom resources such as `StorageCluster`, `Pool`, or `StorageNode`: +After the Helm installation completes, wait for the simplyblock control plane to be ready before creating custom resources such as `StorageCluster`, `Pool`, or `StorageNode`: ```bash kubectl -n simplyblock wait controlplane simplyblock \ diff --git a/operator/ARCHITECTURE.md b/operator/ARCHITECTURE.md index e47236335..f1d92ce9b 100644 --- a/operator/ARCHITECTURE.md +++ b/operator/ARCHITECTURE.md @@ -1,13 +1,13 @@ # simplyblock-operator Architecture Overview ## Purpose -`simplyblock-operator` is a Kubernetes operator that maps custom resources (CRs) to Simplyblock control-plane API operations. It reconciles desired state from Kubernetes into actual storage-system state and writes observed results back into CR status. +`simplyblock-operator` is a Kubernetes operator that maps custom resources (CRs) to simplyblock control-plane API operations. It reconciles desired state from Kubernetes into actual storage-system state and writes observed results back into CR status. ## High-Level Architecture 1. The Helm chart (external to this repository) installs the operator and creates a singleton `ControlPlane` CR that gates the rest of the system on control-plane readiness. -2. Users apply Simplyblock CRs in Kubernetes (cluster, node set, pool, backup, restore, import, policy, replication, task, volume migration). +2. Users apply simplyblock CRs in Kubernetes (cluster, node set, pool, backup, restore, import, policy, replication, task, volume migration). 3. Controller-runtime watches those CRs in the operator's namespace and triggers reconcilers. -4. Reconcilers resolve cluster auth from Kubernetes Secrets and call the Simplyblock Web API (`http://simplyblock-webappapi:5000` by default), optionally over mTLS. +4. Reconcilers resolve cluster auth from Kubernetes Secrets and call the simplyblock Web API (`http://simplyblock-webappapi:5000` by default), optionally over mTLS. 5. Reconcilers persist results to CR status fields and Kubernetes objects (Secrets, DaemonSets, Services/EndpointSlices, RBAC, StorageClasses, PodDisruptionBudgets, Jobs). ## Core Components @@ -16,7 +16,7 @@ - `cmd/simplyblock-rebalancer/` A standalone binary (built from its own `Dockerfile.simplyblock-rebalancer`, based on Red Hat UBI with `fio`, `nvme-cli`) that is decoupled from controller-runtime. It has three modes: - `--mode=baseline`: takes a trimmed-mean set of `fio` NVMe/TCP write-latency samples and writes a `{"p50_ns", "p99_ns"}` JSON result to a termination log (used by a per-node measurement Job). - - `--mode=probe`: long-running Prometheus exporter that watches a ConfigMap-backed node config and exposes `simplyblock_node_fio_write_latency_p50_ns` / `_p99_ns` gauges (labelled by `cluster`/`node`) on `:9199` (default). + - `--mode=probe`: long-running Prometheus exporter that watches a ConfigMap-backed node config and exposes `simplyblock_node_fio_write_latency_p50_ns` / `_p99_ns` gauges (labeled by `cluster`/`node`) on `:9199` (default). - `--mode=validate-migration`: connects and validates NVMe-oF paths (from `VMIG_CONNECTIONS`) during a volume migration; run as a Job/container by the `VolumeMigration` controller. - `api/v1alpha1/*` CRD Go types (spec + status) for: @@ -47,7 +47,7 @@ - API payload structs - resource builders (StorageNodeSet DaemonSet, headless Service + EndpointSlice, SPDK proxy Service/EndpointSlice, per-namespace RBAC, namespaced StorageClasses) - mTLS / cert-manager `ClusterIssuer` integration (`simplyblock-certificate-authority-issuer`) - - status/action constants and formatting helpers (e.g. `CSIProvisioner = "csi.simplyblock.io"`) + - status/action constants and formatting helpers (e.g., `CSIProvisioner = "csi.simplyblock.io"`) - `internal/rebalancer/*` Shared JSON schema types (`NodeConfig`, `LatencyResult`) exchanged between the operator and the `simplyblock-rebalancer` binary. @@ -56,7 +56,7 @@ Most reconcilers follow the same pattern: 1. Fetch CR. 2. Handle deletion and finalizers. 3. Resolve upstream identifiers and credentials (cluster UUID, pool UUID, secret token). -4. Execute API operation(s) against Simplyblock control plane. +4. Execute API operation(s) against simplyblock control plane. 5. Update CR status (and sometimes related K8s resources). 6. Requeue on transient dependency errors or eventually consistent states. @@ -88,7 +88,7 @@ Most controllers project an explicit `running/success/failed` action workflow in - `StorageBackupSync` (no CRD) Watches `StorageCluster` objects and creates missing `StorageBackup` CRs for backups discovered in the backend, labeled with `storage.simplyblock.io/imported`. - `NodeDrainCoordinator` (no CRD) - Coordinates Simplyblock storage-node shutdown and restart during Kubernetes node drain events (e.g., rolling OS upgrades), tracking per-node progress in `StorageNodeSet.status.drainCoordination` (`detected` → `shutdown_called` → `draining` → `restart_called` → `complete`, or `failed`). It detects cordoned nodes, manages per-node `PodDisruptionBudget` objects (`simplyblock-drain-`) to throttle drains, and enforces a `MaxFaultTolerance` gate on how many nodes may drain simultaneously. A self-PDB (`simplyblock-operator-self`) protects the operator pod while it sets up storage PDB protection on the same node. + Coordinates simplyblock storage-node shutdown and restart during Kubernetes node drain events (e.g., rolling OS upgrades), tracking per-node progress in `StorageNodeSet.status.drainCoordination` (`detected` → `shutdown_called` → `draining` → `restart_called` → `complete`, or `failed`). It detects cordoned nodes, manages per-node `PodDisruptionBudget` objects (`simplyblock-drain-`) to throttle drains, and enforces a `MaxFaultTolerance` gate on how many nodes may drain simultaneously. A self-PDB (`simplyblock-operator-self`) protects the operator pod while it sets up storage PDB protection on the same node. ## External Interfaces - Kubernetes API: @@ -98,27 +98,27 @@ Most controllers project an explicit `running/success/failed` action workflow in ## Security Model - API authentication is bearer-token based; the operator uses its own in-cluster service-account token, and cluster credentials live in Kubernetes Secrets resolved per reconcile loop. User identities are not propagated to the backend. -- mTLS between the operator and the Simplyblock control plane uses certificates issued by a cert-manager `ClusterIssuer` (`simplyblock-certificate-authority-issuer`), gated by `SB_TLS_SERVE`/`SB_TLS_CONNECT`. +- mTLS between the operator and the simplyblock control plane uses certificates issued by a cert-manager `ClusterIssuer` (`simplyblock-certificate-authority-issuer`), gated by `SB_TLS_SERVE`/`SB_TLS_CONNECT`. - Pool-level dhchap security and host allow-listing are configurable via the `Pool` CRD. - Controller RBAC is generated in `config/rbac/*` and scoped by controller needs; StorageNodeSet RBAC is created per operator namespace. - User authorization is delegated entirely to standard Kubernetes RBAC. Two aggregation ClusterRoles ship with the operator: `simplyblock-aggregate-to-view` (aggregates into `view`) and `simplyblock-aggregate-to-edit` (aggregates into `edit`/`admin`). See `README.md` for the full access-control model. ## Operational Endpoints - Health probe: `--health-probe-bind-address` (default `:8081`) -- Metrics endpoint: configurable secure/insecure binding via manager flags; the default kustomize deployment serves secure metrics on `:8443` and enables `--leader-elect`. +- Metrics endpoint: configurable secure/insecure binding via manager flags; the default Kustomize deployment serves secure metrics on `:8443` and enables `--leader-elect`. - Leader election: `--leader-elect` (enabled in the shipped manager manifest). - Rebalancer probe: Prometheus latency gauges on `:9199` (from the standalone `simplyblock-rebalancer` binary). ## Deployment Topology - Single manager deployment (`config/manager/manager.yaml`) runs all reconcilers in one process and is scoped to its own namespace. - The Helm chart (external to this repo) installs the operator, creates the singleton `ControlPlane` CR, and configures the cert-manager `ClusterIssuer` used for mTLS. This repository does not contain the chart or the cert-manager `ClusterIssuer`/`Certificate` manifests. -- The kustomize default overlay (`config/default`) enables the CRDs, RBAC, manager, and secure metrics service. The webhook (a `MutatingWebhookConfiguration` for the rebalancer sidecar injector, `simplyblock-rebalancer-injector.simplyblock.io`), the metrics `NetworkPolicy`, and the Prometheus `ServiceMonitor` are defined under `config/` but are disabled by default (and therefore absent from `dist/install.yaml`). +- The Kustomize default overlay (`config/default`) enables the CRDs, RBAC, manager, and secure metrics service. The webhook (a `MutatingWebhookConfiguration` for the rebalancer sidecar injector, `simplyblock-rebalancer-injector.simplyblock.io`), the metrics `NetworkPolicy`, and the Prometheus `ServiceMonitor` are defined under `config/` but are disabled by default (and therefore absent from `dist/install.yaml`). - `make build-installer` renders the full manifest set into `dist/install.yaml`. - Storage-node data-plane preparation is performed through a reconciled DaemonSet generated from the `StorageNodeSet` CR intent. - Multi-arch (amd64/arm64) images for the operator and the `simplyblock-rebalancer` are built via `docker-buildx` and pushed to DockerHub, ECR Public, and quay.io. The OLM bundle is generated via `make bundle`, published to quay.io, and attached to GitHub releases. Grype and Trivy vulnerability scanning (plus CycloneDX SBOM generation) run daily in CI (`security.yml`). ## Current Architectural Characteristics -- API-first orchestration: Kubernetes CRs are declarative frontends; actual storage operations are delegated to the external Simplyblock API. +- API-first orchestration: Kubernetes CRs are declarative frontends; actual storage operations are delegated to the external simplyblock API. - Status-centric feedback: each controller projects upstream state into CR status for observability. - Action workflows: most controllers include explicit action state machines in status (`running/success/failed`) to support long-running operations. - Backup/restore/replication are first-class concerns with cross-cluster import and policy-driven retention. diff --git a/operator/README.md b/operator/README.md index 6850aaf96..10622064c 100644 --- a/operator/README.md +++ b/operator/README.md @@ -79,7 +79,7 @@ the documentation for the supported, end-to-end installation flow — it wires u ## 🔐 Access Control (RBAC) -The operator delegates user authorisation entirely to standard Kubernetes RBAC. +The operator delegates user authorization entirely to standard Kubernetes RBAC. It does not ship per-CR `admin`/`editor`/`viewer` ClusterRoles or any identity-bearing fields on its CRs; cluster admins write `Role`s, `RoleBinding`s and `ClusterRoleBinding`s using the normal K8s primitives. @@ -96,11 +96,11 @@ backend, sets `status.status = "InvalidClusterReference"`, and emits a This converts "admin of cluster `foo`" into "admin of the namespace where StorageCluster `foo` lives" — a problem standard K8s RBAC already solves cleanly. The recommended layout is one namespace per logical storage cluster -(e.g. `cluster-prod`, `cluster-staging`). +(e.g., `cluster-prod`, `cluster-staging`). ### Aggregation into the built-in `view`/`edit`/`admin` roles -The operator installs two `ClusterRole`s labelled to aggregate into the +The operator installs two `ClusterRole`s labeled to aggregate into the standard Kubernetes ClusterRoles: | Operator ClusterRole | Aggregates into | Grants on simplyblock CRs | @@ -125,7 +125,7 @@ kubectl create rolebinding alice-admin \ ### Per-resource scoping with `resourceNames` -For finer-grained delegation — e.g. admin only of `StorageCluster/prod`, not +For finer-grained delegation — e.g., admin only of `StorageCluster/prod`, not any other `StorageCluster` in the same namespace — write a `Role` with `resourceNames`: @@ -164,7 +164,7 @@ subjects: > for `list`, `watch`, and `create`. A user with only the Role above can > `kubectl get storagecluster prod` (a named GET) but not > `kubectl get storagecluster` (a LIST) — they will need a separate, broader -> binding (e.g. the `view` ClusterRole) if you want them to enumerate. This is +> binding (e.g., the `view` ClusterRole) if you want them to enumerate. This is > a property of K8s RBAC, not the operator. ### Delegating who can create clusters and grant admin @@ -177,7 +177,7 @@ common patterns: aggregation role makes that work). To stop arbitrary users from creating namespaces, restrict `create namespaces` at the cluster scope. * **Gate by SA.** Reserve `create storageclusters` for a small set of service - accounts (e.g. your platform automation) and have them stand up tenant + accounts (e.g., your platform automation) and have them stand up tenant namespaces on demand. To let a "cluster owner" delegate admin to teammates *without* giving them From 6c5f82959155af3f197fd218627eb7b4b4e2a854 Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Thu, 10 Sep 2026 19:56:01 +0200 Subject: [PATCH 03/19] chore(csi-driver): delete config-gen-upgrade.sh, which never ran MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit shellcheck reported a parse error on the script, and the reason it could be parsed at all is that nothing has ever run it. `IMAGE_TAG=` is not an assignment. bash reads `` as a redirection and rejects the file, so `bash -n` fails on line 6 and the script exits before its first command. That is not a recent regression: the version added in a7d27703 carried `MAX_LVOL=` and `MAX_PROV=` on lines 9 and 10 and failed to parse just the same. No committed version of this file has ever been runnable. The placeholders do not make it a template either. It carries a `#!/bin/bash` shebang and the executable bit, no extension marking it as one, and no documentation anywhere saying which values to substitute. Nothing references it, on this branch or on main. What it did — run `node_configure.py --upgrade` in a per-node Job whose container it called `s-node-api-config-generator` — is now the operator's work: the storage node DaemonSet in operator/internal/utils/storage_nodeset_ds.go runs that script in a container of exactly that name. Its only two commits since it was added are a bulk label rename and the move into csi-driver/, so nothing has been maintaining it either. --- csi-driver/scripts/config-gen-upgrade.sh | 59 ------------------------ 1 file changed, 59 deletions(-) delete mode 100644 csi-driver/scripts/config-gen-upgrade.sh diff --git a/csi-driver/scripts/config-gen-upgrade.sh b/csi-driver/scripts/config-gen-upgrade.sh deleted file mode 100644 index a2e18dd18..000000000 --- a/csi-driver/scripts/config-gen-upgrade.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/bash - -LABEL_KEY="io.simplyblock.node-type" -LABEL_VALUE="simplyblock-storage-plane" -IMAGE_REPO="simplyblock/simplyblock" -IMAGE_TAG= -IMAGE_PULL_POLICY="Always" -NAMESPACE="${1:-simplyblock}" - -NODES=$(kubectl get nodes -l "${LABEL_KEY}=${LABEL_VALUE}" -o jsonpath='{.items[*].metadata.name}') - -for NODE in $NODES; do - SANITIZED_NODE=$(echo "$NODE" | tr '.' '-') - JOB_NAME="simplyblock-upgrade-${SANITIZED_NODE}" - - cat < Date: Thu, 10 Sep 2026 20:49:39 +0200 Subject: [PATCH 04/19] ci: scope checkov, which never saw the exclusions checkov runs in project mode, so MegaLinter hands it `--directory .` and never builds a file list. FILTER_REGEX_EXCLUDE is applied while building that list, which means it never reached checkov: 286 of its 649 findings were in paths excluded from every other linter, including the vendored dependency charts and the kubebuilder output. Project mode is still the right mode. It is what lets checkov render the Helm charts before scanning them, and what lets the CKV2_* checks reason across resources, so the scope is set in .checkov.yml instead. checkov now covers helm-charts, csi-driver, and operator, less the vendored subcharts, the published chart output, and the generated CRDs, RBAC, and installer. That is 554 findings in 39 seconds, down from 649 in 306. Four things were measured, because the descriptor's variables do not behave the way their names suggest: - REPOSITORY_CHECKOV_FORWARD_EXCLUDED_DIRECTORIES defaults to true and is a no-op. With a directory named in EXCLUDED_DIRECTORIES, checkov scanned it anyway, reported the same findings, and the command carried no --skip-path. - A `directory:` key in .checkov.yml is dead config, because the command-line `--directory` MegaLinter always passes overrides it. - Scoping by repeating `--directory` works but reports each finding once per directory, turning 592 distinct findings into 1364. Scoping by skip-path is both honest and faster, because it prunes the directory walk rather than filtering afterward. - skip-path takes a plain path substring. A regex there aborts the run with `re.PatternError: nothing to repeat`, raised inside a forked worker, so the traceback names neither the pattern nor the file and the run looks like a hang rather than a failure. .ruff_cache is ignored as well. The gate is what creates it, and while ruff writes a .gitignore inside it, that is ruff's choice rather than a guarantee. --- .checkov.yml | 45 +++++++++++++++++++++++++++++++++++++++++++++ .gitignore | 4 +++- .mega-linter.yml | 13 +++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 .checkov.yml diff --git a/.checkov.yml b/.checkov.yml new file mode 100644 index 000000000..de9dfe7a6 --- /dev/null +++ b/.checkov.yml @@ -0,0 +1,45 @@ +# checkov policy for this repository, read by MegaLinter's REPOSITORY_CHECKOV. +# +# Which directories are scanned is set in .mega-linter.yml, not here. checkov +# ignores a `directory:` key whenever the command line carries `--directory`, +# and MegaLinter always passes one. What is left for this file is the paths +# inside those directories that are nobody's to fix, and the checks to skip. +# +# skip-path takes a plain path substring, not a regex. An entry written as +# `helm-charts/charts/[0-9]+\.[0-9]+\.[0-9]+/` aborts the whole run with +# `re.PatternError: nothing to repeat`, and because the traceback is raised +# inside a forked worker it names no file and no pattern. It also turned out to +# be unnecessary: those version-numbered directories hold one .tgz archive +# each, and checkov does not read inside an archive. +# +# Naming this file at the repository root replaces the configuration MegaLinter +# ships inside its image, so the two settings that one made are carried forward. +quiet: true + +skip-check: + # Carried over from MegaLinter's own configuration, which this file replaces. + # A HEALTHCHECK instruction is not how a Kubernetes workload reports health. + - CKV_DOCKER_2 + +skip-path: + # Vendored dependency charts: third-party manifests this repository packages + # but does not write. + - helm-charts/charts/simplyblock-operator/charts/ + # Published release output. + - csi-driver/charts/ + # Generated. A finding here belongs to the Kubebuilder markers and the API + # types it is rendered from, so it is fixed there and regenerated. + - operator/config/crd/ + - operator/config/rbac/ + - operator/dist/ + # Outside the three directories checkov is meant to cover (helm-charts, + # csi-driver, and operator). Scoping by skip-path rather than by repeating + # `--directory` is deliberate: three --directory flags make checkov run its + # whole registry once per directory and report each finding up to three times, + # which inflated 592 distinct findings to 1364. + - atlas-lib/ + - test/ + - shared/ + - .github/ + - .claude/ + - megalinter-reports/ diff --git a/.gitignore b/.gitignore index dd41a144f..53d15c440 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ local.mk /.sbcli/ # MegaLinter's run output, written into the workspace by both the CI job and a -# local `docker run oxsecurity/megalinter` invocation. +# local `docker run oxsecurity/megalinter` invocation. ruff caches there too, +# and while its cache carries a .gitignore of its own, this does not rely on it. megalinter-reports/ mega-linter.log +.ruff_cache/ diff --git a/.mega-linter.yml b/.mega-linter.yml index 76864149c..0d0abcd36 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -106,6 +106,19 @@ SPELL_CSPELL_FILE_EXTENSIONS: - .md - .txt +# checkov runs in project mode, so MegaLinter hands it `--directory .` and never +# builds a file list. FILTER_REGEX_EXCLUDE above is applied while building that +# list, which means it never reaches checkov: 286 of its 649 findings were in +# paths excluded everywhere else. Project mode is still the right mode, because +# it is what lets checkov render the Helm charts before scanning them and run the +# CKV2_* checks that reason across resources, so the scope is set by argument. +# +# Three things were measured rather than assumed. A `directory:` key in +# .checkov.yml is ignored, because the command-line `--directory` overrides it. +# REPOSITORY_CHECKOV_FORWARD_EXCLUDED_DIRECTORIES defaults to true and is a no-op +# here: with a directory named in EXCLUDED_DIRECTORIES, checkov still scanned it +# and the command carried no --skip-path. Removing `--directory` does not remove +# the ones added below, so the pair is what scopes the run. # Report without blocking, for now. The first clean run left roughly 1,880 # findings across the tree, so a gate that failed on them would fail every pull # request from the day it landed and teach everyone to ignore it. It reports From 4bcda26b3485cb7bf5db5a8fba2404cc5198dbca Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Thu, 10 Sep 2026 21:00:35 +0200 Subject: [PATCH 05/19] ci(checkov): skip the paths make helm-sync writes The development chart carries three paths that `make helm-sync` writes from the operator's kubebuilder markers and API types: crds/, templates/roles/, and templates/simplyblock-operator-webhook.yaml. A finding in any of them is fixed at the marker and synced, never edited in the chart, which is the same reason operator/config/rbac/ is already skipped. Ten findings, all of them RBAC wildcards in generated roles. 554 to 544. --- .checkov.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.checkov.yml b/.checkov.yml index de9dfe7a6..4ef1fe1e0 100644 --- a/.checkov.yml +++ b/.checkov.yml @@ -25,6 +25,13 @@ skip-path: # Vendored dependency charts: third-party manifests this repository packages # but does not write. - helm-charts/charts/simplyblock-operator/charts/ + # The three paths `make helm-sync` writes into the development chart. A + # finding here belongs to the operator's Kubebuilder markers and API types, + # the same reason operator/config/rbac/ is skipped below, so it is fixed + # there and synced rather than edited in the chart. + - helm-charts/charts/simplyblock-operator/crds/ + - helm-charts/charts/simplyblock-operator/templates/roles/ + - helm-charts/charts/simplyblock-operator/templates/simplyblock-operator-webhook.yaml # Published release output. - csi-driver/charts/ # Generated. A finding here belongs to the Kubebuilder markers and the API From 7a237c787965e5080ee1a40a1eb569940442b670 Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Thu, 10 Sep 2026 21:12:12 +0200 Subject: [PATCH 06/19] fix(operator): verify the rebalancer's packages, and pin the manager's uid checkov's findings against operator/ were 48. Two were real. The rebalancer image installed fio, nvme-cli, and sudo with --nogpgcheck, which is to say without checking who signed them, into an image that carries Red Hat certification. The reason the flag was there is that oraclelinux-release sets gpgcheck=1 on the repositories it adds and points them at file:///etc/pki/rpm-gpg/RPM-GPG-KEY-oracle, a file it does not ship: the only keys on disk are Red Hat's. Importing Oracle's key satisfies the repositories' own configuration, so both flags are gone. Verified by building the runtime stage: dnf update and dnf install both succeed with checking enabled. The manager's pod security context asked for runAsNonRoot without saying which user, leaving the id to whatever the image declares. It declares 65532, and the Helm chart's own Deployment already pins that same id, so the kustomize base was the one place the number was missing. dist/install.yaml is regenerated, and the only line it gains is that one. The rest were the scanner reading files that are not what it thinks they are: - 38 came from two Kustomize patches. MegaLinter's image has no Kustomize binary, so checkov disables that framework and reads each overlay as a whole Deployment, then reports every field the patch does not repeat. It flagged manager_webhook_patch.yaml for a writable root filesystem next to a comment explaining that the filesystem is read-only. Those overlays are skipped now. config/manager/ is not, because it is a whole manifest and its findings mean something. - The remaining four are left visible and unfixed. A sample CR names no namespace on purpose, the manager genuinely needs its service account token, and `controller:latest` is the placeholder Kustomize replaces, so neither the tag nor the digest check has anything to say about the shipped image. Three findings are deliberate and now carry the reason next to the thing they justify: an OLM bundle built FROM scratch has no user database to add a user to, patching a UBI base is an update layer that installs nothing by design, and the rebalancer's sudo grants exactly two binaries through a sudoers file, which is narrower than the alternative of running the container as root. operator: 48 findings to 4. The repository: 544 to 500. --- .checkov.yml | 9 +++++++++ operator/Dockerfile | 5 +++++ operator/Dockerfile.simplyblock-rebalancer | 17 +++++++++++++++-- operator/bundle.Dockerfile | 4 ++++ operator/config/manager/manager.yaml | 5 +++++ operator/dist/install.yaml | 1 + 6 files changed, 39 insertions(+), 2 deletions(-) diff --git a/.checkov.yml b/.checkov.yml index 4ef1fe1e0..500a990ba 100644 --- a/.checkov.yml +++ b/.checkov.yml @@ -38,6 +38,15 @@ skip-path: # types it is rendered from, so it is fixed there and regenerated. - operator/config/crd/ - operator/config/rbac/ + # The Kustomize overlays, which are strategic-merge patch fragments rather + # than whole manifests. MegaLinter's image carries no Kustomize binary, so + # checkov reports `Framework will be disabled for this run` and falls back to + # reading each file as a complete resource. It then reports every field the + # fragment does not repeat: 38 findings across two patches, including a + # read-only root filesystem that config/manager/manager.yaml does set. The + # base is not skipped, because it is a whole manifest and its findings are + # real. + - operator/config/default/ - operator/dist/ # Outside the three directories checkov is meant to cover (helm-charts, # csi-driver, and operator). Scoping by skip-path rather than by repeating diff --git a/operator/Dockerfile b/operator/Dockerfile index 27fdbc1e3..c7379c78f 100644 --- a/operator/Dockerfile +++ b/operator/Dockerfile @@ -56,6 +56,11 @@ ARG VERSION=0.1.0 ARG RELEASE=1 # Apply all available security patches from the UBI10 repositories. +# +# checkov:skip=CKV_DOCKER_5: The check wants an update paired with an install, +# so that a stale package index cannot be cached ahead of one. Patching the +# base image is the whole purpose of this layer and it installs nothing, which +# is what Red Hat certification asks of a UBI-derived image. RUN microdnf update -y && microdnf clean all # Required labels for Red Hat certification (preflight check operator). diff --git a/operator/Dockerfile.simplyblock-rebalancer b/operator/Dockerfile.simplyblock-rebalancer index 8bd9d5ea3..05d11dc55 100644 --- a/operator/Dockerfile.simplyblock-rebalancer +++ b/operator/Dockerfile.simplyblock-rebalancer @@ -53,12 +53,25 @@ RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ dnf install -y https://yum.oracle.com/repo/OracleLinux/OL10/baseos/latest/x86_64/getPackage/oraclelinux-release-el10-1.0-17.el10.x86_64.rpm ; \ fi -RUN dnf update -y --nogpgcheck && \ +# The oraclelinux-release RPM above sets gpgcheck=1 on the repositories it adds +# and points them at file:///etc/pki/rpm-gpg/RPM-GPG-KEY-oracle, which is a file +# it does not ship: only Red Hat's keys are on disk. Importing Oracle's key is +# what lets the packages below be verified. Without it dnf can only be told +# --nogpgcheck, which installs fio and nvme-cli into a certified image without +# checking who signed them. +RUN rpm --import https://yum.oracle.com/RPM-GPG-KEY-oracle-ol10 + +# checkov:skip=CKV2_DOCKER_1: sudo is the point, not an oversight. The container +# runs as uid 65532 to satisfy the OpenShift SCC and Red Hat certification, and +# nvme-cli and fio need kernel access it cannot have. The sudoers file written +# below grants exactly two binaries and nothing else, which is a narrower grant +# than running the whole container as root would be. +RUN dnf update -y && \ dnf install -y --nodocs \ fio \ nvme-cli \ sudo \ - && dnf remove -y python3-urllib3 python3-requests python3-idna --nogpgcheck \ + && dnf remove -y python3-urllib3 python3-requests python3-idna \ && dnf clean all COPY --from=builder /workspace/simplyblock-rebalancer /usr/local/bin/simplyblock-rebalancer diff --git a/operator/bundle.Dockerfile b/operator/bundle.Dockerfile index 0fb0e48b6..af27d432f 100644 --- a/operator/bundle.Dockerfile +++ b/operator/bundle.Dockerfile @@ -1,5 +1,9 @@ FROM scratch +# checkov:skip=CKV_DOCKER_3: An OLM bundle is metadata, not a runnable image. +# It has no shell and no user database to add a user to, and nothing ever +# executes in it: the registry reads the labels and copies the manifests out. + # Core bundle labels. LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ diff --git a/operator/config/manager/manager.yaml b/operator/config/manager/manager.yaml index 7055eae71..4ae4ead93 100644 --- a/operator/config/manager/manager.yaml +++ b/operator/config/manager/manager.yaml @@ -55,6 +55,11 @@ spec: # This ensures that deployments meet the highest security requirements for Kubernetes. # For more details, see: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted runAsNonRoot: true + # The image sets USER 65532, and the Helm chart's own Deployment pins the + # same id. Naming it here too means the manifest says what it runs as + # rather than deferring to whatever the image happens to declare, and it + # is high enough not to collide with a host user. + runAsUser: 65532 seccompProfile: type: RuntimeDefault containers: diff --git a/operator/dist/install.yaml b/operator/dist/install.yaml index 35a7db3fd..32f1b8e41 100644 --- a/operator/dist/install.yaml +++ b/operator/dist/install.yaml @@ -6476,6 +6476,7 @@ spec: name: webhook-certs securityContext: runAsNonRoot: true + runAsUser: 65532 seccompProfile: type: RuntimeDefault serviceAccountName: simplyblock-operator-controller-manager From 38f310ff8d5cb26db147c4cb62806bb4b4a30d64 Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Thu, 10 Sep 2026 21:16:42 +0200 Subject: [PATCH 07/19] ci(checkov): stop scanning the test fixtures Two hundred and fifty-seven of checkov's remaining findings were on pods and claims that exist for the length of a test run and are then deleted. Asking a throwaway pod for a seccomp profile, a liveness probe, and a read-only root filesystem is not a finding anybody will act on, and it was half of what the gate reported. Skipped: - csi-driver/e2e/, whose nine templates the Ginkgo suite applies and removes. All 179 findings there came from those, at roughly 20 apiece. - deploy/kubernetes/testpod, testclone, and testrestore, plus the WordPress and MySQL demo in mysql-pvc.yaml. The install walkthrough has a reader apply these by hand to prove the driver works. deploy.sh is what settles which of those manifests an installation applies. Its list names driver, config-map, secret, controller-rbac, node-rbac, controller, node, storageclass, and the snapshot controller, and none of the fixtures above appear in it. controller.yaml and node.yaml therefore stay in scope: those describe workloads that run, and node.yaml's privileges are the interesting kind. 500 findings to 243, and what is left is all on manifests that deploy something. --- .checkov.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.checkov.yml b/.checkov.yml index 500a990ba..26df2f689 100644 --- a/.checkov.yml +++ b/.checkov.yml @@ -34,6 +34,20 @@ skip-path: - helm-charts/charts/simplyblock-operator/templates/simplyblock-operator-webhook.yaml # Published release output. - csi-driver/charts/ + # Test fixtures. These are pods and claims a test run creates and deletes, on + # a test cluster, and no installation ever carries them: the nine templates + # under e2e/templates are applied by the Ginkgo suite, and testpod, + # testclone, testrestore, and the WordPress and MySQL demo are the ones the + # install walkthrough has a reader apply by hand to prove the driver works. + # deploy.sh names what an installation actually applies, and none of these + # appear in it. Two hundred and fifty-seven findings asking a throwaway pod + # for a seccomp profile and a liveness probe. + # + # deploy/kubernetes/controller.yaml and node.yaml are deliberately not here. + # deploy.sh does apply those, so they describe workloads that run. + - csi-driver/e2e/ + - csi-driver/deploy/kubernetes/test + - csi-driver/deploy/kubernetes/mysql-pvc.yaml # Generated. A finding here belongs to the Kubebuilder markers and the API # types it is rendered from, so it is fixed there and regenerated. - operator/config/crd/ From 339056a7af0056acf2d48fe576c144144045bdb0 Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Thu, 10 Sep 2026 21:23:51 +0200 Subject: [PATCH 08/19] chore(csi-driver): delete deploy/CRDs, a specification draft yamllint reported that csi-driver/deploy/CRDs/pool.yaml is not valid YAML: it ends on three lines of stray `~~~`, a markdown fence that was pasted in and never taken out. Fixing those three lines would have made a dead file parse. The commit that added the directory says what it is: "new CRDs for operator - serve as a specification draft" (72d92884, November 2025). It is a sketch of an API that was later built elsewhere, and everything about it says so. The group is simplyblock.com, which appears in no Go or Python source in this repository. The operator's CRDs are storage.simplyblock.io, generated into operator/config/crd/bases from the API types. Of the kinds drafted here only StorageNode shares a name with a real one, and it is in the wrong group; SimplyBlockStorageCluster is neither the right kind nor the right spelling of the brand. Three of the drafts declare `kind: Rackup`. lvol.yaml pairs it with `plural: lvols`, replication.yaml with `plural: replications`, and lvolmigration.yaml with `plural: lvolmigrations`, so all three are a Backup template copied with the typo left in. The API server rejects a CustomResourceDefinition shaped like that, which means none of these was ever applied to a cluster. Nothing references the directory. deploy.sh, which is the script that installs this driver by hand, lists what it applies and no CRD from here is in it. The README.MD is not documentation either: it is a scratchpad of Docker Swarm container ids, a line reading `???`, and a fragment of somebody's `docker ps`. The real specification lives in operator/docs/designs, and the real CRDs are generated. Git keeps this draft if anyone wants to read it. --- csi-driver/deploy/CRDs/Backup.yaml | 142 ----------- csi-driver/deploy/CRDs/BackupSchedule.yaml | 131 ---------- csi-driver/deploy/CRDs/ClusterHealth.yaml | 0 csi-driver/deploy/CRDs/ClusterTasks.yaml | 73 ------ csi-driver/deploy/CRDs/Devices.yaml | 88 ------- csi-driver/deploy/CRDs/README.MD | 56 ----- csi-driver/deploy/CRDs/StorageNodeHealth.yaml | 0 csi-driver/deploy/CRDs/cluster.yaml | 224 ------------------ csi-driver/deploy/CRDs/lvol.yaml | 131 ---------- csi-driver/deploy/CRDs/lvolmigration.yaml | 76 ------ csi-driver/deploy/CRDs/pool.yaml | 88 ------- csi-driver/deploy/CRDs/replication.yaml | 107 --------- csi-driver/deploy/CRDs/storagenode.yml | 199 ---------------- csi-driver/deploy/CRDs/synch-replication.yaml | 77 ------ 14 files changed, 1392 deletions(-) delete mode 100644 csi-driver/deploy/CRDs/Backup.yaml delete mode 100644 csi-driver/deploy/CRDs/BackupSchedule.yaml delete mode 100644 csi-driver/deploy/CRDs/ClusterHealth.yaml delete mode 100644 csi-driver/deploy/CRDs/ClusterTasks.yaml delete mode 100644 csi-driver/deploy/CRDs/Devices.yaml delete mode 100644 csi-driver/deploy/CRDs/README.MD delete mode 100644 csi-driver/deploy/CRDs/StorageNodeHealth.yaml delete mode 100644 csi-driver/deploy/CRDs/cluster.yaml delete mode 100644 csi-driver/deploy/CRDs/lvol.yaml delete mode 100644 csi-driver/deploy/CRDs/lvolmigration.yaml delete mode 100644 csi-driver/deploy/CRDs/pool.yaml delete mode 100644 csi-driver/deploy/CRDs/replication.yaml delete mode 100644 csi-driver/deploy/CRDs/storagenode.yml delete mode 100644 csi-driver/deploy/CRDs/synch-replication.yaml diff --git a/csi-driver/deploy/CRDs/Backup.yaml b/csi-driver/deploy/CRDs/Backup.yaml deleted file mode 100644 index 82c33d88c..000000000 --- a/csi-driver/deploy/CRDs/Backup.yaml +++ /dev/null @@ -1,142 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: backups.simplyblock.com -spec: - group: simplyblock.com - scope: Namespaced - names: - plural: backups - singular: backup - kind: Backup - shortNames: - - bkp - - # create: takes one snapshot and then creates backups from the chain of snapshots of the PVC - # as long as those snapshots do not have backups yet. Example: - # s1->s2->s3->lvol1 is the chain of snapshot operations before the backup operation - # and only s1 has an associated backup. Now when the CRD is applied, a new backup-id - # is assigned and then a snapshot s4 is created. Then snapshots s4, s3, s2 are backed up. - # (s1 has a backup already). If keepOnlineBackup, s4 is kept, otherwise its deleted after the operations. - # delete: deletes the chain of backups for that PVC. Snapshots are only deleted if they - # were created for the backup. - # if the backup list is available on this cluster (fdb), it will use it. If this is performed on - # another cluster, it requires a backup list input file (see above/below) to identify the backups - # in restore process, PVCs will be created from backed up data. - # if replacePVConRestore is set to True, it means the bound PVCs will be replaced with the - # version from the Backup. If set to False, another PVC will be created under a new name. - - action: "restore" - versions: - - name: 25.10.5 - served: true - storage: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - clusterUUID: - type: string - description: "UUID of the cluster this backup belongs to or is restored to" - pool: - type: string - description: "Pool to snapshot and backup" - pvc: - type: string - description: "Alternatively to pool, an individual PVC can be backed up" - s3: - type: object - properties: - bucket: - type: string - region: - type: string - accessKeySecret: - type: string - secretKeySecret: - type: string - description: "Optional S3 target" - filesystem: - type: object - properties: - mountPoint: - type: string - description: "Optional filesystem target instead of S3" - retention: - type: integer - description: "Number of backups to keep" - keepOnlineBackup: - type: boolean - description: "Whether to keep the online snapshot after backup" - backupRestoreList: - type: string - description: "Filename and location for input json" - replacePVConRestore: - type: boolean - description: "True, if bound PVCs are to be replaced with backup version" - status: - type: object - properties: - scheduleID: - type: string - description: "UUID" - lastAction: - type: string - description: "Last action performed by operator" - lastActionAt: - type: string - format: date-time - state: - type: string - description: "State of backup: Pending, Running, Completed, Failed" - PVCs: - type: array - items: - type: object - properties: - name: - type: string - poolUUID: - type: string - backups: - type: array - items: - type: object - properties: - snapshotUUID: - type: string - backupID: - type: string - subresources: - status: {} - additionalPrinterColumns: - - name: Cluster - type: string - jsonPath: .spec.clusterUUID - - name: Pool - type: string - jsonPath: .spec.pool - - name: PVC - type: string - jsonPath: .spec.pool - - name: Target - type: string - jsonPath: .spec.s3.bucket - - name: Filesystem - type: string - jsonPath: .spec.filesystem.mountPoint - - name: Interval - type: string - jsonPath: .spec.interval - - name: Retention - type: integer - jsonPath: .spec.retention - - name: LastBackupDateTime - type: string - jsonPath: .status.DateTime - - name: LastBackupState - type: string - jsonPath: .status.state \ No newline at end of file diff --git a/csi-driver/deploy/CRDs/BackupSchedule.yaml b/csi-driver/deploy/CRDs/BackupSchedule.yaml deleted file mode 100644 index 0db42cc05..000000000 --- a/csi-driver/deploy/CRDs/BackupSchedule.yaml +++ /dev/null @@ -1,131 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: backupschedules.simplyblock.com -spec: - group: simplyblock.com - scope: Namespaced - names: - plural: backupscheduless - singular: backupschedule - kind: BackupSchedule - shortNames: - - bks - - # delete-all-backups: must delete the online snapshots (if existent) and associated backups for that schedule - # this CRD may be run in another cluster to restore the backups - # restore operation takes pool or PVC name. - # if the backup list is available on this cluster (fdb), it will use it. If this is performed on - # another cluster, it requires a backup list input file (see above/below) to uniquly identify the backups - # in restore process, PVCs will be created from backed up data. - # if replacePVConRestore is set to True, it means the bound PVCs will be replaced with the - # version from the Backup. If set to False, another PVC will be created under a new name. - - action: "restore" - versions: - - name: 25.10.5 - served: true - storage: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - clusterUUID: - type: string - description: "UUID of the cluster this backup belongs to or is restored to" - pool: - type: string - description: "Pool to snapshot and backup" - pvc: - type: string - description: "Alternatively to pool, an individual PVC can be backed up" - s3: - type: object - properties: - bucket: - type: string - region: - type: string - accessKeySecret: - type: string - secretKeySecret: - type: string - description: "Optional S3 target" - filesystem: - type: object - properties: - mountPoint: - type: string - description: "Optional filesystem target instead of S3" - backupRestoreList: - type: string - description: "Filename and location for input json" - replacePVConRestore: - type: boolean - description: "True, if bound PVCs are to be replaced with backup version" - status: - type: object - properties: - backupID: - type: array - description: "UUID" - lastAction: - type: string - description: "Last action performed by operator" - lastActionAt: - type: string - format: date-time - state: - type: string - description: "State of backup: Pending, Running, Completed, Failed" - PVCs: - type: array - items: - type: object - properties: - name: - type: string - poolUUID: - type: string - backups: - type: array - items: - type: object - properties: - snapshotUUID: - type: string - backupID: - type: string - subresources: - status: {} - additionalPrinterColumns: - - name: Cluster - type: string - jsonPath: .spec.clusterUUID - - name: Pool - type: string - jsonPath: .spec.pool - - name: PVC - type: string - jsonPath: .spec.pvc - - name: Target - type: string - jsonPath: .spec.s3.bucket - - name: Filesystem - type: string - jsonPath: .spec.filesystem.mountPoint - - name: Interval - type: string - jsonPath: .spec.interval - - name: Retention - type: integer - jsonPath: .spec.retention - - name: LastBackupDateTime - type: string - jsonPath: .status.DateTime - - name: LastBackupState - type: string - jsonPath: .status.state \ No newline at end of file diff --git a/csi-driver/deploy/CRDs/ClusterHealth.yaml b/csi-driver/deploy/CRDs/ClusterHealth.yaml deleted file mode 100644 index e69de29bb..000000000 diff --git a/csi-driver/deploy/CRDs/ClusterTasks.yaml b/csi-driver/deploy/CRDs/ClusterTasks.yaml deleted file mode 100644 index 2d870f8f0..000000000 --- a/csi-driver/deploy/CRDs/ClusterTasks.yaml +++ /dev/null @@ -1,73 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: tasks.simplyblock.com -spec: - group: simplyblock.com - scope: Namespaced - names: - plural: tasks - singular: task - kind: Rackup - shortNames: - - tsk - action: "cancel" - versions: - - name: 25.11.1 - served: true - storage: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - clusterUUID: - type: string - description: "UUID of the cluster these tasks belongs too" - taskID: - type: string - description: "used to cancel a task" - subtasks: - type: boolean - description: "output subtasks too" - completed: - type: boolean - description: "Add completed tasks" - status: - type: object - properties: - tasks: - type: array - items: - type: object - properties: - uuid: - type: string - taskType: - type: string - taskStatus: - type: string - parentTask: - type: string - startedAt: - type: date-time - retried: - type: integer - - subresources: - status: {} - additionalPrinterColumns: - - name: Cluster - type: string - jsonPath: .spec.clusterUUID - - name: taskType - type: string - jsonPath: .spec.taskType - - name: subTasks - type: string - jsonPath: .spec.subtasks - - name: completedTasks - type: string - jsonPath: .status.completed \ No newline at end of file diff --git a/csi-driver/deploy/CRDs/Devices.yaml b/csi-driver/deploy/CRDs/Devices.yaml deleted file mode 100644 index 669c6b75d..000000000 --- a/csi-driver/deploy/CRDs/Devices.yaml +++ /dev/null @@ -1,88 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: devices.simplyblock.com -spec: - group: simplyblock.com - scope: Namespaced - names: - plural: devices - singular: devices - kind: Rackup - shortNames: - - dev - - action: "get-status" - versions: - - name: 25.11.1 - served: true - storage: true - schema: - openAPIV3Schema: - spec: - type: object - properties: - nodeUUID: - type: string - description: "UUID of the storage node`" - includeStats: - type: boolean - statsHistoryInSeconds: - type: integer - status: - type: object - properties: - devices: - type: array - items: - type: object - properties: - uuid: - type: string - health: - type: string - capacity: - type: integer - model: - type: string - utilization: - type: integer - status: - type: string - stats: - type: array - items: - type: object - properties: - wiops: - type: integer - riops: - type: integer - wtp: - type: integer - rtp: - type: integer - capacityUtil: - type: integer - - subresources: - status: {} - additionalPrinterColumns: - - name: Cluster - type: string - jsonPath: .spec.clusterUUID - - name: targetNode - type: string - jsonPath: .spec.targetNode - - name: Pool - type: string - jsonPath: .spec.pool - - name: PVC - type: string - jsonPath: .spec.pvc - - name: Status - type: string - jsonPath: .status.state - - name: Status - type: date-time - jsonPath: .status.startTime \ No newline at end of file diff --git a/csi-driver/deploy/CRDs/README.MD b/csi-driver/deploy/CRDs/README.MD deleted file mode 100644 index 158a75474..000000000 --- a/csi-driver/deploy/CRDs/README.MD +++ /dev/null @@ -1,56 +0,0 @@ -These CRDs cover all resource types for Simplyblock operator. -Apart from the standard operations (create, get, update, delete), some have additional -actions, specified in the "action" field. -E.g. Backups can be restored, clusters can be activated and upgraded, -storage nodes can be restarted and suspended -replications can be stopped, resumed, failed-over and failed-back - -Packaging of containers into PODs: -1. simplyblock-kvs -foundationdb + coordinator -app_fdb-server.ivs5fjl8se44qa60frmsxw2fb.k8dtfdxajzuu5n7srvlijmikw -app_fdb-backup-agent.1.ar76tui5nfit166er9zrgugz0 -app_CleanupFDB.1.n6a8y25ckfjxhpl0umooz8axk -??? - - -2. simpblyblock-observability -ENTIRE log mgmt. and monitoring stack without prometheus (can be disabled!) -monitoring_opensearch.1.e4ism2jaimd9ljj11tw0jm8am -monitoring_graylog.1.wf7qnrgo5mjnyxnomdntht7g7 -monitoring_grafana.1.ijf8z9c2ap8lcddashqgbj7et -monitoring_thanos-sidecar.ivs5fjl8se44qa60frmsxw2fb.tqeblhqducajyee0bzbmjbt90 -e44qa60frmsxw2fb.14e0arjdgr7vne6jsvfutr6z9 -monitoring_thanos-query.ivs5fjl8se44qa60frmsxw2fb.qbttzex5b4zu6hhf7tuecghty -thanosio/thanos:v0.31.0 -monitoring_node-exporter.ivs5fjl8se44qa60frmsxw2fb.qveinv2rbl4ph2aje66jzq50u - -3. simplyblock-WebAPI -Include Prometheus - -4. simplyblock-services -pp_LVolStatsCollector.1.8m1p8tpkkvsphxc89rf4qor51 -app_MgmtNodeMonitor.1.ol2oury31jde99q3qdawcaljn -app_TasksRunnerRestart.1.i2ezt6563bnky2gqr7p2d1t54 -app_TasksRunnerLVolSyncDelete.1.z17is5c4x65qdrq1o2q6229vh -app_HealthCheck.1.n1cy0ytarz9m1viywftxnas83 -app_TasksRunnerJCCompResume.1.drxmxzzufirtaj09be9k87er4 -app_SnapshotMonitor.1.nt7vz4hz3slywo1vjcdd577tk -app_DeviceMonitor.1.0jxv23onq5fy5x6ki4ksm61n1 -app_TasksRunnerPortAllow.1.zwi8gkxfsr4w92ozsxlviglcd -app_TasksRunnerNewDeviceMigration.1.kjcqjdz9uiroohmbdr53hzmqd -app_MainDistrEventCollector.1.8ub3urb45m7abvvsi522432fr -app_TasksNodeAddRunner.1.mi4xkekchokfc3prwn744cw3l -app_CapacityAndStatsCollector.1.vw55n3kxmnxzn5bnnt77p6exb -app_TasksRunnerFailedMigration.1.8nn9u7uhpupwecq7jumrqley9 -app_TasksRunnerClusterStatus.1.nuifhdq985efldlzyxfce8747 -app_StorageNodeMonitor.1.q430g2vfpz2gambbf18arb4c8 -app_LVolMonitor.1.le2sqbw3ebgklkan2gf5fylzl -app_TasksRunnerMigration.1.vl19rxbnquuct0t0vej6runou -app_CapacityMonitor.1.z2euv14ng5xfmbdjv1jpse6e1 - - - - - - Up 13 hours monitoring_thanos-compactor.ivs5fjl8se44qa60frmsxw2fb.8dnumd5yc292529qpcqki5x \ No newline at end of file diff --git a/csi-driver/deploy/CRDs/StorageNodeHealth.yaml b/csi-driver/deploy/CRDs/StorageNodeHealth.yaml deleted file mode 100644 index e69de29bb..000000000 diff --git a/csi-driver/deploy/CRDs/cluster.yaml b/csi-driver/deploy/CRDs/cluster.yaml deleted file mode 100644 index 58eb85795..000000000 --- a/csi-driver/deploy/CRDs/cluster.yaml +++ /dev/null @@ -1,224 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: clusters.simplyblock.example.com -spec: - group: simplyblock.com - scope: Namespaced - names: - plural: storage-clusters - singular: storage-cluster - kind: SimplyBlockStorageCluster - shortNames: - - sbc - action: "activate" #activate #upgrade - versions: - - name: v25.11.1 - served: true - storage: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - # === Create-only fields (reset to default when changed) === - mgmtIfc: - type: string - description: "Management interface name (create-only)" - enableNodeAffinity: - type: boolean - description: "Enable node affinity for storage nodes (create-only)" - stripeWdata: - type: integer - description: "Stripe N value (1,2,4) - create only" - enum: [1, 2, 4] - stripeWparity: - type: integer - description: "Stripe K value (0,1,2) - create only" - enum: [0, 1, 2] - haType: - type: string - description: "Logical volume HA type (ha, single) - create only" - enum: ["ha", "single"] - ClusterName: - type: string - description: "Optional cluster name at creation time" - GrafanaEndpoint: - type: string - description: "Grafana endpoint URL" - isSingleNode: - type: boolean - description: "For single node clusters only" - ingressHostSource: - type: string - description: "Ingress host source" - enum: [ "hostip", "loadbalancer", "dns" ] - tlsSecretName: - type: string - description: "TLS secret for ingress HTTPS termination" - dnsName: - type: string - description: "Fully qualified DNS name (required if ingressHostSource=dns)" - strictNodeAntiAffinity: - type: boolean - description: "Enable strict node anti-affinity" - qpairCount: - type: integer - description: "NVMe/TCP transport qpair count per LV" - - # === Updatable fields === - qosClasses: - type: string - description: "comma separated list of up to 6 classes with names " - capWarn: - type: integer - description: "Capacity warning level in percent" - default: 89 - capCrit: - type: integer - description: "Capacity critical level in percent" - default: 99 - provCapWarn: - type: integer - description: "Provisioned capacity warning level" - default: 250 - provCapCrit: - type: integer - description: "Provisioned capacity critical level" - default: 500 - logDelInterval: - type: string - description: "Logging retention policy (e.g., 3d)" - default: "3d" - metricsRetentionPeriod: - type: string - description: "Retention period for Prometheus metrics (e.g., 7d)" - default: "7d" - clientQpairCount: - type: integer - description: "NVMe/TCP transport qpair count per LV for clients" - contactPoint: - type: string - description: "Email or webhook for alerts" - includeStats: - type: boolean - statsHistoryInSeconds: - type: integer - includeEventLog: - type: boolean - eventLogEntries: - type: integer - - status: - type: object - properties: - UUID: - type: string - description: "Cluster UUID of the node" - health: - type: boolean - description: "cluster overall health" - mgmtNodes: - type: integer - description: "Number of management nodes" - storageNodes: - type: integer - description: "Number of storage nodes" - NQN: - type: string - description: "cluster NQN" - mgmtIp: - type: string - description: "Management IP address of the node" - state: - type: string - description: "Current state of the cluster" - enum: ["UNREADY", "IN_ACTIVATION", "ACTIVE", "SUSPENDED", "DEGRADED"] - rebalancing: - type: boolean - description: "Indicates whether the cluster is currently rebalancing data" - clusterUUID: - type: string - description: "Cluster-generated unique identifier" - secretName: - type: string - description: "Kubernetes Secret containing cluster credentials" - disabledMonitoringStack: - type: boolean - fdbconn: - type: string - grafana_endpoint: - type: string - graylog_endpoint: - type: string - message: - type: string - description: "Optional status message from operator" - lastUpdated: - type: string - format: date-time - description: "Timestamp of last status update" - created: - type: string - format: date-time - description: "Timestamp of create" - stats: - type: array - items: - type: object - properties: - wiops: - type: integer - riops: - type: integer - wtp: - type: integer - rtp: - type: integer - capacityUtil: - type: integer - eventLog: - type: array - items: - type: object - properties: - created: - type: date-time - uuid: - type: string - eventType: - type: string - severity: - type: string - description: - type: string - storageID: - type: string - status: - type: string - - subresources: - status: {} - additionalPrinterColumns: - - name: ClusterUUID - type: string - jsonPath: .status.UUID - - name: ClusterName - type: string - jsonPath: .spec.clusterName - - name: HA Type - type: string - jsonPath: .spec.haType - - name: State - type: string - jsonPath: .status.state - - name: Rebalancing - type: boolean - jsonPath: .status.rebalancing - - name: Last Updated - type: date - jsonPath: .status.lastUpdated - - diff --git a/csi-driver/deploy/CRDs/lvol.yaml b/csi-driver/deploy/CRDs/lvol.yaml deleted file mode 100644 index 47b604bf4..000000000 --- a/csi-driver/deploy/CRDs/lvol.yaml +++ /dev/null @@ -1,131 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: lvols.simplyblock.com -spec: - group: simplyblock.com - scope: Namespaced - names: - plural: lvols - singular: lvol - kind: Rackup - shortNames: - - lvl - - #create: - action: "get-status" - versions: - - name: 25.11.1 - served: true - storage: true - schema: - openAPIV3Schema: - spec: - type: object - properties: - namePVC: - type: string - includeStats: - type: boolean - statsHistoryInSeconds: - type: integer - status: - type: object - properties: - uuid: - type: string - nodeUUID: - type: string - hostname: - type: string - clonedFromSnap: - type: string - nqn: - type: string - subsysPort: - type: string - namespaceID: - type: string - poolUUID: - type: string - poolName: - type: string - status: - type: string - health: - type: string - isCrypto: - type: boolean - size: - type: integer - utilization: - type: integer - percentUtil: - type: integer - createDt: - type: date-time - updateDt: - type: date-time - qosIOPS: - type: integer - qosWTP: - type: integer - qosRTP: - type: integer - qosRWTP: - type: integer - qosClass: - type: integer - maxNamespacesPerSubsystem: - type: integer - fabric: - type: string - - stats: - type: array - items: - type: object - properties: - wiops: - type: integer - riops: - type: integer - wtp: - type: integer - rtp: - type: integer - capacityUtil: - type: integer - - subresources: - status: {} - additionalPrinterColumns: - - name: Cluster - type: string - jsonPath: .spec.clusterUUID - - name: targetNode - type: string - jsonPath: .spec.targetNode - - name: Pool - type: string - jsonPath: .spec.pool - - name: PVC - type: string - jsonPath: .spec.pvc - - name: Status - type: string - jsonPath: .status.state - - name: Status - type: date-time - jsonPath: .status.startTime - - - - - - - - - - - diff --git a/csi-driver/deploy/CRDs/lvolmigration.yaml b/csi-driver/deploy/CRDs/lvolmigration.yaml deleted file mode 100644 index 767ef9c19..000000000 --- a/csi-driver/deploy/CRDs/lvolmigration.yaml +++ /dev/null @@ -1,76 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: lvolmigrations.simplyblock.com -spec: - group: simplyblock.com - scope: Namespaced - names: - plural: lvolmigrations - singular: lvolmigration - kind: Rackup - shortNames: - - lmg - - versions: - - name: 25.11.1 - served: true - storage: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - clusterUUID: - type: string - description: "UUID of the cluster this replication belongs to or is restored to" - targetNode: - type: string - description: "node-id (uuid of target node)" - pool: - type: string - description: "All PVCs in this pool are subject to this migration" - pvc: - type: string - description: "Alternatively, a migration can be setup for a single PVC" - status: - type: object - properties: - migrationID: - type: string - description: "unique ID of migration" - lastAction: - type: string - description: "Last action performed by operator" - lastActionAt: - type: string - format: date-time - startTime: - type: string - format: date-time - state: - type: string - description: "State of migration: Running, Cleanup, Failed" - subresources: - status: {} - additionalPrinterColumns: - - name: Cluster - type: string - jsonPath: .spec.clusterUUID - - name: targetNode - type: string - jsonPath: .spec.targetNode - - name: Pool - type: string - jsonPath: .spec.pool - - name: PVC - type: string - jsonPath: .spec.pvc - - name: Status - type: string - jsonPath: .status.state - - name: Status - type: date-time - jsonPath: .status.startTime diff --git a/csi-driver/deploy/CRDs/pool.yaml b/csi-driver/deploy/CRDs/pool.yaml deleted file mode 100644 index 8b89f6d12..000000000 --- a/csi-driver/deploy/CRDs/pool.yaml +++ /dev/null @@ -1,88 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: pools.simplyblock.com -spec: - group: simplyblock.com - scope: Namespaced - names: - plural: pools - singular: pool - kind: Pool - shortNames: - - pl - - versions: - - name: 25.11.1 - served: true - storage: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - name: - type: string - clusterUUID: - type: string - description: "UUID of the parent cluster" - status: - type: string - enum: ["enabled", "disabled"] - capacityLimit: - type: string - description: "Total capacity limit, e.g., 100Gi" - qosIOPSLimit: - type: integer - description: "QoS IOPS limit" - rwLimit: - type: integer - rLimit: - type: integer - wLimit: - type: integer - action: - type: string - description: "One-shot action like restart or resync" - required: ["name", "clusterUUID"] - status: - type: object - properties: - uuid: - type: string - description: "Automatically generated UUID of the pool" - subresources: - status: {} - additionalPrinterColumns: - - name: UUID - type: string - jsonPath: .spec.uuid - - name: Name - type: string - jsonPath: .spec.name - - name: Status - type: string - jsonPath: .spec.status - - name: Capacity - type: string - jsonPath: .spec.capacity - - name: QoS IOPS Limit - type: integer - jsonPath: .spec.qosIOPSLimit - - name: RW Limit - type: integer - jsonPath: .spec.rwLimit - - name: R Limit - type: integer - jsonPath: .spec.rLimit - - name: W Limit - type: integer - jsonPath: .spec.wLimit - - name: Cluster UUID - type: string - jsonPath: .spec.clusterUUID - ~~~ - -~~~ diff --git a/csi-driver/deploy/CRDs/replication.yaml b/csi-driver/deploy/CRDs/replication.yaml deleted file mode 100644 index f2daa5a61..000000000 --- a/csi-driver/deploy/CRDs/replication.yaml +++ /dev/null @@ -1,107 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: replications.simplyblock.com -spec: - group: simplyblock.com - scope: Namespaced - names: - plural: replications - singular: replication - kind: Rackup - shortNames: - - rpl - - # create: a new replication is setup between two storage clusters (one per site) - # it can be setup for an entire storage pool or a particular PVC - # an interval is defined - # delete: a replication is entirely removed. This will also remove the target PVCs - # stop: an ongoing replication is stopped. It can be resumed at a late time. - # resume: resume a stopped replication. intermediate data must be replicated and this - # can initially take some time to catch up - # fail-over: in this scenario, the target PVCs are flipped from primary to secondary. - # The PVCs mounted on primary are disconnected (if still possible) and then the - # (re)provisioning of the same PVCs happens on (new) target nodes. - # fail-back: During fail-back the delta between the most recent snapshot on the primary - # and the current state on the secondary is synchronized back to primary - # this can be done iteratively with smaller and smaller snapshots - # finally, the PVC is frozen, a final snapshot is taken and the remaining data - # is synchronized back. now the PVC is disconnected and re-provisioned (to - # either the same host or a host on primary). using a raid-1 lvm, we could even make it - # interrupt-free - - action: "stop" #resume #fail-over #fail-back - versions: - - name: 25.11.1 - served: true - storage: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - clusterUUID: - type: string - description: "UUID of the cluster this replication belongs to or is restored to" - targetClusterUUID: - type: string - description: "UUID of the target cluster this replication belongs to or is restored to" - pool: - type: string - description: "All PVCs in this pool are subject to this replication" - pvc: - type: string - description: "Alternatively, a replication can be setup for a single PVC" - interval: - type: string - description: "Replication Frequency, minimum is 60 seconds" - status: - type: object - properties: - replicationID: - type: string - description: "unique ID of replication" - lastAction: - type: string - description: "Last action performed by operator" - lastActionAt: - type: string - format: date-time - state: - type: string - description: "State of replication: Stopped, Running, Delayed, Failed" - backlog: - type: integer - description: "Number of snapshots waiting for or or in active replication" - dataBacklog: - type: integer - description: "Number of GB waiting for or in replication" - subresources: - status: {} - additionalPrinterColumns: - - name: Cluster - type: string - jsonPath: .spec.clusterUUID - - name: targetCluster - type: string - jsonPath: .spec.targetClusterUUID - - name: Pool - type: string - jsonPath: .spec.pool - - name: PVC - type: string - jsonPath: .spec.pvc - - name: Status - type: string - jsonPath: .status.state - - name: backlog - type: string - jsonPath: .status.backlog - - name: dataBacklog - type: string - jsonPath: .status.dataBacklog - - name: Interval - type: string - jsonPath: .spec.interval diff --git a/csi-driver/deploy/CRDs/storagenode.yml b/csi-driver/deploy/CRDs/storagenode.yml deleted file mode 100644 index b120dd602..000000000 --- a/csi-driver/deploy/CRDs/storagenode.yml +++ /dev/null @@ -1,199 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: nodes.simplyblock.com -spec: - group: simplyblock.com - scope: Namespaced - names: - kind: StorageNode - plural: storage-nodes - singular: storage-node - shortNames: - - sbn - action: "restart" #suspend, shutdown, remove - versions: - - name: 25.11.1 - served: true - storage: true - schema: - openAPIV3Schema: - type: object - required: ["spec"] - properties: - spec: - type: object - description: "Specification for creating a storage node." - properties: - clusterUUID: - type: string - useSeparateJournalDevice: - type: boolean - MaxLVol: - type: integer - MaxSize: - type: string - description: "Maximum size of logical volumes (e.g. 10Ti)" - spdkImage: - type: string - mgmtIfc: - type: string - coreIsolation: - type: boolean - corePercentage: - type: integer - minimum: 1 - maximum: 100 - coreMask: - type: string - description: "Hexadecimal CPU mask, used only if corePercentage is not set." - pcieAllowList: - type: array - items: - type: string - pcieDenyList: - type: array - items: - type: string - pcieModel: - type: string - driveSizeRange: - type: string - description: "e.g. '1TB-4TB'; requires pcieModel" - socketsToUse: - type: integer - nodesPerSocket: - type: integer - dataNIC: - type: string - description: "List of NIC names used for data traffic." - items: - type: string - haJmCount: - type: integer - namespace: - type: string - - # Restart parameters - addPcieToAllowList: - type: array - description: "Add devices to allow list during restart" - items: - type: string - nodeAddr: - type: string - nodeIp: - type: string - force: - type: boolean - includeStats: - type: boolean - statsHistoryInSeconds: - type: integer - - status: - type: object - description: "Operator-reported runtime status." - properties: - uuid: - type: string - health: - type: string - state: - type: string - enum: - - online - - offline - - schedulable - - unreachable - - in_restart - - in_shutdown - - suspended - devices: - type: object - properties: - total: - type: integer - active: - type: integer - capacity: - type: object - properties: - totalBytes: - type: string - usedBytes: - type: string - usedPercent: - type: integer - hugepagesAssigned: - type: string - lvolsProvisioned: - type: integer - secondaryNodeUUID: - type: string - hostname: - type: string - spdkPort: - type: integer - lvolPort: - type: integer - hublvolPort: - type: integer - uptime: - type: string - memory: - type: string - workerHostname: - type: string - mgmtIp: - type: string - stats: - type: array - items: - type: object - properties: - wiops: - type: integer - riops: - type: integer - wtp: - type: integer - rtp: - type: integer - capacityUtil: - type: integer - - subresources: - status: { } - - additionalPrinterColumns: - - name: UUID - type: string - - name: Hostname - type: string - jsonPath: .status.hostname - - name: MgmtIP - type: string - jsonPath: .status.mgmtIp - - name: Status - type: string - jsonPath: .status.state - - name: Health - type: string - jsonPath: .status.health - - name: Uptime - type: string - jsonPath: .status.uptime - - name: TotalActiveDevices - type: string - jsonPath: .status.devices.active - - name: CapacityUsed% - type: integer - jsonPath: .status.capacity.usedPercent - - name: CapacityUsed - type: string - jsonPath: .status.capacity.usedBytes - - name: CapacityTotal - type: string - jsonPath: .status.capacity.totalBytes - diff --git a/csi-driver/deploy/CRDs/synch-replication.yaml b/csi-driver/deploy/CRDs/synch-replication.yaml deleted file mode 100644 index 3eb8e01b5..000000000 --- a/csi-driver/deploy/CRDs/synch-replication.yaml +++ /dev/null @@ -1,77 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: replications.simplyblock.com -spec: - group: simplyblock.com - scope: Namespaced - names: - plural: synchreplications - singular: synchreplication - kind: Rackup - shortNames: - - srp - - action: "stop" #resume #fail-over #fail-back - versions: - - name: 25.11.1 - served: true - storage: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - clusterUUID: - type: string - description: "UUID of the cluster this replication belongs to or is restored to" - pool: - type: string - description: "All PVCs in this pool are subject to this replication" - pvc: - type: string - description: "Alternatively, a replication can be setup for a single PVC" - status: - type: object - properties: - synReplicationID: - type: string - description: "unique ID of replication" - lastAction: - type: string - description: "Last action performed by operator" - lastActionAt: - type: string - format: date-time - state: - type: string - description: "State of replication: Stopped, Running, Failed" - backlog: - type: integer - description: "Time of interruption (stop) of replication in seconds." - dataBacklog: - type: integer - description: "Number of GB waiting for or in replication" - subresources: - status: {} - additionalPrinterColumns: - - name: Cluster - type: string - jsonPath: .spec.clusterUUID - - name: Pool - type: string - jsonPath: .spec.pool - - name: PVC - type: string - jsonPath: .spec.pvc - - name: Status - type: string - jsonPath: .status.state - - name: backlog - type: string - jsonPath: .status.backlog - - name: dataBacklog - type: string - jsonPath: .status.dataBacklog From d7da18dbab57286bc78636c65f1752bc42170917 Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Thu, 10 Sep 2026 21:34:42 +0200 Subject: [PATCH 09/19] ci(checkov): drop two checks this product cannot satisfy CKV_K8S_21 reported 36 resources in a namespace called `default`, and every one of them is an artifact of how it was read. A Helm template renders to `default` when nothing passes --namespace, which is why the resources came out named Deployment.default.simplyblock-webappapi. An installation names its namespace, and the manifests under csi-driver/deploy omit it deliberately so a reader can apply them wherever they happen to be working. CKV_K8S_43 wants an image digest. The charts take a repository and a tag as values, which is how a user points them at their own registry or pins their own build, so a digest would remove the thing that makes them configurable. The tags are still checked: CKV_K8S_14 stays enabled, and it found simplyblock/spdkcsi:latest in csi-driver/deploy/kubernetes/controller.yaml and node.yaml, where every upstream sidecar beside it names a version. The upstream snapshot controller is skipped by path. It is kubernetes-csi/external-snapshotter's manifest copied into both trees with its header comment intact, and its 37 findings belong to the project that wrote it. Changing them here would fork a vendored file and lose the change at the next copy. 243 findings to 158. What is left is not a configuration problem: 69 of them are the five control-plane Deployments and the MinIO StatefulSet in controlplane_deploy.yaml, and 42 more are the CSI controller and node plugins, whose privileges are the ones this product actually needs. --- .checkov.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.checkov.yml b/.checkov.yml index 26df2f689..d103e0f0f 100644 --- a/.checkov.yml +++ b/.checkov.yml @@ -21,6 +21,21 @@ skip-check: # A HEALTHCHECK instruction is not how a Kubernetes workload reports health. - CKV_DOCKER_2 + # Every one of the 36 resources this reported sits in a namespace called + # `default`, because that is what a Helm template renders to when nothing + # passes --namespace: the resources come out named + # Deployment.default.simplyblock-webappapi and the like. An installation names + # its namespace, and the manifests under csi-driver/deploy leave it out on + # purpose so a reader can apply them wherever they are working. + - CKV_K8S_21 + + # The charts take an image repository and a tag as values, which is how a user + # points them at their own registry or pins their own build. Requiring a digest + # would mean the chart could no longer be configured that way. The tags + # themselves are still checked, by CKV_K8S_14, which is not skipped: it found + # simplyblock/spdkcsi:latest in two manifests that ought to name a version. + - CKV_K8S_43 + skip-path: # Vendored dependency charts: third-party manifests this repository packages # but does not write. @@ -34,6 +49,12 @@ skip-path: - helm-charts/charts/simplyblock-operator/templates/simplyblock-operator-webhook.yaml # Published release output. - csi-driver/charts/ + # The upstream snapshot controller, copied from kubernetes-csi/external-snapshotter + # into both trees with its header comment intact and renamed to + # simplyblock-snapshot-controller. It deploys, so it is not a fixture, but its + # 37 findings belong to the project that wrote it: changing them here would + # fork a vendored manifest and lose the changes at the next copy. + - setup-snapshot-controller.yaml # Test fixtures. These are pods and claims a test run creates and deletes, on # a test cluster, and no installation ever carries them: the nine templates # under e2e/templates are applied by the Ginkgo suite, and testpod, From b80d57a8bda18165609d55d3d67c82a0442c7cae Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Thu, 10 Sep 2026 21:43:38 +0200 Subject: [PATCH 10/19] ci(checkov): skip the CSI controller and node plugins A node plugin mounts filesystems and opens raw block devices on the host. It runs privileged, shares the host network namespace, and holds CAP_SYS_ADMIN, and the controller is not far behind. Twenty-five of the 42 findings on these two manifests were checkov reading that design back as a list of faults: not privileged, no added capabilities, no host network, a read-only root filesystem, a high uid. A CSI driver cannot be any of those things. Skipping by path is the only way to express it, because checkov cannot disable a check for one file, and it costs the seventeen findings on these manifests that were not about privilege. The Helm chart under csi-driver/charts is the supported way to install this driver and these are the walkthrough's manual path, so the resource limits and probes they lack are worth less than the same gap in the chart would be. One of the seventeen is worth acting on separately, and is recorded here so it is not lost with the rest: controller.yaml and node.yaml both run `simplyblock/spdkcsi:latest`, while every upstream sidecar beside them names a version (csi-provisioner:v4.0.1, csi-snapshotter:v8.2.0, csi-attacher:v4.5.1). A floating tag on the one image this project builds means an install cannot be reproduced and a rollback has nothing to roll back to. Pinning it needs somebody to say which version, so it is not done here. 158 findings to 116, and 111 of those are the chart. --- .checkov.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.checkov.yml b/.checkov.yml index d103e0f0f..816203fba 100644 --- a/.checkov.yml +++ b/.checkov.yml @@ -49,6 +49,21 @@ skip-path: - helm-charts/charts/simplyblock-operator/templates/simplyblock-operator-webhook.yaml # Published release output. - csi-driver/charts/ + # The CSI controller and node plugins. A node plugin mounts filesystems and + # opens raw block devices on the host, so it runs privileged, shares the host + # network namespace, and holds CAP_SYS_ADMIN, and the controller is not far + # behind. Twenty-five of the findings here are checkov describing that design + # back: not privileged, no added capabilities, no host network, a read-only + # root filesystem, a high uid. None of them is something this driver can be. + # + # This gives up seventeen findings that are not about privilege, which is the + # cost of skipping by path: checkov cannot skip a check for one file. The + # Helm chart under csi-driver/charts is the supported way to install this + # driver and these manifests are the walkthrough's manual path, so the + # resource limits and probes they lack matter less than they would in the + # chart. The two that do matter are recorded in the commit. + - csi-driver/deploy/kubernetes/node.yaml + - csi-driver/deploy/kubernetes/controller.yaml # The upstream snapshot controller, copied from kubernetes-csi/external-snapshotter # into both trees with its header comment intact and renamed to # simplyblock-snapshot-controller. It deploys, so it is not a fixture, but its From 02d81f9b5038631cead8cc1f14a8618faac18857 Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Thu, 10 Sep 2026 22:02:30 +0200 Subject: [PATCH 11/19] style(go): give every function body its own lines 299 function declarations across 116 files kept their body on the signature's line, as `func (GroupByHardware) Name() string { return "identical hardware" }` does. They are split, and nothing else about them changes. The rewrite was mechanical: for each declaration whose body opened and closed on one line, a newline went in after the brace and before it, and gofmt did the indentation. It is safe by construction, because a one-line body cannot contain a line comment: the comment would swallow its own closing brace and the file would not have parsed. Verified rather than assumed. All four modules build, `go vet ./...` passes in each of them, which type-checks the test files that hold 60 of these, and the atlas-lib suite is green. An AST scan of the result finds none left, and the same scan found the same 299 before, from an independently written program. Empty bodies are untouched: `func (noopReporter) Report() {}` is how a no-op implementation of an interface is written, and 14 of those stay as they are. Function literals are untouched too, because `defer func() { cancel() }()` and a closure passed to a Ginkgo It read better on one line. The rule that keeps this true follows in the next commit. The house style gate reports 178 findings in the comments of these files, none of them on a line this change touched, all of them British spellings and quotation marks that predate the gate. They are left for a deliberate pass. --- atlas-lib/blockdev/blkid.go | 8 ++- atlas-lib/blockdev/boundary_test.go | 4 +- atlas-lib/blockdev/candidate.go | 4 +- atlas-lib/blockdev/candidate_test.go | 12 +++- atlas-lib/blockdev/content.go | 4 +- atlas-lib/blockdev/fixture_test.go | 13 +++- atlas-lib/blockdev/local_linux.go | 12 +++- atlas-lib/blockdev/local_other.go | 16 +++-- atlas-lib/blockdev/usage_test.go | 4 +- atlas-lib/controlplane/errors.go | 4 +- atlas-lib/errs/class/class.go | 20 ++++-- atlas-lib/errs/class/class_test.go | 36 +++++++--- atlas-lib/errs/deferrers/deferrers_test.go | 4 +- atlas-lib/inventory/devices_fixture_test.go | 8 ++- atlas-lib/inventory/hugepages.go | 8 ++- atlas-lib/inventory/inventory.go | 8 ++- atlas-lib/kube/derived.go | 12 +++- atlas-lib/link/errors.go | 4 +- atlas-lib/link/hub.go | 8 ++- atlas-lib/link/peer.go | 36 +++++++--- atlas-lib/link/session.go | 12 +++- atlas-lib/lvm/volume_test.go | 12 +++- atlas-lib/nvme/siblings.go | 8 ++- atlas-lib/nvmeof/detach_test.go | 8 ++- atlas-lib/nvmeof/fabrics_test.go | 4 +- atlas-lib/nvmeof/reconcile.go | 12 +++- atlas-lib/nvmeof/repair_test.go | 8 ++- atlas-lib/nvmeof/wait_test.go | 4 +- atlas-lib/pci/scan.go | 8 ++- atlas-lib/prometheus/capacity.go | 4 +- atlas-lib/prometheus/client.go | 4 +- atlas-lib/storage/accessor_test.go | 4 +- atlas-lib/storage/storagerpc/server.go | 4 +- .../storage/storagerpc/storagerpc_test.go | 4 +- atlas-lib/volstack/layer.go | 4 +- atlas-lib/volstack/layers/fabric.go | 12 +++- atlas-lib/volstack/layers/fabric_test.go | 16 +++-- atlas-lib/volstack/layers/filesystem.go | 12 +++- .../volstack/layers/filesystem_strategy.go | 28 ++++++-- atlas-lib/volstack/layers/filesystem_test.go | 4 +- .../volstack/layers/lvmphysicalvolume.go | 8 ++- .../volstack/layers/lvmphysicalvolume_test.go | 8 ++- atlas-lib/volstack/layers/lvmvolume.go | 20 ++++-- atlas-lib/volstack/layers/lvmvolume_test.go | 4 +- atlas-lib/volstack/layers/lvmvolumegroup.go | 4 +- .../volstack/layers/lvmvolumegroup_test.go | 4 +- atlas-lib/volstack/layers/members.go | 12 +++- atlas-lib/volstack/layers/members_test.go | 8 ++- .../volstack/layers/staging_probe_test.go | 12 +++- atlas-lib/volstack/plans/plans.go | 16 +++-- atlas-lib/volstack/plans/plans_test.go | 12 +++- atlas-lib/volstack/record.go | 4 +- atlas-lib/volstack/runner.go | 4 +- atlas-lib/volstack/runner_test.go | 12 +++- csi-driver/internal/controlplane/cluster.go | 12 +++- .../internal/csi/controller/errorclass_rpc.go | 12 +++- .../csi/controller/mock_controlplane_test.go | 8 ++- csi-driver/internal/fabric/repair_test.go | 8 ++- csi-driver/internal/guardian/guardian_test.go | 4 +- .../cmd/simplyblock-nodeprobe/main_test.go | 4 +- operator/cmd/simplyblock-upgrade/run.go | 4 +- .../autoplacement/autobalancing_test.go | 4 +- .../storagedevice_controller_unit_test.go | 16 +++-- .../volumemigration_helpers_test.go | 12 +++- .../deployment/operatorops_unit_test.go | 4 +- operator/internal/cpinformer/manager.go | 4 +- operator/internal/cpinformer/manager_test.go | 8 ++- .../cpinformer/subscriptions/cache.go | 8 ++- .../cpinformer/subscriptions/device.go | 8 ++- .../cpinformer/subscriptions/device_test.go | 4 +- .../internal/cpinformer/subscriptions/node.go | 8 ++- .../cpinformer/subscriptions/node_test.go | 4 +- .../cpinformer/subscriptions/volume.go | 4 +- .../cpinformer/subscriptions/volume_test.go | 4 +- operator/internal/csilink/hub.go | 4 +- operator/internal/discovery/grouping.go | 8 ++- operator/internal/discovery/placement.go | 8 ++- operator/internal/discovery/rules.go | 32 ++++++--- operator/internal/metricsapi/server.go | 4 +- operator/internal/metricsapi/storage.go | 20 ++++-- operator/internal/metricsapi/storage_test.go | 4 +- operator/internal/nodeprobe/report.go | 4 +- operator/internal/upgrade/blocked_test.go | 24 +++++-- operator/internal/upgrade/check.go | 12 +++- operator/internal/upgrade/crds/crds.go | 12 +++- operator/internal/upgrade/derive/rule.go | 30 +++++--- operator/internal/upgrade/discover/kind.go | 12 +++- operator/internal/upgrade/discover/release.go | 8 ++- operator/internal/upgrade/finding.go | 8 ++- operator/internal/upgrade/helm/config.go | 8 ++- operator/internal/upgrade/keys/keys.go | 8 ++- operator/internal/upgrade/phase.go | 4 +- operator/internal/upgrade/plan.go | 8 ++- operator/internal/upgrade/position.go | 8 ++- operator/internal/upgrade/readonly.go | 4 +- operator/internal/upgrade/registry_test.go | 12 +++- operator/internal/upgrade/release/release.go | 4 +- operator/internal/upgrade/report.go | 12 +++- operator/internal/upgrade/rule.go | 4 +- operator/internal/upgrade/runner_test.go | 8 ++- operator/internal/upgrade/steps/crds.go | 24 +++++-- operator/internal/upgrade/steps/described.go | 12 +++- operator/internal/upgrade/steps/migrate.go | 72 ++++++++++++++----- operator/internal/upgrade/steps/ownership.go | 36 +++++++--- operator/internal/upgrade/steps/upgrade.go | 36 +++++++--- operator/internal/upgrade/subject.go | 8 ++- operator/internal/upgrade/tui/reporter.go | 4 +- operator/internal/webhook/certmanager.go | 4 +- test/integration/cluster/copy.go | 4 +- test/integration/cluster/talos.go | 16 +++-- test/integration/controlplane/server.go | 8 ++- test/integration/controlplane/state.go | 4 +- test/integration/fabric/nodeshell.go | 8 ++- test/integration/fabric/target.go | 16 +++-- test/integration/onnode/volstack_test.go | 8 ++- test/integration/suites/volstack_test.go | 4 +- 116 files changed, 899 insertions(+), 300 deletions(-) diff --git a/atlas-lib/blockdev/blkid.go b/atlas-lib/blockdev/blkid.go index e06df5026..3f22224b8 100644 --- a/atlas-lib/blockdev/blkid.go +++ b/atlas-lib/blockdev/blkid.go @@ -48,12 +48,16 @@ type BlkidProber struct { } // NewBlkidProber returns a BlkidProber that runs blkid on the local host. -func NewBlkidProber() *BlkidProber { return NewBlkidProberWithRunner(execRunner) } +func NewBlkidProber() *BlkidProber { + return NewBlkidProberWithRunner(execRunner) +} // NewBlkidProberWithRunner returns a BlkidProber that runs blkid through run, which is // how a test supplies scripted answers and how a harness probes a device on // another machine. -func NewBlkidProberWithRunner(run Runner) *BlkidProber { return &BlkidProber{run: run} } +func NewBlkidProberWithRunner(run Runner) *BlkidProber { + return &BlkidProber{run: run} +} // ErrPartitionTable reports a device carrying a partition table rather than a // filesystem: something is on it, but not something mountable, and formatting diff --git a/atlas-lib/blockdev/boundary_test.go b/atlas-lib/blockdev/boundary_test.go index 6d07b50bd..94f2bd2b0 100644 --- a/atlas-lib/blockdev/boundary_test.go +++ b/atlas-lib/blockdev/boundary_test.go @@ -47,7 +47,9 @@ func (s *synth) prober(opts ...Option) *Prober { return NewProberWithOpener(func(context.Context, Device) (Reader, error) { return s, nil }, opts...) } -func (s *synth) Close() error { return nil } +func (s *synth) Close() error { + return nil +} func (s *synth) ReadAt(ctx context.Context, p []byte, off int64) (int, error) { s.reads = append(s.reads, readRecord{off, int64(len(p))}) diff --git a/atlas-lib/blockdev/candidate.go b/atlas-lib/blockdev/candidate.go index 6b0748a04..7ffd0336c 100644 --- a/atlas-lib/blockdev/candidate.go +++ b/atlas-lib/blockdev/candidate.go @@ -125,7 +125,9 @@ type Candidate struct { } // Available reports whether the device may be handed to a storage cluster. -func (c Candidate) Available() bool { return len(c.Rejections) == 0 } +func (c Candidate) Available() bool { + return len(c.Rejections) == 0 +} // RejectedFor reports whether reason is among the grounds. func (c Candidate) RejectedFor(reason Reason) bool { diff --git a/atlas-lib/blockdev/candidate_test.go b/atlas-lib/blockdev/candidate_test.go index 23932ad05..1b294c380 100644 --- a/atlas-lib/blockdev/candidate_test.go +++ b/atlas-lib/blockdev/candidate_test.go @@ -23,11 +23,15 @@ type sparse struct { bytes map[int64][]byte } -func (s sparse) Reader() Reader { return sparseReader{s} } +func (s sparse) Reader() Reader { + return sparseReader{s} +} type sparseReader struct{ s sparse } -func (sparseReader) Close() error { return nil } +func (sparseReader) Close() error { + return nil +} func (r sparseReader) ReadAt(ctx context.Context, p []byte, off int64) (int, error) { if err := ctx.Err(); err != nil { @@ -79,7 +83,9 @@ func inspectorOver(t *testing.T, contents map[string]sparse, exclusive Exclusive } // blank is a device of the given size whose every byte is zero. -func blank(size int64) sparse { return sparse{size: size, bytes: map[int64][]byte{}} } +func blank(size int64) sparse { + return sparse{size: size, bytes: map[int64][]byte{}} +} // found finds one candidate by kernel name. func found(t *testing.T, cands []Candidate, name string) Candidate { diff --git a/atlas-lib/blockdev/content.go b/atlas-lib/blockdev/content.go index e9da85514..a024fca66 100644 --- a/atlas-lib/blockdev/content.go +++ b/atlas-lib/blockdev/content.go @@ -269,7 +269,9 @@ func firstNonZero(r regions) (int64, bool) { return 0, false } -func nonZero(c rune) bool { return c != 0 } +func nonZero(c rune) bool { + return c != 0 +} // detailOf names every signature found, not just the one that decided the // reading, so a refusal shows an operator the whole device in one message. diff --git a/atlas-lib/blockdev/fixture_test.go b/atlas-lib/blockdev/fixture_test.go index 9ea1a05d9..48ddbe07e 100644 --- a/atlas-lib/blockdev/fixture_test.go +++ b/atlas-lib/blockdev/fixture_test.go @@ -70,11 +70,15 @@ func (im image) Device() Device { // read that falls between them: the regions are what was captured, and a prober // reaching outside them is a prober reading something the fixture cannot vouch // for. -func (im image) Reader() Reader { return &imageReader{im: im} } +func (im image) Reader() Reader { + return &imageReader{im: im} +} type imageReader struct{ im image } -func (r *imageReader) Close() error { return nil } +func (r *imageReader) Close() error { + return nil +} func (r *imageReader) ReadAt(ctx context.Context, p []byte, off int64) (int, error) { if err := ctx.Err(); err != nil { @@ -140,7 +144,10 @@ func readRegion(t *testing.T, path, wantSHA, what string) []byte { return data } -func sum(b []byte) []byte { h := sha256.Sum256(b); return h[:] } +func sum(b []byte) []byte { + h := sha256.Sum256(b) + return h[:] +} // imageNames lists every capture on disk, so a fixture that is added without a // scenario row still gets its provenance checked. diff --git a/atlas-lib/blockdev/local_linux.go b/atlas-lib/blockdev/local_linux.go index 235761e93..05fbaa59c 100644 --- a/atlas-lib/blockdev/local_linux.go +++ b/atlas-lib/blockdev/local_linux.go @@ -22,7 +22,9 @@ import ( ) // NewProber returns a Prober reading local devices, bypassing the page cache. -func NewProber(opts ...Option) *Prober { return NewProberWithOpener(OpenLocal, opts...) } +func NewProber(opts ...Option) *Prober { + return NewProberWithOpener(OpenLocal, opts...) +} // OpenExclusive asks the kernel whether it will hand the device at path over, // and closes it again immediately. @@ -91,9 +93,13 @@ type localReader struct { // Degraded reports whether this reader fell back from O_DIRECT, so a caller can // count the reads whose freshness the kernel did not guarantee. -func (r *localReader) Degraded() bool { return !r.direct } +func (r *localReader) Degraded() bool { + return !r.direct +} -func (r *localReader) Close() error { return r.f.Close() } +func (r *localReader) Close() error { + return r.f.Close() +} // ReadAt fills p from off, bounded by ctx. // diff --git a/atlas-lib/blockdev/local_other.go b/atlas-lib/blockdev/local_other.go index 98194d8c9..5762d452f 100644 --- a/atlas-lib/blockdev/local_other.go +++ b/atlas-lib/blockdev/local_other.go @@ -23,17 +23,25 @@ var errNotLinux = errors.New("blockdev: reading a local block device is supporte // NewProber returns a Prober whose reads report that this platform has no local // block devices to read. -func NewProber(opts ...Option) *Prober { return NewProberWithOpener(OpenLocal, opts...) } +func NewProber(opts ...Option) *Prober { + return NewProberWithOpener(OpenLocal, opts...) +} // OpenLocal reports that a local device cannot be opened on this platform. -func OpenLocal(context.Context, Device) (Reader, error) { return nil, errNotLinux } +func OpenLocal(context.Context, Device) (Reader, error) { + return nil, errNotLinux +} // ResolveDevice reports that a local device cannot be inspected on this platform. -func ResolveDevice(string) (Device, error) { return Device{}, errNotLinux } +func ResolveDevice(string) (Device, error) { + return Device{}, errNotLinux +} // OpenExclusive reports that the kernel cannot be asked here. // // It is deliberately not ErrDeviceBusy. A probe that could not be made says // nothing about the device, and answering "busy" would put a reason on a // refusal that this platform never established. -func OpenExclusive(string) error { return errNotLinux } +func OpenExclusive(string) error { + return errNotLinux +} diff --git a/atlas-lib/blockdev/usage_test.go b/atlas-lib/blockdev/usage_test.go index 5af3df0b3..fd41dea25 100644 --- a/atlas-lib/blockdev/usage_test.go +++ b/atlas-lib/blockdev/usage_test.go @@ -45,7 +45,9 @@ func usageHost(t *testing.T) (ScanConfig, []Disk) { } // free is an exclusive opener that hands over every device. -func free(string) error { return nil } +func free(string) error { + return nil +} func TestReadUsageClimbsFromAPartitionToItsDisk(t *testing.T) { cfg, disks := usageHost(t) diff --git a/atlas-lib/controlplane/errors.go b/atlas-lib/controlplane/errors.go index c8bdccd14..078566bed 100644 --- a/atlas-lib/controlplane/errors.go +++ b/atlas-lib/controlplane/errors.go @@ -43,7 +43,9 @@ func (e *StatusError) Error() string { // HTTPStatus returns the status code the control plane answered with. It is the // method the shared classifier looks for, so any error type able to report an // HTTP status is classified the same way. -func (e *StatusError) HTTPStatus() int { return e.StatusCode } +func (e *StatusError) HTTPStatus() int { + return e.StatusCode +} // Unwrap returns the atlas sentinel this status stands for, or nil when it has // none, so errors.Is(err, errs.ErrNotFound) holds for a 404 without the caller diff --git a/atlas-lib/errs/class/class.go b/atlas-lib/errs/class/class.go index 0d7999770..61a91e846 100644 --- a/atlas-lib/errs/class/class.go +++ b/atlas-lib/errs/class/class.go @@ -70,7 +70,9 @@ type Class struct { } // Permanent reports whether retrying is pointless. -func (c Class) Permanent() bool { return !c.Retryable } +func (c Class) Permanent() bool { + return !c.Retryable +} // httpStatuser is implemented by errors that carry an HTTP status, as // controlplane.StatusError does. The classifier looks for the method rather than @@ -130,10 +132,14 @@ func Of(err error) Class { } // Code is Of(err).Code, for callers that need nothing else. -func Code(err error) codes.Code { return Of(err).Code } +func Code(err error) codes.Code { + return Of(err).Code +} // Retryable is Of(err).Retryable, the operator's requeue-or-fail decision. -func Retryable(err error) bool { return Of(err).Retryable } +func Retryable(err error) bool { + return Of(err).Retryable +} // Status returns err as a gRPC status error carrying Of(err).Code and err's // message: what an RPC handler returns. A nil err stays nil, and an err that @@ -262,5 +268,9 @@ type peerError struct { err error } -func (e peerError) Error() string { return e.msg } -func (e peerError) Unwrap() error { return e.err } +func (e peerError) Error() string { + return e.msg +} +func (e peerError) Unwrap() error { + return e.err +} diff --git a/atlas-lib/errs/class/class_test.go b/atlas-lib/errs/class/class_test.go index 4356a0894..ca3ee8b8c 100644 --- a/atlas-lib/errs/class/class_test.go +++ b/atlas-lib/errs/class/class_test.go @@ -20,24 +20,40 @@ type httpErr struct { msg string } -func (e *httpErr) Error() string { return fmt.Sprintf("control-plane returned %d: %s", e.code, e.msg) } -func (e *httpErr) HTTPStatus() int { return e.code } +func (e *httpErr) Error() string { + return fmt.Sprintf("control-plane returned %d: %s", e.code, e.msg) +} +func (e *httpErr) HTTPStatus() int { + return e.code +} // timeoutErr is a net.Error that timed out. type timeoutErr struct{} -func (timeoutErr) Error() string { return "i/o timeout" } -func (timeoutErr) Timeout() bool { return true } +func (timeoutErr) Error() string { + return "i/o timeout" +} +func (timeoutErr) Timeout() bool { + return true +} // Temporary is deprecated but still part of net.Error. -func (timeoutErr) Temporary() bool { return true } +func (timeoutErr) Temporary() bool { + return true +} // refusedErr is a net.Error that did not time out. type refusedErr struct{} -func (refusedErr) Error() string { return "connection refused" } -func (refusedErr) Timeout() bool { return false } -func (refusedErr) Temporary() bool { return false } +func (refusedErr) Error() string { + return "connection refused" +} +func (refusedErr) Timeout() bool { + return false +} +func (refusedErr) Temporary() bool { + return false +} var _ net.Error = timeoutErr{} var _ net.Error = refusedErr{} @@ -129,7 +145,9 @@ func TestOf_HTTPStatusWinsOverItsSentinel(t *testing.T) { type statusWithSentinel struct{ httpErr } -func (e *statusWithSentinel) Unwrap() error { return errs.ErrNotFound } +func (e *statusWithSentinel) Unwrap() error { + return errs.ErrNotFound +} func TestOf_TransportAndContext(t *testing.T) { for _, tc := range []struct { diff --git a/atlas-lib/errs/deferrers/deferrers_test.go b/atlas-lib/errs/deferrers/deferrers_test.go index c14181e7c..a8a4f2637 100644 --- a/atlas-lib/errs/deferrers/deferrers_test.go +++ b/atlas-lib/errs/deferrers/deferrers_test.go @@ -10,7 +10,9 @@ import ( type errCloser struct{ err error } -func (e errCloser) Close() error { return e.err } +func (e errCloser) Close() error { + return e.err +} func captureLogger(t *testing.T) *bytes.Buffer { t.Helper() diff --git a/atlas-lib/inventory/devices_fixture_test.go b/atlas-lib/inventory/devices_fixture_test.go index e9d834c37..255f50691 100644 --- a/atlas-lib/inventory/devices_fixture_test.go +++ b/atlas-lib/inventory/devices_fixture_test.go @@ -98,7 +98,9 @@ func unreadableDisks() *blockdev.Prober { // zeroReader serves zeros at every offset. type zeroReader struct{} -func (zeroReader) Close() error { return nil } +func (zeroReader) Close() error { + return nil +} func (zeroReader) ReadAt(ctx context.Context, p []byte, _ int64) (int, error) { if err := ctx.Err(); err != nil { @@ -113,4 +115,6 @@ func (zeroReader) ReadAt(ctx context.Context, p []byte, _ int64) (int, error) { // handsOverEveryDevice is an exclusive opener whose answer is always that // nothing holds the device, so the fixture's mounts and holders are the only // thing deciding a device's usage. -func handsOverEveryDevice(string) error { return nil } +func handsOverEveryDevice(string) error { + return nil +} diff --git a/atlas-lib/inventory/hugepages.go b/atlas-lib/inventory/hugepages.go index dbbb1b728..8d4e0d4c5 100644 --- a/atlas-lib/inventory/hugepages.go +++ b/atlas-lib/inventory/hugepages.go @@ -69,10 +69,14 @@ type HugePagePool struct { } // AllocatedBytes is how much memory this pool holds. -func (p HugePagePool) AllocatedBytes() uint64 { return p.Total * p.SizeBytes } +func (p HugePagePool) AllocatedBytes() uint64 { + return p.Total * p.SizeBytes +} // FreeBytes is how much of this pool nothing has taken. -func (p HugePagePool) FreeBytes() uint64 { return p.Free * p.SizeBytes } +func (p HugePagePool) FreeBytes() uint64 { + return p.Free * p.SizeBytes +} // NUMAHugePages is one memory node's share of one pool. type NUMAHugePages struct { diff --git a/atlas-lib/inventory/inventory.go b/atlas-lib/inventory/inventory.go index f23a77708..58a9c4a50 100644 --- a/atlas-lib/inventory/inventory.go +++ b/atlas-lib/inventory/inventory.go @@ -285,10 +285,14 @@ type NUMAHugePagesOfSize struct { } // AllocatedBytes is how much huge-page memory this entry accounts for. -func (n NUMAHugePagesOfSize) AllocatedBytes() uint64 { return n.Total * n.SizeBytes } +func (n NUMAHugePagesOfSize) AllocatedBytes() uint64 { + return n.Total * n.SizeBytes +} // FreeBytes is how much of it nothing has taken. -func (n NUMAHugePagesOfSize) FreeBytes() uint64 { return n.Free * n.SizeBytes } +func (n NUMAHugePagesOfSize) FreeBytes() uint64 { + return n.Free * n.SizeBytes +} // ByNUMANode is the inventory grouped by memory node, ascending, with one final // entry for what belongs to no node. diff --git a/atlas-lib/kube/derived.go b/atlas-lib/kube/derived.go index dfc2b8dbf..be428d537 100644 --- a/atlas-lib/kube/derived.go +++ b/atlas-lib/kube/derived.go @@ -157,19 +157,25 @@ type Derived struct { // Fits reports whether the formula's natural output was already within its // limit, which is the question a preflight check asks. A false Fits with a // legal [Derived.Value] is a name the migration would have to rewrite. -func (d Derived) Fits() bool { return !d.Truncated } +func (d Derived) Fits() bool { + return !d.Truncated +} // Errors reports what the API server would refuse about [Derived.Value], and is // empty for every value this package produces. It exists so a caller that builds // an identifier some other way can be checked against the same helpers. -func (d Derived) Errors() []string { return d.Kind.validate(d.Value) } +func (d Derived) Errors() []string { + return d.Kind.validate(d.Value) +} // Validate reports what the API server would refuse about a value used as this // kind, using the API server's own helpers rather than a second opinion about // them. It is exported for the caller that has to check a value a formula did // not produce, which is every value this product already wrote before the // formulas were written down. -func Validate(kind Kind, value string) []string { return kind.validate(value) } +func Validate(kind Kind, value string) []string { + return kind.validate(value) +} // Derive builds the identifier for these parts. // diff --git a/atlas-lib/link/errors.go b/atlas-lib/link/errors.go index ae9624d49..202390caf 100644 --- a/atlas-lib/link/errors.go +++ b/atlas-lib/link/errors.go @@ -20,7 +20,9 @@ var ErrNoSession error = noSessionError{} type noSessionError struct{} -func (noSessionError) Error() string { return "no live session for peer" } +func (noSessionError) Error() string { + return "no live session for peer" +} func (noSessionError) GRPCStatus() *status.Status { return status.New(codes.Unavailable, "no live session for peer") diff --git a/atlas-lib/link/hub.go b/atlas-lib/link/hub.go index 138f01cdc..e1252a5d5 100644 --- a/atlas-lib/link/hub.go +++ b/atlas-lib/link/hub.go @@ -123,10 +123,14 @@ func NewHub(cfg HubConfig) (*Hub, error) { } // Registry is the set of peers currently linked. -func (h *Hub) Registry() *Registry { return h.registry } +func (h *Hub) Registry() *Registry { + return h.registry +} // Addr is the address the hub listens on. -func (h *Hub) Addr() net.Addr { return h.cfg.Listener.Addr() } +func (h *Hub) Addr() net.Addr { + return h.cfg.Listener.Addr() +} // Serve accepts peer links until ctx ends or the listener fails, then shuts the // hub down: the listener closes and every session with it, so peers notice diff --git a/atlas-lib/link/peer.go b/atlas-lib/link/peer.go index dab5dff91..647f5dfed 100644 --- a/atlas-lib/link/peer.go +++ b/atlas-lib/link/peer.go @@ -34,16 +34,24 @@ type PeerID struct { } // NodePeer is the id of the CSI node plugin on the named Kubernetes node. -func NodePeer(node string) PeerID { return PeerID{Kind: PeerKindNode, Name: node} } +func NodePeer(node string) PeerID { + return PeerID{Kind: PeerKindNode, Name: node} +} // ControllerPeer is the id of the CSI controller plugin in the named pod. -func ControllerPeer(pod string) PeerID { return PeerID{Kind: PeerKindController, Name: pod} } +func ControllerPeer(pod string) PeerID { + return PeerID{Kind: PeerKindController, Name: pod} +} // String renders the id as "kind/name", e.g. "node/worker-3". -func (id PeerID) String() string { return string(id.Kind) + "/" + id.Name } +func (id PeerID) String() string { + return string(id.Kind) + "/" + id.Name +} // Zero reports whether the id names nothing. -func (id PeerID) Zero() bool { return id.Kind == "" && id.Name == "" } +func (id PeerID) Zero() bool { + return id.Kind == "" && id.Name == "" +} // validate rejects ids that cannot address a peer. It is deliberately not a // check against the known kinds: an authenticator is free to mint kinds this @@ -107,19 +115,29 @@ type Peer struct { // Conn is the connection to the peer: a client of every service the peer // registered on its side of the link. -func (p *Peer) Conn() grpc.ClientConnInterface { return p.session.Conn() } +func (p *Peer) Conn() grpc.ClientConnInterface { + return p.session.Conn() +} // HasCapability reports whether the peer said it serves the named capability. // Asking beats calling and handling codes.Unimplemented, which is // indistinguishable from a peer that is merely older. -func (p *Peer) HasCapability(name string) bool { return slices.Contains(p.Capabilities, name) } +func (p *Peer) HasCapability(name string) bool { + return slices.Contains(p.Capabilities, name) +} // Done is closed when the peer's session ends, whether it was torn down, timed // out or lost. -func (p *Peer) Done() <-chan struct{} { return p.session.Done() } +func (p *Peer) Done() <-chan struct{} { + return p.session.Done() +} // Close tears the peer's session down. The peer is expected to reconnect. -func (p *Peer) Close() error { return p.session.Close() } +func (p *Peer) Close() error { + return p.session.Close() +} // String renders the peer as "kind/name@instance-uid". -func (p *Peer) String() string { return p.ID.String() + "@" + p.InstanceUID } +func (p *Peer) String() string { + return p.ID.String() + "@" + p.InstanceUID +} diff --git a/atlas-lib/link/session.go b/atlas-lib/link/session.go index 04e3274bc..05af5687a 100644 --- a/atlas-lib/link/session.go +++ b/atlas-lib/link/session.go @@ -127,13 +127,19 @@ func newSession(raw net.Conn, client bool, cfg sessionConfig) (*Session, error) } // Conn is a client of every service the *other* end registered. -func (s *Session) Conn() *grpc.ClientConn { return s.cc } +func (s *Session) Conn() *grpc.ClientConn { + return s.cc +} // Done is closed once the session has ended, for whatever reason. -func (s *Session) Done() <-chan struct{} { return s.mux.CloseChan() } +func (s *Session) Done() <-chan struct{} { + return s.mux.CloseChan() +} // RemoteAddr is the address of the other end, for diagnostics. -func (s *Session) RemoteAddr() net.Addr { return s.mux.RemoteAddr() } +func (s *Session) RemoteAddr() net.Addr { + return s.mux.RemoteAddr() +} // Close ends the session and releases everything under it. It is idempotent, // and safe to call from inside an RPC handler running on this very session — diff --git a/atlas-lib/lvm/volume_test.go b/atlas-lib/lvm/volume_test.go index c8c0c3bc4..c2aaa2cf7 100644 --- a/atlas-lib/lvm/volume_test.go +++ b/atlas-lib/lvm/volume_test.go @@ -140,11 +140,17 @@ type fakeVolumeProvisioning struct { args []string } -func (f *fakeVolumeProvisioning) Name() string { return f.name } +func (f *fakeVolumeProvisioning) Name() string { + return f.name +} -func (f *fakeVolumeProvisioning) Handles(def LogicalVolumeDefinition) bool { return f.handles(def) } +func (f *fakeVolumeProvisioning) Handles(def LogicalVolumeDefinition) bool { + return f.handles(def) +} -func (f *fakeVolumeProvisioning) CreateVolumeArgs(LogicalVolumeDefinition) []string { return f.args } +func (f *fakeVolumeProvisioning) CreateVolumeArgs(LogicalVolumeDefinition) []string { + return f.args +} func TestManager_CreateLogicalVolume_DispatchesByHandles(t *testing.T) { RegisterVolumeProvisioning(&fakeVolumeProvisioning{ diff --git a/atlas-lib/nvme/siblings.go b/atlas-lib/nvme/siblings.go index 816a52d2c..7af6769ac 100644 --- a/atlas-lib/nvme/siblings.go +++ b/atlas-lib/nvme/siblings.go @@ -115,10 +115,14 @@ func IsCoTenant(d, o Device) bool { // IsSibling reports whether o is another block device backing the same volume as // d. It is the method form of the package-level IsSibling, for a call site that // reads as a question about d. Both are pure, comparing the two snapshots. -func (d Device) IsSibling(o Device) bool { return IsSibling(d, o) } +func (d Device) IsSibling(o Device) bool { + return IsSibling(d, o) +} // IsCoTenant reports whether o is a *different* volume sharing d's subsystem, // the relation that forbids disconnecting the subsystem for d alone. It is the // method form of the package-level IsCoTenant. Both are pure, comparing the // two snapshots. -func (d Device) IsCoTenant(o Device) bool { return IsCoTenant(d, o) } +func (d Device) IsCoTenant(o Device) bool { + return IsCoTenant(d, o) +} diff --git a/atlas-lib/nvmeof/detach_test.go b/atlas-lib/nvmeof/detach_test.go index 385e67a10..9cc79ede9 100644 --- a/atlas-lib/nvmeof/detach_test.go +++ b/atlas-lib/nvmeof/detach_test.go @@ -17,7 +17,9 @@ type recordingConnector struct { err error } -func (c *recordingConnector) Connect(context.Context, Target) error { return nil } +func (c *recordingConnector) Connect(context.Context, Target) error { + return nil +} func (c *recordingConnector) ConnectPaths(context.Context, []Target) ([]PathResult, error) { return nil, nil } @@ -36,7 +38,9 @@ func (c *recordingConnector) DisconnectController(_ context.Context, ctrl nvme.C c.controllers = append(c.controllers, ctrl.ID) return nil } -func (c *recordingConnector) IsConnected(context.Context, string) (bool, error) { return true, nil } +func (c *recordingConnector) IsConnected(context.Context, string) (bool, error) { + return true, nil +} // stubMultiNamespace substitutes the subsystem capability answer, standing in for // the Identify Controller command the real one may issue. diff --git a/atlas-lib/nvmeof/fabrics_test.go b/atlas-lib/nvmeof/fabrics_test.go index 0418cf6a8..b1c65b650 100644 --- a/atlas-lib/nvmeof/fabrics_test.go +++ b/atlas-lib/nvmeof/fabrics_test.go @@ -20,7 +20,9 @@ type fakeSubs struct { byNQN func(ctx context.Context, nqn string) (nvme.Subsystem, error) } -func (f fakeSubs) List(context.Context) ([]nvme.Subsystem, error) { return nil, nil } +func (f fakeSubs) List(context.Context) ([]nvme.Subsystem, error) { + return nil, nil +} func (f fakeSubs) ByNQN(ctx context.Context, nqn string) (nvme.Subsystem, error) { return f.byNQN(ctx, nqn) } diff --git a/atlas-lib/nvmeof/reconcile.go b/atlas-lib/nvmeof/reconcile.go index 1d58ffa0e..b85381c90 100644 --- a/atlas-lib/nvmeof/reconcile.go +++ b/atlas-lib/nvmeof/reconcile.go @@ -30,14 +30,20 @@ type PathState struct { } // Complete reports whether every published path is live. -func (s PathState) Complete() bool { return s.Expected > 0 && s.Live >= s.Expected } +func (s PathState) Complete() bool { + return s.Expected > 0 && s.Live >= s.Expected +} // Degraded reports whether the volume is usable but short of paths: I/O still // flows, with less redundancy than the control plane published. -func (s PathState) Degraded() bool { return s.Live > 0 && s.Live < s.Expected } +func (s PathState) Degraded() bool { + return s.Live > 0 && s.Live < s.Expected +} // Down reports whether no path is live, i.e., the volume cannot serve I/O. -func (s PathState) Down() bool { return s.Live == 0 } +func (s PathState) Down() bool { + return s.Live == 0 +} // ReconcilePaths makes the attached fabric paths of a volume match the control // plane's current answer, and reports the resulting state. diff --git a/atlas-lib/nvmeof/repair_test.go b/atlas-lib/nvmeof/repair_test.go index 2ff97799e..b6b973aea 100644 --- a/atlas-lib/nvmeof/repair_test.go +++ b/atlas-lib/nvmeof/repair_test.go @@ -145,7 +145,9 @@ func (k *kernel) DisconnectController(_ context.Context, ctrl nvme.Controller) e return nil } -func (k *kernel) IsConnected(context.Context, string) (bool, error) { return len(k.ctrls) > 0, nil } +func (k *kernel) IsConnected(context.Context, string) (bool, error) { + return len(k.ctrls) > 0, nil +} // subsystem is what a sysfs scan would report for the current state. func (k *kernel) subsystem() nvme.Subsystem { @@ -210,7 +212,9 @@ func (r kernelDevs) ListWithSelector(ctx context.Context, sel nvme.DeviceSelecto return sel.Filter(all), nil } -func (r kernelDevs) ByUUID(context.Context, string) (nvme.Device, error) { return nvme.Device{}, nil } +func (r kernelDevs) ByUUID(context.Context, string) (nvme.Device, error) { + return nvme.Device{}, nil +} func (r kernelDevs) ByDevicePath(context.Context, string) (nvme.Device, error) { return nvme.Device{}, nil } diff --git a/atlas-lib/nvmeof/wait_test.go b/atlas-lib/nvmeof/wait_test.go index 45feefe8e..8aceea3e5 100644 --- a/atlas-lib/nvmeof/wait_test.go +++ b/atlas-lib/nvmeof/wait_test.go @@ -42,7 +42,9 @@ func (f *fakeDevs) ListWithSelector(ctx context.Context, sel nvme.DeviceSelector return sel.Filter(all), nil } -func (f *fakeDevs) ByUUID(context.Context, string) (nvme.Device, error) { return nvme.Device{}, nil } +func (f *fakeDevs) ByUUID(context.Context, string) (nvme.Device, error) { + return nvme.Device{}, nil +} func (f *fakeDevs) ByDevicePath(context.Context, string) (nvme.Device, error) { return nvme.Device{}, nil } diff --git a/atlas-lib/pci/scan.go b/atlas-lib/pci/scan.go index 94f123b34..f15d87b2c 100644 --- a/atlas-lib/pci/scan.go +++ b/atlas-lib/pci/scan.go @@ -121,7 +121,9 @@ type Device struct { } // IsNVMe reports whether the device is an NVMe controller. -func (d Device) IsNVMe() bool { return strings.HasPrefix(d.Class, classNVMePrefix) } +func (d Device) IsNVMe() bool { + return strings.HasPrefix(d.Class, classNVMePrefix) +} // BoundToUserspace reports whether a userspace-IO driver owns the device, which // on this product's hosts means SPDK has taken it or something left it taken. @@ -130,7 +132,9 @@ func (d Device) BoundToUserspace() bool { } // HasKernelDriver reports whether any driver owns it at all. -func (d Device) HasKernelDriver() bool { return d.Driver != "" } +func (d Device) HasKernelDriver() bool { + return d.Driver != "" +} // String renders the device for a log line or an event. func (d Device) String() string { diff --git a/atlas-lib/prometheus/capacity.go b/atlas-lib/prometheus/capacity.go index 71cbf0eff..89c197105 100644 --- a/atlas-lib/prometheus/capacity.go +++ b/atlas-lib/prometheus/capacity.go @@ -41,7 +41,9 @@ type Capacity struct { // Sampled reports whether the control plane has ever taken this reading. An // unsampled Capacity is all zeros, which is indistinguishable from a genuinely // empty entity without asking. -func (c Capacity) Sampled() bool { return !c.SampledAt.IsZero() } +func (c Capacity) Sampled() bool { + return !c.SampledAt.IsZero() +} // The entity a capacity sample belongs to, as the exporter names it: the metric // prefix, and the label carrying the entity's UUID. diff --git a/atlas-lib/prometheus/client.go b/atlas-lib/prometheus/client.go index 220a19e11..89a15578e 100644 --- a/atlas-lib/prometheus/client.go +++ b/atlas-lib/prometheus/client.go @@ -121,4 +121,6 @@ func (p *Provider) queryMatrix( // whole rounds a sample to an integer. Prometheus carries every value as a // float, and these are byte counts, nanosecond latencies, and second-resolution // timestamps that were integers before they were scraped. -func whole(v float64) int64 { return int64(math.Round(v)) } +func whole(v float64) int64 { + return int64(math.Round(v)) +} diff --git a/atlas-lib/storage/accessor_test.go b/atlas-lib/storage/accessor_test.go index 2ecefb08b..52419d73a 100644 --- a/atlas-lib/storage/accessor_test.go +++ b/atlas-lib/storage/accessor_test.go @@ -18,7 +18,9 @@ type fakeDevices struct { calls int } -func (f *fakeDevices) List(context.Context) ([]nvme.Device, error) { return f.devs, nil } +func (f *fakeDevices) List(context.Context) ([]nvme.Device, error) { + return f.devs, nil +} func (f *fakeDevices) ListWithSelector(_ context.Context, sel nvme.DeviceSelector) ([]nvme.Device, error) { f.calls++ diff --git a/atlas-lib/storage/storagerpc/server.go b/atlas-lib/storage/storagerpc/server.go index 0223fd6f6..b17100858 100644 --- a/atlas-lib/storage/storagerpc/server.go +++ b/atlas-lib/storage/storagerpc/server.go @@ -22,7 +22,9 @@ const ( ) // Capabilities is what [NewServer] serves, for an agent's Hello. -func Capabilities() []string { return []string{CapabilitySubsystems, CapabilityDevices} } +func Capabilities() []string { + return []string{CapabilitySubsystems, CapabilityDevices} +} // Server answers NVMe lookups against the node it runs on. // diff --git a/atlas-lib/storage/storagerpc/storagerpc_test.go b/atlas-lib/storage/storagerpc/storagerpc_test.go index 571298215..c96db2550 100644 --- a/atlas-lib/storage/storagerpc/storagerpc_test.go +++ b/atlas-lib/storage/storagerpc/storagerpc_test.go @@ -52,7 +52,9 @@ type fakeDevices struct { err error } -func (f *fakeDevices) List(context.Context) ([]nvme.Device, error) { return f.devices, f.err } +func (f *fakeDevices) List(context.Context) ([]nvme.Device, error) { + return f.devices, f.err +} func (f *fakeDevices) ListWithSelector(ctx context.Context, sel nvme.DeviceSelector) ([]nvme.Device, error) { all, err := f.List(ctx) diff --git a/atlas-lib/volstack/layer.go b/atlas-lib/volstack/layer.go index 7a112160b..59fb733a3 100644 --- a/atlas-lib/volstack/layer.go +++ b/atlas-lib/volstack/layer.go @@ -147,7 +147,9 @@ type Geometry struct { // Known reports whether this geometry describes a layout a layer above can align // to. A virtualized device reports the zero value, and a filesystem over one // passes no stripe alignment because there is nothing real to align to. -func (g Geometry) Known() bool { return g.ChunkBytes > 0 && g.Stripes > 0 } +func (g Geometry) Known() bool { + return g.ChunkBytes > 0 && g.Stripes > 0 +} // Healer is implemented by a layer whose object can go bad under a live stack // and be repaired in place. Heal never recreates: the data already exists. diff --git a/atlas-lib/volstack/layers/fabric.go b/atlas-lib/volstack/layers/fabric.go index 78549a519..3ff820b6a 100644 --- a/atlas-lib/volstack/layers/fabric.go +++ b/atlas-lib/volstack/layers/fabric.go @@ -52,10 +52,14 @@ type Fabric struct { } // NewFabric returns the fabric layer for one volume. -func NewFabric(cfg FabricConfig) *Fabric { return &Fabric{cfg: cfg} } +func NewFabric(cfg FabricConfig) *Fabric { + return &Fabric{cfg: cfg} +} // Name is what the record calls this layer. -func (f *Fabric) Name() string { return "fabric" } +func (f *Fabric) Name() string { + return "fabric" +} // selector identifies this volume's namespace among everything attached. func (f *Fabric) selector() nvme.DeviceSelector { @@ -174,7 +178,9 @@ func (f *Fabric) Release(ctx context.Context, _ volstack.Artifact) error { // Destroy does nothing. The namespace belongs to the control plane and is // removed by DeleteVolume, so there is no durable object here for a node to take // away. -func (f *Fabric) Destroy(context.Context, volstack.Artifact) error { return nil } +func (f *Fabric) Destroy(context.Context, volstack.Artifact) error { + return nil +} // Healthy reports whether the volume can currently take I/O, which is the read a // heal dispatches on. diff --git a/atlas-lib/volstack/layers/fabric_test.go b/atlas-lib/volstack/layers/fabric_test.go index 3fc8c4a3c..2b5e3f5ac 100644 --- a/atlas-lib/volstack/layers/fabric_test.go +++ b/atlas-lib/volstack/layers/fabric_test.go @@ -33,7 +33,9 @@ type fakeConnector struct { disconnectErr error } -func (f *fakeConnector) Connect(context.Context, nvmeof.Target) error { return f.connectErr } +func (f *fakeConnector) Connect(context.Context, nvmeof.Target) error { + return f.connectErr +} func (f *fakeConnector) ConnectPaths(_ context.Context, ts []nvmeof.Target) ([]nvmeof.PathResult, error) { f.connected = append(f.connected, ts) @@ -52,9 +54,13 @@ func (f *fakeConnector) Disconnect(_ context.Context, nqn string) error { return f.disconnectErr } -func (f *fakeConnector) DisconnectController(context.Context, nvme.Controller) error { return nil } +func (f *fakeConnector) DisconnectController(context.Context, nvme.Controller) error { + return nil +} -func (f *fakeConnector) IsConnected(context.Context, string) (bool, error) { return false, nil } +func (f *fakeConnector) IsConnected(context.Context, string) (bool, error) { + return false, nil +} // fakeDevices answers device lookups from a fixed list. type fakeDevices struct { @@ -66,7 +72,9 @@ type fakeDevices struct { asked []nvme.DeviceSelector } -func (f *fakeDevices) List(context.Context) ([]nvme.Device, error) { return f.devices, f.err } +func (f *fakeDevices) List(context.Context) ([]nvme.Device, error) { + return f.devices, f.err +} func (f *fakeDevices) ListWithSelector(_ context.Context, sel nvme.DeviceSelector) ([]nvme.Device, error) { f.asked = append(f.asked, sel) diff --git a/atlas-lib/volstack/layers/filesystem.go b/atlas-lib/volstack/layers/filesystem.go index 265d54bd6..0da843417 100644 --- a/atlas-lib/volstack/layers/filesystem.go +++ b/atlas-lib/volstack/layers/filesystem.go @@ -97,10 +97,14 @@ type Filesystem struct { } // NewFilesystem returns the filesystem layer for one volume. -func NewFilesystem(cfg FilesystemConfig) *Filesystem { return &Filesystem{cfg: cfg} } +func NewFilesystem(cfg FilesystemConfig) *Filesystem { + return &Filesystem{cfg: cfg} +} // Name is what the record calls this layer. -func (f *Filesystem) Name() string { return "filesystem" } +func (f *Filesystem) Name() string { + return "filesystem" +} // Observe reads the device below and reports what may be done to it. // @@ -234,7 +238,9 @@ func (f *Filesystem) clear(ctx context.Context) error { // Destroy does nothing. Removing a filesystem means removing the volume it is // on, which is the control plane's, and a node reaching for that on a teardown // is the defect the separation of Release and Destroy exists to prevent. -func (f *Filesystem) Destroy(context.Context, volstack.Artifact) error { return nil } +func (f *Filesystem) Destroy(context.Context, volstack.Artifact) error { + return nil +} // Healthy reports whether the mount is still serving. // diff --git a/atlas-lib/volstack/layers/filesystem_strategy.go b/atlas-lib/volstack/layers/filesystem_strategy.go index 25be32a0b..4ffea2449 100644 --- a/atlas-lib/volstack/layers/filesystem_strategy.go +++ b/atlas-lib/volstack/layers/filesystem_strategy.go @@ -87,7 +87,9 @@ func FilesystemStrategyFor(fsType string) FilesystemLayerStrategy { // extStrategy is the ext family. type extStrategy struct{ fsType string } -func (e extStrategy) Name() string { return e.fsType } +func (e extStrategy) Name() string { + return e.fsType +} // extBlockBytes is the block size mkfs picks for a volume of any size worth // striping, and the unit stride and stripe_width are counted in. A filesystem @@ -139,12 +141,16 @@ func (e extStrategy) GrowCommand(device, _ string) []string { // MountFlags adds nothing: ext mounts a volume and its clone side by side // without complaint, since it does not refuse a filesystem whose UUID it has // already seen. -func (e extStrategy) MountFlags(flags []string) []string { return flags } +func (e extStrategy) MountFlags(flags []string) []string { + return flags +} // xfsStrategy is XFS. type xfsStrategy struct{} -func (xfsStrategy) Name() string { return "xfs" } +func (xfsStrategy) Name() string { + return "xfs" +} // FormatOptions align the filesystem to the stripes underneath it, so a write // that fills one chunk lands on one member rather than across two. @@ -180,13 +186,21 @@ func (xfsStrategy) GrowCommand(_, mountpoint string) []string { // contributes nothing to either question rather than being turned away. type plainStrategy struct{ fsType string } -func (p plainStrategy) Name() string { return p.fsType } +func (p plainStrategy) Name() string { + return p.fsType +} -func (p plainStrategy) FormatOptions(options []string, _ FormatParameters) []string { return options } +func (p plainStrategy) FormatOptions(options []string, _ FormatParameters) []string { + return options +} -func (p plainStrategy) MountFlags(flags []string) []string { return flags } +func (p plainStrategy) MountFlags(flags []string) []string { + return flags +} // GrowCommand is nil. Nothing here knows how to grow a filesystem it knows // nothing else about, and guessing at a tool name would run something arbitrary // against a volume holding data. -func (p plainStrategy) GrowCommand(_, _ string) []string { return nil } +func (p plainStrategy) GrowCommand(_, _ string) []string { + return nil +} diff --git a/atlas-lib/volstack/layers/filesystem_test.go b/atlas-lib/volstack/layers/filesystem_test.go index 9da1ce2f2..b3e0862b8 100644 --- a/atlas-lib/volstack/layers/filesystem_test.go +++ b/atlas-lib/volstack/layers/filesystem_test.go @@ -46,7 +46,9 @@ type mountCall struct { options []string } -func newFakeFS() *fakeFS { return &fakeFS{mountPoints: map[string]bool{}} } +func newFakeFS() *fakeFS { + return &fakeFS{mountPoints: map[string]bool{}} +} func (f *fakeFS) Format(_ context.Context, device, fsType string, options []string) error { f.formatted = append(f.formatted, formatCall{device, fsType, options}) diff --git a/atlas-lib/volstack/layers/lvmphysicalvolume.go b/atlas-lib/volstack/layers/lvmphysicalvolume.go index b369ec43e..1f0fb0ee3 100644 --- a/atlas-lib/volstack/layers/lvmphysicalvolume.go +++ b/atlas-lib/volstack/layers/lvmphysicalvolume.go @@ -61,7 +61,9 @@ func NewLVMPhysicalVolume(cfg LVMPhysicalVolumeConfig) *LVMPhysicalVolume { } // Name is what the record calls this layer. -func (l *LVMPhysicalVolume) Name() string { return "lvmPhysicalVolume" } +func (l *LVMPhysicalVolume) Name() string { + return "lvmPhysicalVolume" +} // Observe reads the device below and reports what may be done to it. // @@ -227,7 +229,9 @@ func (l *LVMPhysicalVolume) Ensure(ctx context.Context, below volstack.Artifact) // hold: it is on the device wherever the device is, so there is nothing for this // host to give up, and an unstage that removed one would be wiping the identity // off a volume that is merely between pods. -func (l *LVMPhysicalVolume) Release(context.Context, volstack.Artifact) error { return nil } +func (l *LVMPhysicalVolume) Release(context.Context, volstack.Artifact) error { + return nil +} // Destroy wipes the label, which is what makes the device blank again for // anything that reads its content. Only a deletion path reaches it, and by then diff --git a/atlas-lib/volstack/layers/lvmphysicalvolume_test.go b/atlas-lib/volstack/layers/lvmphysicalvolume_test.go index 74e4087e8..c169b7445 100644 --- a/atlas-lib/volstack/layers/lvmphysicalvolume_test.go +++ b/atlas-lib/volstack/layers/lvmphysicalvolume_test.go @@ -84,7 +84,9 @@ func keysFor(args []string) []string { } // ran reports whether this command was issued at all. -func (l *lvmCommands) ran(command string) bool { return l.indexOf(command) >= 0 } +func (l *lvmCommands) ran(command string) bool { + return l.indexOf(command) >= 0 +} // indexOf is where this command was issued, or -1, so a test can assert an order // as well as a presence. @@ -106,7 +108,9 @@ func (l *lvmCommands) issued() string { return strings.Join(lines, "\n") } -func (l *lvmCommands) manager() *lvm.Manager { return lvm.NewManagerWithRunner(l.run) } +func (l *lvmCommands) manager() *lvm.Manager { + return lvm.NewManagerWithRunner(l.run) +} const ( testVG = "vol-33333333-3333-3333-3333-333333333333" diff --git a/atlas-lib/volstack/layers/lvmvolume.go b/atlas-lib/volstack/layers/lvmvolume.go index 0280a063e..46aae1b92 100644 --- a/atlas-lib/volstack/layers/lvmvolume.go +++ b/atlas-lib/volstack/layers/lvmvolume.go @@ -65,10 +65,14 @@ func NewLVMLogicalVolume(cfg LVMLogicalVolumeConfig) *LVMLogicalVolume { } // Name is what the record calls this layer. -func (l *LVMLogicalVolume) Name() string { return "lvmLogicalVolume" } +func (l *LVMLogicalVolume) Name() string { + return "lvmLogicalVolume" +} // group is this volume's volume group. -func (l *LVMLogicalVolume) group() lvm.VolumeGroup { return lvm.VolumeGroup{Name: l.cfg.VolumeGroup} } +func (l *LVMLogicalVolume) group() lvm.VolumeGroup { + return lvm.VolumeGroup{Name: l.cfg.VolumeGroup} +} // volume is this volume's logical volume. func (l *LVMLogicalVolume) volume() lvm.LogicalVolume { @@ -230,7 +234,9 @@ func (l *LVMLogicalVolume) create(ctx context.Context) error { // down through both, so the hold is given up either way, and giving it up twice // would mean this layer deactivating everything else in the group along with its // own volume. -func (l *LVMLogicalVolume) Release(context.Context, volstack.Artifact) error { return nil } +func (l *LVMLogicalVolume) Release(context.Context, volstack.Artifact) error { + return nil +} // Destroy removes the volume and the data in it. The group that held it goes // with the layer below, which a teardown reaches next. Only a deletion path @@ -281,11 +287,15 @@ func (l *LVMLogicalVolume) target() lvm.LogicalVolume { // NodeCapability is what a node must carry for this volume's type to run there. // A type needing a kernel module the node does not have fails as a mount error on // the wrong node otherwise, discovered instead of reported. -func (l *LVMLogicalVolume) NodeCapability() volstack.Capability { return l.cfg.Capability } +func (l *LVMLogicalVolume) NodeCapability() volstack.Capability { + return l.cfg.Capability +} // PinsToNode reports false: this layer's durable state is the LVM metadata, which // lives on the device and travels with it. Nothing of it stays on the host. -func (l *LVMLogicalVolume) PinsToNode() bool { return false } +func (l *LVMLogicalVolume) PinsToNode() bool { + return false +} // LVMLogicalVolumeParams is what the record carries for this layer. type LVMLogicalVolumeParams struct { diff --git a/atlas-lib/volstack/layers/lvmvolume_test.go b/atlas-lib/volstack/layers/lvmvolume_test.go index 331beba33..7ee545e82 100644 --- a/atlas-lib/volstack/layers/lvmvolume_test.go +++ b/atlas-lib/volstack/layers/lvmvolume_test.go @@ -48,7 +48,9 @@ func newLVMVolume(vg, lvs, attr string, def lvm.LogicalVolumeDefinition) *lvmVol } // present is the lvs listing for a volume group holding this volume. -func present() string { return " " + testLV + "\n" } +func present() string { + return " " + testLV + "\n" +} // A group holding no volume of ours is the only state an lvcreate may run in. // The group itself is already there by then, made by the layer below. diff --git a/atlas-lib/volstack/layers/lvmvolumegroup.go b/atlas-lib/volstack/layers/lvmvolumegroup.go index 6351c0f3c..30264e61b 100644 --- a/atlas-lib/volstack/layers/lvmvolumegroup.go +++ b/atlas-lib/volstack/layers/lvmvolumegroup.go @@ -42,7 +42,9 @@ func NewLVMVolumeGroup(cfg LVMVolumeGroupConfig) *LVMVolumeGroup { } // Name is what the record calls this layer. -func (l *LVMVolumeGroup) Name() string { return "lvmVolumeGroup" } +func (l *LVMVolumeGroup) Name() string { + return "lvmVolumeGroup" +} // membership is which of the devices below already belong to this volume's // group, and which do not. diff --git a/atlas-lib/volstack/layers/lvmvolumegroup_test.go b/atlas-lib/volstack/layers/lvmvolumegroup_test.go index f2cc279b1..45bb6102f 100644 --- a/atlas-lib/volstack/layers/lvmvolumegroup_test.go +++ b/atlas-lib/volstack/layers/lvmvolumegroup_test.go @@ -27,7 +27,9 @@ func newLVMGroup(perDevice map[string]string) (*LVMVolumeGroup, *lvmCommands) { } // ours is what pvs prints for a member of this volume's group. -func ours() string { return " " + testVG + "\n" } +func ours() string { + return " " + testVG + "\n" +} // Members carrying no group at all are what a bring-up creates one over. func TestLVMVolumeGroupAbsentCreates(t *testing.T) { diff --git a/atlas-lib/volstack/layers/members.go b/atlas-lib/volstack/layers/members.go index b94d87975..553bd6abc 100644 --- a/atlas-lib/volstack/layers/members.go +++ b/atlas-lib/volstack/layers/members.go @@ -29,14 +29,20 @@ type Members struct { // NewMembers returns a composite over the given layers, in the order they are to // be assembled. -func NewMembers(members volstack.Plan) *Members { return &Members{members: members} } +func NewMembers(members volstack.Plan) *Members { + return &Members{members: members} +} // Name is what the record calls this layer. -func (m *Members) Name() string { return "members" } +func (m *Members) Name() string { + return "members" +} // Members is the sub-plan, which the record carries as a field of its own so // that a teardown replays the order rather than re-deriving it. -func (m *Members) Members() volstack.Plan { return m.members } +func (m *Members) Members() volstack.Plan { + return m.members +} // Observe reports the composite as only as present as its members. // diff --git a/atlas-lib/volstack/layers/members_test.go b/atlas-lib/volstack/layers/members_test.go index cb463ebe8..79f3341ba 100644 --- a/atlas-lib/volstack/layers/members_test.go +++ b/atlas-lib/volstack/layers/members_test.go @@ -27,9 +27,13 @@ type memberLayer struct { ensureErr error } -func (m *memberLayer) Name() string { return m.name } +func (m *memberLayer) Name() string { + return m.name +} -func (m *memberLayer) note(verb string) { *m.log = append(*m.log, m.name+":"+verb) } +func (m *memberLayer) note(verb string) { + *m.log = append(*m.log, m.name+":"+verb) +} func (m *memberLayer) own() volstack.Artifact { if m.state == volstack.StateAbsent { diff --git a/atlas-lib/volstack/layers/staging_probe_test.go b/atlas-lib/volstack/layers/staging_probe_test.go index d80fe7660..c00a26573 100644 --- a/atlas-lib/volstack/layers/staging_probe_test.go +++ b/atlas-lib/volstack/layers/staging_probe_test.go @@ -19,7 +19,9 @@ import ( // the only probes a test counts are the filesystem layer's. type constDevice struct{} -func (constDevice) Name() string { return "below" } +func (constDevice) Name() string { + return "below" +} func (constDevice) Observe(context.Context, volstack.Artifact) (volstack.State, volstack.Artifact, error) { return volstack.StateReady, belowArtifact(), nil @@ -29,8 +31,12 @@ func (constDevice) Ensure(context.Context, volstack.Artifact) (volstack.Artifact return belowArtifact(), nil } -func (constDevice) Release(context.Context, volstack.Artifact) error { return nil } -func (constDevice) Destroy(context.Context, volstack.Artifact) error { return nil } +func (constDevice) Release(context.Context, volstack.Artifact) error { + return nil +} +func (constDevice) Destroy(context.Context, volstack.Artifact) error { + return nil +} func TestBringingAStackUpReadsTheDeviceOnce(t *testing.T) { cases := []struct { diff --git a/atlas-lib/volstack/plans/plans.go b/atlas-lib/volstack/plans/plans.go index c970aa0ac..a200966e7 100644 --- a/atlas-lib/volstack/plans/plans.go +++ b/atlas-lib/volstack/plans/plans.go @@ -69,19 +69,27 @@ type Volume struct { } // VolumeGroup is the name of the group this volume's LVM layers use. -func (v Volume) VolumeGroup() string { return VolumeGroupName(v.UUID) } +func (v Volume) VolumeGroup() string { + return VolumeGroupName(v.UUID) +} // LogicalVolume is the name of the one logical volume inside that group. -func (v Volume) LogicalVolume() string { return LogicalVolumeName(v.UUID) } +func (v Volume) LogicalVolume() string { + return LogicalVolumeName(v.UUID) +} // VolumeGroupName is the group-naming rule, exported for a caller that has a // volume's identity and no plan: a teardown working from a stack record, or a // sweep looking for what this driver left on a node, has to name the group the // way the plan that created it did, character for character. -func VolumeGroupName(uuid string) string { return volumeGroupPrefix + uuid } +func VolumeGroupName(uuid string) string { + return volumeGroupPrefix + uuid +} // LogicalVolumeName is the same rule for the volume inside the group. -func LogicalVolumeName(uuid string) string { return logicalVolumePrefix + uuid } +func LogicalVolumeName(uuid string) string { + return logicalVolumePrefix + uuid +} // LogicalVolumeOptions is what the LVM rows differ in. The linear, VDO, and // striped plans use one lvmLogicalVolume layer with different contents here, diff --git a/atlas-lib/volstack/plans/plans_test.go b/atlas-lib/volstack/plans/plans_test.go index ba0c59a12..cdceb1b5a 100644 --- a/atlas-lib/volstack/plans/plans_test.go +++ b/atlas-lib/volstack/plans/plans_test.go @@ -252,11 +252,17 @@ func (r *recordingFS) Mount(_ context.Context, _, target, _ string, _ []string) return nil } -func (r *recordingFS) Unmount(context.Context, string) error { return nil } +func (r *recordingFS) Unmount(context.Context, string) error { + return nil +} -func (r *recordingFS) ForceUnmount(context.Context, string) error { return nil } +func (r *recordingFS) ForceUnmount(context.Context, string) error { + return nil +} -func (r *recordingFS) Grow(context.Context, []string) error { return nil } +func (r *recordingFS) Grow(context.Context, []string) error { + return nil +} func (r *recordingFS) IsMountPoint(_ context.Context, path string) (bool, error) { r.checked = append(r.checked, path) diff --git a/atlas-lib/volstack/record.go b/atlas-lib/volstack/record.go index 1023cf493..7b9de60da 100644 --- a/atlas-lib/volstack/record.go +++ b/atlas-lib/volstack/record.go @@ -72,7 +72,9 @@ type Store struct { // NewStore returns a store over dir, which has to outlive the container: a // plugin restart is an ordinary event and the record is the only thing that // tells the restarted process what the previous one built. -func NewStore(dir string) *Store { return &Store{dir: dir} } +func NewStore(dir string) *Store { + return &Store{dir: dir} +} // path is the record's file, named for the volume handle with the separators a // handle carries made safe for a filename. It never leaves the store's own diff --git a/atlas-lib/volstack/runner.go b/atlas-lib/volstack/runner.go index 7387c6e9a..783e5c6fa 100644 --- a/atlas-lib/volstack/runner.go +++ b/atlas-lib/volstack/runner.go @@ -22,7 +22,9 @@ type Runner struct { } // NewRunner returns a runner recording into store. -func NewRunner(store *Store) *Runner { return &Runner{store: store} } +func NewRunner(store *Store) *Runner { + return &Runner{store: store} +} // Up brings the plan up bottom to top and returns what the topmost layer // exposes, which is what the RPC acts on. diff --git a/atlas-lib/volstack/runner_test.go b/atlas-lib/volstack/runner_test.go index fb5de4bee..7ec4fd523 100644 --- a/atlas-lib/volstack/runner_test.go +++ b/atlas-lib/volstack/runner_test.go @@ -37,9 +37,13 @@ type fakeLayer struct { ensured []string // the device names this layer was handed, per Ensure } -func (f *fakeLayer) Name() string { return f.name } +func (f *fakeLayer) Name() string { + return f.name +} -func (f *fakeLayer) note(verb string) { *f.log = append(*f.log, f.name+":"+verb) } +func (f *fakeLayer) note(verb string) { + *f.log = append(*f.log, f.name+":"+verb) +} func (f *fakeLayer) Observe(_ context.Context, below Artifact) (State, Artifact, error) { f.note("observe") @@ -533,7 +537,9 @@ type compositeLayer struct { members Plan } -func (c compositeLayer) Members() Plan { return c.members } +func (c compositeLayer) Members() Plan { + return c.members +} // A fan-in layer's sub-plan is recorded, in order and as a field of its own. // The order cannot be recovered from a set, and a failover that reassembles the diff --git a/csi-driver/internal/controlplane/cluster.go b/csi-driver/internal/controlplane/cluster.go index 1259e0d55..2c39b2f3e 100644 --- a/csi-driver/internal/controlplane/cluster.go +++ b/csi-driver/internal/controlplane/cluster.go @@ -93,13 +93,19 @@ type ClusterClient struct { poolID string // pool scope for this client, empty meaning cluster-level only } -func (c *ClusterClient) ClusterID() string { return c.API.ClusterID } -func (c *ClusterClient) PoolID() string { return c.poolID } +func (c *ClusterClient) ClusterID() string { + return c.API.ClusterID +} +func (c *ClusterClient) PoolID() string { + return c.poolID +} // ScopeToPool binds this client to one storage pool, so a volume operation // need not scan every pool to find its volume. It is set once, immediately // after construction, by whoever resolved the pool reference. -func (c *ClusterClient) ScopeToPool(poolID string) { c.poolID = poolID } +func (c *ClusterClient) ScopeToPool(poolID string) { + c.poolID = poolID +} // poolForVolume returns the pool ID for lvolID. If this client is already // scoped to a pool, that pool ID is returned immediately. Otherwise, all pools diff --git a/csi-driver/internal/csi/controller/errorclass_rpc.go b/csi-driver/internal/csi/controller/errorclass_rpc.go index 16dd58c2c..a7c6b6c78 100644 --- a/csi-driver/internal/csi/controller/errorclass_rpc.go +++ b/csi-driver/internal/csi/controller/errorclass_rpc.go @@ -56,14 +56,20 @@ var _ interface { // IsIdempotent reports that the handler must resolve a conflict by looking up the // existing object (e.g., a 409 on create) before returning. -func (c classifiedError) IsIdempotent() bool { return c.class.Idempotent } +func (c classifiedError) IsIdempotent() bool { + return c.class.Idempotent +} // IsSuccess reports that the error is a no-op for this RPC and it should return // success (e.g., a 404 on delete). -func (c classifiedError) IsSuccess() bool { return c.class.Success } +func (c classifiedError) IsSuccess() bool { + return c.class.Success +} // Retryable reports whether retrying the operation can help. -func (c classifiedError) Retryable() bool { return c.class.Retryable } +func (c classifiedError) Retryable() bool { + return c.class.Retryable +} // Error implements error. func (c classifiedError) Error() string { diff --git a/csi-driver/internal/csi/controller/mock_controlplane_test.go b/csi-driver/internal/csi/controller/mock_controlplane_test.go index e6df7c0d3..26ec5a823 100644 --- a/csi-driver/internal/csi/controller/mock_controlplane_test.go +++ b/csi-driver/internal/csi/controller/mock_controlplane_test.go @@ -150,8 +150,12 @@ func newMockSBCLI() *mockSBCLI { return m } -func (m *mockSBCLI) URL() string { return m.srv.URL } -func (m *mockSBCLI) Close() { m.srv.Close() } +func (m *mockSBCLI) URL() string { + return m.srv.URL +} +func (m *mockSBCLI) Close() { + m.srv.Close() +} // locked wraps a handler so it holds the mock's mutex for the duration of the call. func (m *mockSBCLI) locked(h http.HandlerFunc) http.HandlerFunc { diff --git a/csi-driver/internal/fabric/repair_test.go b/csi-driver/internal/fabric/repair_test.go index 0ff5bff7b..e5028e0f2 100644 --- a/csi-driver/internal/fabric/repair_test.go +++ b/csi-driver/internal/fabric/repair_test.go @@ -425,13 +425,17 @@ func (f fakeSubs) ByNQN(_ context.Context, nqn string) (nvme.Subsystem, error) { type fakeDevs struct{ devices []nvme.Device } -func (f fakeDevs) List(context.Context) ([]nvme.Device, error) { return f.devices, nil } +func (f fakeDevs) List(context.Context) ([]nvme.Device, error) { + return f.devices, nil +} func (f fakeDevs) ListWithSelector(_ context.Context, sel nvme.DeviceSelector) ([]nvme.Device, error) { return sel.Filter(f.devices), nil } -func (f fakeDevs) ByUUID(context.Context, string) (nvme.Device, error) { return nvme.Device{}, errs404 } +func (f fakeDevs) ByUUID(context.Context, string) (nvme.Device, error) { + return nvme.Device{}, errs404 +} func (f fakeDevs) ByDevicePath(context.Context, string) (nvme.Device, error) { return nvme.Device{}, errs404 } diff --git a/csi-driver/internal/guardian/guardian_test.go b/csi-driver/internal/guardian/guardian_test.go index 67b7f9717..6b46c2991 100644 --- a/csi-driver/internal/guardian/guardian_test.go +++ b/csi-driver/internal/guardian/guardian_test.go @@ -46,7 +46,9 @@ const ( // ─── helpers ────────────────────────────────────────────────────────────────── -func boolPtr(b bool) *bool { return &b } +func boolPtr(b bool) *bool { + return &b +} func newTestGuardian(cs *fake.Clientset) *Guardian { return &Guardian{ diff --git a/operator/cmd/simplyblock-nodeprobe/main_test.go b/operator/cmd/simplyblock-nodeprobe/main_test.go index 353d87a82..2d5c8fdc9 100644 --- a/operator/cmd/simplyblock-nodeprobe/main_test.go +++ b/operator/cmd/simplyblock-nodeprobe/main_test.go @@ -17,7 +17,9 @@ import ( // noEnv is the environment of a probe run by hand, with none of what the Job // sets. -func noEnv(string) string { return "" } +func noEnv(string) string { + return "" +} // envOf serves a fixed environment, which is how the Job's field references // reach the process. diff --git a/operator/cmd/simplyblock-upgrade/run.go b/operator/cmd/simplyblock-upgrade/run.go index ab7dc86da..03741257d 100644 --- a/operator/cmd/simplyblock-upgrade/run.go +++ b/operator/cmd/simplyblock-upgrade/run.go @@ -27,7 +27,9 @@ type session struct { } // Close gives the terminal back. -func (s *session) Close() error { return s.Reporter.Close() } +func (s *session) Close() error { + return s.Reporter.Close() +} // newSession connects, builds the catalog, and returns a runner over the // cluster. diff --git a/operator/internal/autoplacement/autobalancing_test.go b/operator/internal/autoplacement/autobalancing_test.go index ba44ecc3e..8732a5914 100644 --- a/operator/internal/autoplacement/autobalancing_test.go +++ b/operator/internal/autoplacement/autobalancing_test.go @@ -30,7 +30,9 @@ func makeVP(uuid, nodeUUID, poolUUID, status string, migrating bool, iops float6 } } -func neverCooling(_ string) bool { return false } +func neverCooling(_ string) bool { + return false +} // volsByNode builds the volumesByNode map expected by SelectVolumesForMigration. func volsByNode(vps ...VolumePlacement) map[string][]VolumePlacement { diff --git a/operator/internal/controller/storagedevice_controller_unit_test.go b/operator/internal/controller/storagedevice_controller_unit_test.go index a5f6b0e6f..d05bfff75 100644 --- a/operator/internal/controller/storagedevice_controller_unit_test.go +++ b/operator/internal/controller/storagedevice_controller_unit_test.go @@ -30,9 +30,13 @@ const ( sdNodeCR = "production-7f3a9c" ) -func sdScope() cpinformer.Scope { return cpinformer.Scope{sdCluster, sdNodeID} } +func sdScope() cpinformer.Scope { + return cpinformer.Scope{sdCluster, sdNodeID} +} -func sdName() string { return simplyblockv1alpha1.StorageDeviceName(sdNodeCR, sdDevice) } +func sdName() string { + return simplyblockv1alpha1.StorageDeviceName(sdNodeCR, sdDevice) +} // fakeDeviceCache is a static DeviceCache for reconciler tests. type fakeDeviceCache struct { @@ -40,8 +44,12 @@ type fakeDeviceCache struct { devices map[string]subscriptions.DeviceDTO } -func (f *fakeDeviceCache) Triggers() <-chan event.GenericEvent { return nil } -func (f *fakeDeviceCache) Synced(cpinformer.Scope) bool { return f.synced } +func (f *fakeDeviceCache) Triggers() <-chan event.GenericEvent { + return nil +} +func (f *fakeDeviceCache) Synced(cpinformer.Scope) bool { + return f.synced +} func (f *fakeDeviceCache) Lookup(key types.NamespacedName) (cpinformer.Scope, subscriptions.DeviceDTO, bool) { dto, ok := f.devices[key.Name] if !ok { diff --git a/operator/internal/controller/volumemigration_helpers_test.go b/operator/internal/controller/volumemigration_helpers_test.go index c0a70af56..2f43c9001 100644 --- a/operator/internal/controller/volumemigration_helpers_test.go +++ b/operator/internal/controller/volumemigration_helpers_test.go @@ -782,6 +782,12 @@ func TestIsIndeterminateCreate(t *testing.T) { type timeoutError struct{} -func (timeoutError) Error() string { return "i/o timeout" } -func (timeoutError) Timeout() bool { return true } -func (timeoutError) Temporary() bool { return true } +func (timeoutError) Error() string { + return "i/o timeout" +} +func (timeoutError) Timeout() bool { + return true +} +func (timeoutError) Temporary() bool { + return true +} diff --git a/operator/internal/controllers/deployment/operatorops_unit_test.go b/operator/internal/controllers/deployment/operatorops_unit_test.go index fde17f24f..3abc4dfde 100644 --- a/operator/internal/controllers/deployment/operatorops_unit_test.go +++ b/operator/internal/controllers/deployment/operatorops_unit_test.go @@ -90,7 +90,9 @@ func worker(name string, options ...func(*corev1.Node)) *corev1.Node { return node } -func cordoned(node *corev1.Node) { node.Spec.Unschedulable = true } +func cordoned(node *corev1.Node) { + node.Spec.Unschedulable = true +} func tainted(node *corev1.Node) { node.Spec.Taints = []corev1.Taint{{ diff --git a/operator/internal/cpinformer/manager.go b/operator/internal/cpinformer/manager.go index e4a26e388..0739a10e7 100644 --- a/operator/internal/cpinformer/manager.go +++ b/operator/internal/cpinformer/manager.go @@ -159,7 +159,9 @@ func (m *SubscriptionManager) AddSubscription(sub Subscription) *ScopeSet { } // NeedLeaderElection implements manager.LeaderElectionRunnable. -func (m *SubscriptionManager) NeedLeaderElection() bool { return bool(m.election) } +func (m *SubscriptionManager) NeedLeaderElection() bool { + return bool(m.election) +} // Start runs every subscription until ctx is canceled, then tears the streams // down. It implements manager.Runnable. diff --git a/operator/internal/cpinformer/manager_test.go b/operator/internal/cpinformer/manager_test.go index f8e4e7631..c3e8b84e5 100644 --- a/operator/internal/cpinformer/manager_test.go +++ b/operator/internal/cpinformer/manager_test.go @@ -20,8 +20,12 @@ type stubSub struct { events []Event } -func (s *stubSub) Name() string { return "stub" } -func (s *stubSub) Path(Scope) string { return s.path } +func (s *stubSub) Name() string { + return "stub" +} +func (s *stubSub) Path(Scope) string { + return s.path +} func (s *stubSub) Ingest(_ context.Context, ev Event) error { s.mu.Lock() s.events = append(s.events, ev) diff --git a/operator/internal/cpinformer/subscriptions/cache.go b/operator/internal/cpinformer/subscriptions/cache.go index 97dcb183d..ce6a46894 100644 --- a/operator/internal/cpinformer/subscriptions/cache.go +++ b/operator/internal/cpinformer/subscriptions/cache.go @@ -118,10 +118,14 @@ func (c *Cache[DTO]) Find(id string) (cpinformer.Scope, DTO, bool) { } // List returns every resource cached for one scope. -func (c *Cache[DTO]) List(scope cpinformer.Scope) []DTO { return c.store.List(scope) } +func (c *Cache[DTO]) List(scope cpinformer.Scope) []DTO { + return c.store.List(scope) +} // All returns every cached resource across every scope. -func (c *Cache[DTO]) All() []DTO { return c.store.All() } +func (c *Cache[DTO]) All() []DTO { + return c.store.All() +} // Synced reports whether a scope has received its initial snapshot. Until it // has, an absent resource is an absence of information rather than evidence diff --git a/operator/internal/cpinformer/subscriptions/device.go b/operator/internal/cpinformer/subscriptions/device.go index b79b67582..4ce5b8d8f 100644 --- a/operator/internal/cpinformer/subscriptions/device.go +++ b/operator/internal/cpinformer/subscriptions/device.go @@ -93,7 +93,9 @@ func NewDeviceSubscription() *DeviceSubscription { } // Name implements cpinformer.Subscription. -func (s *DeviceSubscription) Name() string { return "device" } +func (s *DeviceSubscription) Name() string { + return "device" +} // Path implements cpinformer.Subscription: devices are scoped per (cluster, // storage node). The control plane offers no cluster-wide device stream, so one @@ -176,7 +178,9 @@ func (s *DeviceSubscription) enqueue(ctx context.Context, scope cpinformer.Scope // Triggers is the reconcile-trigger channel; the reconciler attaches it via // source.Channel. Each event names the StorageDevice object to reconcile. -func (s *DeviceSubscription) Triggers() <-chan event.GenericEvent { return s.ch } +func (s *DeviceSubscription) Triggers() <-chan event.GenericEvent { + return s.ch +} // Lookup returns the cached device that the named StorageDevice object mirrors, // with the scope it belongs to, or ok=false if the control plane no longer diff --git a/operator/internal/cpinformer/subscriptions/device_test.go b/operator/internal/cpinformer/subscriptions/device_test.go index 0853abba6..d8c38493e 100644 --- a/operator/internal/cpinformer/subscriptions/device_test.go +++ b/operator/internal/cpinformer/subscriptions/device_test.go @@ -23,7 +23,9 @@ const ( sdNodeCR = "production-7f3a9c" ) -func deviceScope() cpinformer.Scope { return cpinformer.Scope{sdCluster, sdNode} } +func deviceScope() cpinformer.Scope { + return cpinformer.Scope{sdCluster, sdNode} +} // sdNodeObject is the StorageNode the devices belong to. Its namespace is not // the operator's on purpose: a device object is created beside its node, so a diff --git a/operator/internal/cpinformer/subscriptions/node.go b/operator/internal/cpinformer/subscriptions/node.go index a25de0d35..b23a65eaa 100644 --- a/operator/internal/cpinformer/subscriptions/node.go +++ b/operator/internal/cpinformer/subscriptions/node.go @@ -74,7 +74,9 @@ func NewNodeSubscription() *NodeSubscription { } // Name implements cpinformer.Subscription. -func (s *NodeSubscription) Name() string { return "storagenode" } +func (s *NodeSubscription) Name() string { + return "storagenode" +} // Path implements cpinformer.Subscription: nodes are scoped per cluster. One // stream carries every node of the cluster, so the scope has a single element. @@ -117,7 +119,9 @@ func (s *NodeSubscription) enqueue(ctx context.Context, nodeID string) { // Triggers is the reconcile-trigger channel, which the reconciler attaches via // source.Channel. Each event names the StorageNode object to reconcile. -func (s *NodeSubscription) Triggers() <-chan event.GenericEvent { return s.ch } +func (s *NodeSubscription) Triggers() <-chan event.GenericEvent { + return s.ch +} // Lookup returns the cached node with the given backend id, or ok=false when // the control plane no longer reports it. It takes the id rather than an object diff --git a/operator/internal/cpinformer/subscriptions/node_test.go b/operator/internal/cpinformer/subscriptions/node_test.go index 3575cf762..b319eb1eb 100644 --- a/operator/internal/cpinformer/subscriptions/node_test.go +++ b/operator/internal/cpinformer/subscriptions/node_test.go @@ -27,7 +27,9 @@ const ( var snObject = types.NamespacedName{Namespace: "default", Name: "simplyblock-node-asxeub"} // nodeScope is a cluster on its own: one stream serves every node of it. -func nodeScope() cpinformer.Scope { return cpinformer.Scope{snCluster} } +func nodeScope() cpinformer.Scope { + return cpinformer.Scope{snCluster} +} func registeredNodes(t *testing.T) *NodeSubscription { t.Helper() diff --git a/operator/internal/cpinformer/subscriptions/volume.go b/operator/internal/cpinformer/subscriptions/volume.go index 0fe022fa1..8dded4b41 100644 --- a/operator/internal/cpinformer/subscriptions/volume.go +++ b/operator/internal/cpinformer/subscriptions/volume.go @@ -69,7 +69,9 @@ func NewVolumeSubscription() *VolumeSubscription { } // Name implements cpinformer.Subscription. -func (s *VolumeSubscription) Name() string { return "volume" } +func (s *VolumeSubscription) Name() string { + return "volume" +} // Path implements cpinformer.Subscription: volumes are scoped per (cluster, // pool), so one stream is opened per storage pool. diff --git a/operator/internal/cpinformer/subscriptions/volume_test.go b/operator/internal/cpinformer/subscriptions/volume_test.go index 41a0a9da6..08cd99234 100644 --- a/operator/internal/cpinformer/subscriptions/volume_test.go +++ b/operator/internal/cpinformer/subscriptions/volume_test.go @@ -21,7 +21,9 @@ const ( lvOther = "8d0000b2-4c3d-5e6f-a012-3b4c5d6e7f80" ) -func poolScope(pool string) cpinformer.Scope { return cpinformer.Scope{lvCluster, pool} } +func poolScope(pool string) cpinformer.Scope { + return cpinformer.Scope{lvCluster, pool} +} func ingestVolume(t *testing.T, sub *VolumeSubscription, scope cpinformer.Scope, kind, data string) { t.Helper() diff --git a/operator/internal/csilink/hub.go b/operator/internal/csilink/hub.go index 606b76e18..889d1d3a9 100644 --- a/operator/internal/csilink/hub.go +++ b/operator/internal/csilink/hub.go @@ -130,7 +130,9 @@ type hubRunnable struct{ hub *link.Hub } // NeedLeaderElection is false: the listener has to exist wherever the Service // may route, and leadership is enforced by the hub's Accepting gate instead. -func (r *hubRunnable) NeedLeaderElection() bool { return false } +func (r *hubRunnable) NeedLeaderElection() bool { + return false +} func (r *hubRunnable) Start(ctx context.Context) error { log := logf.FromContext(ctx).WithName("csi-link") diff --git a/operator/internal/discovery/grouping.go b/operator/internal/discovery/grouping.go index 5952e8768..9014f47ea 100644 --- a/operator/internal/discovery/grouping.go +++ b/operator/internal/discovery/grouping.go @@ -112,7 +112,9 @@ type Grouper interface { // — and it is the guess a reviewer regroups. type GroupByHardware struct{} -func (GroupByHardware) Name() string { return "identical hardware" } +func (GroupByHardware) Name() string { + return "identical hardware" +} func (GroupByHardware) Group(workers []Worker) []Group { bySignature := map[string]*Group{} @@ -206,7 +208,9 @@ type SingleNodeSet struct { // the racks can see there is nothing to preserve in renaming it. const DefaultNodeSetName = "discovered" -func (SingleNodeSet) Name() string { return "single node set" } +func (SingleNodeSet) Name() string { + return "single node set" +} func (b SingleNodeSet) Build(groups []Group) []simplyblockv1alpha2.NodeSet { if len(groups) == 0 { diff --git a/operator/internal/discovery/placement.go b/operator/internal/discovery/placement.go index 356ee70da..4d32a53b5 100644 --- a/operator/internal/discovery/placement.go +++ b/operator/internal/discovery/placement.go @@ -81,7 +81,9 @@ type NUMANodeResources struct { // worker choose the same node. type MostAvailableNUMANode struct{} -func (MostAvailableNUMANode) Name() string { return "most available NUMA node" } +func (MostAvailableNUMANode) Name() string { + return "most available NUMA node" +} func (p MostAvailableNUMANode) Choose( report nodeprobe.Report, @@ -204,7 +206,9 @@ func describeNode(id int) string { // one input. type AllDevices struct{} -func (AllDevices) Name() string { return "all devices" } +func (AllDevices) Name() string { + return "all devices" +} func (AllDevices) Choose(_ nodeprobe.Report, admitted []nodeprobe.Device) ([]nodeprobe.Device, string) { return admitted, "every unclaimed device was used, without regard to its memory node" diff --git a/operator/internal/discovery/rules.go b/operator/internal/discovery/rules.go index 8b527a1cc..71eaefe85 100644 --- a/operator/internal/discovery/rules.go +++ b/operator/internal/discovery/rules.go @@ -121,7 +121,9 @@ type AvailableRule struct { AllowPartitioned bool } -func (AvailableRule) Name() string { return "available" } +func (AvailableRule) Name() string { + return "available" +} func (r AvailableRule) Admit(_ nodeprobe.Report, device nodeprobe.Device) (bool, string) { if device.Available { @@ -151,7 +153,9 @@ type ClassRule struct { Class DeviceClass } -func (ClassRule) Name() string { return "device class" } +func (ClassRule) Name() string { + return "device class" +} func (r ClassRule) Admit(_ nodeprobe.Report, device nodeprobe.Device) (bool, string) { if r.Class == ClassNVMe && device.Transport != string(blockdev.TransportNVMe) { @@ -187,7 +191,9 @@ func addressKind(class DeviceClass) string { // waiver would be handed to a cluster as though it were a disk. type WholeDiskRule struct{} -func (WholeDiskRule) Name() string { return "whole disk" } +func (WholeDiskRule) Name() string { + return "whole disk" +} func (WholeDiskRule) Admit(_ nodeprobe.Report, device nodeprobe.Device) (bool, string) { if device.Kind != string(blockdev.KindDisk) { @@ -207,7 +213,9 @@ type AllowDenyRule struct { Deny []string } -func (AllowDenyRule) Name() string { return "allow and deny lists" } +func (AllowDenyRule) Name() string { + return "allow and deny lists" +} func (r AllowDenyRule) Admit(_ nodeprobe.Report, device nodeprobe.Device) (bool, string) { address := r.Class.Address(device) @@ -237,7 +245,9 @@ type ModelRule struct { Model string } -func (ModelRule) Name() string { return "model" } +func (ModelRule) Name() string { + return "model" +} func (r ModelRule) Admit(_ nodeprobe.Report, device nodeprobe.Device) (bool, string) { if r.Model == "" { @@ -255,7 +265,9 @@ type SizeRule struct { Min, Max uint64 } -func (SizeRule) Name() string { return "size range" } +func (SizeRule) Name() string { + return "size range" +} func (r SizeRule) Admit(_ nodeprobe.Report, device nodeprobe.Device) (bool, string) { if device.SizeBytes < r.Min { @@ -276,7 +288,9 @@ func (r SizeRule) Admit(_ nodeprobe.Report, device nodeprobe.Device) (bool, stri // store on. type WorkerHasDevices struct{} -func (WorkerHasDevices) Name() string { return "has devices" } +func (WorkerHasDevices) Name() string { + return "has devices" +} func (WorkerHasDevices) Admit(report nodeprobe.Report, admitted []nodeprobe.Device) (bool, string) { if len(admitted) > 0 { @@ -314,7 +328,9 @@ func describeControllers(controllers []nodeprobe.Controller) string { // a fleet that wants only fully readable machines in its draft can say so. type WorkerWasReadable struct{} -func (WorkerWasReadable) Name() string { return "fully readable" } +func (WorkerWasReadable) Name() string { + return "fully readable" +} func (WorkerWasReadable) Admit(report nodeprobe.Report, _ []nodeprobe.Device) (bool, string) { if len(report.Unreadable) == 0 { diff --git a/operator/internal/metricsapi/server.go b/operator/internal/metricsapi/server.go index 7a8e7f5c2..d725214db 100644 --- a/operator/internal/metricsapi/server.go +++ b/operator/internal/metricsapi/server.go @@ -161,7 +161,9 @@ func NewServer( // NeedLeaderElection implements manager.LeaderElectionRunnable: every replica // serves, because every replica may be the one the Service routes to. -func (s *Server) NeedLeaderElection() bool { return false } +func (s *Server) NeedLeaderElection() bool { + return false +} // Start implements manager.Runnable. It blocks until ctx is canceled, then // drains and shuts the listener down. diff --git a/operator/internal/metricsapi/storage.go b/operator/internal/metricsapi/storage.go index ccc30afcd..cb54ae726 100644 --- a/operator/internal/metricsapi/storage.go +++ b/operator/internal/metricsapi/storage.go @@ -89,7 +89,9 @@ func NewStorage(volumes VolumeSource, reader client.Reader, capacity CapacitySou } // New implements rest.Storage. -func (s *Storage) New() runtime.Object { return &metricsv1alpha1.LogicalVolumeMetrics{} } +func (s *Storage) New() runtime.Object { + return &metricsv1alpha1.LogicalVolumeMetrics{} +} // Destroy implements rest.Storage. There is nothing to release: no client, no // watch, and no connection is owned here. @@ -97,16 +99,24 @@ func (s *Storage) Destroy() {} // NamespaceScoped implements rest.Scoper. The resource is namespaced because // that is what confines a tenant to their own volumes through ordinary RBAC. -func (s *Storage) NamespaceScoped() bool { return true } +func (s *Storage) NamespaceScoped() bool { + return true +} // GetSingularName implements rest.SingularNameProvider. -func (s *Storage) GetSingularName() string { return ResourceName } +func (s *Storage) GetSingularName() string { + return ResourceName +} // ShortNames implements rest.ShortNamesProvider. -func (s *Storage) ShortNames() []string { return []string{ShortName} } +func (s *Storage) ShortNames() []string { + return []string{ShortName} +} // NewList implements rest.Lister. -func (s *Storage) NewList() runtime.Object { return &metricsv1alpha1.LogicalVolumeMetricsList{} } +func (s *Storage) NewList() runtime.Object { + return &metricsv1alpha1.LogicalVolumeMetricsList{} +} // Get implements rest.Getter. The name is a PersistentVolumeClaim's, so the // lookup runs claim to volume and never scans the cache. diff --git a/operator/internal/metricsapi/storage_test.go b/operator/internal/metricsapi/storage_test.go index acc815b6d..0ced84fc9 100644 --- a/operator/internal/metricsapi/storage_test.go +++ b/operator/internal/metricsapi/storage_test.go @@ -47,7 +47,9 @@ const ( // subscription's live cache. type fakeVolumes struct{ items []subscriptions.VolumeDTO } -func (f fakeVolumes) All() []subscriptions.VolumeDTO { return f.items } +func (f fakeVolumes) All() []subscriptions.VolumeDTO { + return f.items +} func (f fakeVolumes) Get(id string) (subscriptions.VolumeDTO, bool) { for _, v := range f.items { diff --git a/operator/internal/nodeprobe/report.go b/operator/internal/nodeprobe/report.go index e11cbda30..926bc8ddf 100644 --- a/operator/internal/nodeprobe/report.go +++ b/operator/internal/nodeprobe/report.go @@ -176,7 +176,9 @@ type HugePagePool struct { } // AllocatedBytes is how much memory this pool holds. -func (p HugePagePool) AllocatedBytes() uint64 { return p.Total * p.SizeBytes } +func (p HugePagePool) AllocatedBytes() uint64 { + return p.Total * p.SizeBytes +} // NUMAHugePages is one memory node's share of one pool. type NUMAHugePages struct { diff --git a/operator/internal/upgrade/blocked_test.go b/operator/internal/upgrade/blocked_test.go index 3412ff639..87851e3a2 100644 --- a/operator/internal/upgrade/blocked_test.go +++ b/operator/internal/upgrade/blocked_test.go @@ -22,9 +22,15 @@ type describedOnly struct { applied int } -func (d *describedOnly) Stage() Stage { return StageUpgrade } -func (d *describedOnly) Phase() Phase { return "" } -func (d *describedOnly) BlockedBy() string { return d.reason } +func (d *describedOnly) Stage() Stage { + return StageUpgrade +} +func (d *describedOnly) Phase() Phase { + return "" +} +func (d *describedOnly) BlockedBy() string { + return d.reason +} func (d *describedOnly) Describe(_ context.Context, _ *Scope, subject Subject) (*Action, error) { if !subject.IsUpgrade() { @@ -33,9 +39,15 @@ func (d *describedOnly) Describe(_ context.Context, _ *Scope, subject Subject) ( return &Action{Rule: d.id, Verb: VerbCreate, Object: subject.Ref, Detail: "something"}, nil } -func (d *describedOnly) Done(context.Context, *Scope, Subject) (bool, error) { return false, nil } -func (d *describedOnly) Validate(context.Context, *Scope, Subject) error { return nil } -func (d *describedOnly) Verify(context.Context, *Scope, Subject) error { return nil } +func (d *describedOnly) Done(context.Context, *Scope, Subject) (bool, error) { + return false, nil +} +func (d *describedOnly) Validate(context.Context, *Scope, Subject) error { + return nil +} +func (d *describedOnly) Verify(context.Context, *Scope, Subject) error { + return nil +} func (d *describedOnly) Apply(context.Context, *Scope, Subject) error { d.applied++ diff --git a/operator/internal/upgrade/check.go b/operator/internal/upgrade/check.go index fd8a16273..04b6ffec2 100644 --- a/operator/internal/upgrade/check.go +++ b/operator/internal/upgrade/check.go @@ -39,9 +39,15 @@ type CheckFunc struct { Fn func(ctx context.Context, s *Scope) (Findings, error) } -func (c CheckFunc) ID() ID { return c.RuleID } -func (c CheckFunc) Description() string { return c.Summary } -func (c CheckFunc) Stages() []Stage { return c.RunIn } +func (c CheckFunc) ID() ID { + return c.RuleID +} +func (c CheckFunc) Description() string { + return c.Summary +} +func (c CheckFunc) Stages() []Stage { + return c.RunIn +} func (c CheckFunc) Check(ctx context.Context, s *Scope) (Findings, error) { return c.Fn(ctx, s) diff --git a/operator/internal/upgrade/crds/crds.go b/operator/internal/upgrade/crds/crds.go index 2d96d9823..c9e0e3629 100644 --- a/operator/internal/upgrade/crds/crds.go +++ b/operator/internal/upgrade/crds/crds.go @@ -63,13 +63,19 @@ type Definition struct { } // Name is the CRD's name, which is also its identity in the cluster. -func (d Definition) Name() string { return d.Object.Name } +func (d Definition) Name() string { + return d.Object.Name +} // Kind is the kind the CRD serves. -func (d Definition) Kind() string { return d.Object.Spec.Names.Kind } +func (d Definition) Kind() string { + return d.Object.Spec.Names.Kind +} // Group is the API group the CRD belongs to. -func (d Definition) Group() string { return d.Object.Spec.Group } +func (d Definition) Group() string { + return d.Object.Spec.Group +} // Shape reports which of §11's groups this CRD is in. // diff --git a/operator/internal/upgrade/derive/rule.go b/operator/internal/upgrade/derive/rule.go index 4aa0ccf5b..71944fcc5 100644 --- a/operator/internal/upgrade/derive/rule.go +++ b/operator/internal/upgrade/derive/rule.go @@ -50,14 +50,28 @@ type Rule struct { Enumerate func(ctx context.Context, s *upgrade.Scope) ([]upgrade.Input, error) } -func (r Rule) ID() upgrade.ID { return r.RuleID } -func (r Rule) Description() string { return r.Summary } -func (r Rule) Written() string { return r.Where } -func (r Rule) Model() upgrade.Model { return r.Which } -func (r Rule) Formula() atlaskube.Formula { return r.Build } -func (r Rule) Fix() upgrade.Fix { return r.Resolution } - -func (r Rule) Space() upgrade.Space { return r.Unique } +func (r Rule) ID() upgrade.ID { + return r.RuleID +} +func (r Rule) Description() string { + return r.Summary +} +func (r Rule) Written() string { + return r.Where +} +func (r Rule) Model() upgrade.Model { + return r.Which +} +func (r Rule) Formula() atlaskube.Formula { + return r.Build +} +func (r Rule) Fix() upgrade.Fix { + return r.Resolution +} + +func (r Rule) Space() upgrade.Space { + return r.Unique +} func (r Rule) Inputs(ctx context.Context, s *upgrade.Scope) ([]upgrade.Input, error) { if r.Enumerate == nil { diff --git a/operator/internal/upgrade/discover/kind.go b/operator/internal/upgrade/discover/kind.go index b49f71de1..068abf561 100644 --- a/operator/internal/upgrade/discover/kind.go +++ b/operator/internal/upgrade/discover/kind.go @@ -66,9 +66,15 @@ const ( Occupied ) -func (k Kind) ID() upgrade.ID { return k.RuleID } -func (k Kind) Description() string { return k.Summary } -func (k Kind) Requires() []upgrade.ID { return k.Needs } +func (k Kind) ID() upgrade.ID { + return k.RuleID +} +func (k Kind) Description() string { + return k.Summary +} +func (k Kind) Requires() []upgrade.ID { + return k.Needs +} // describe says what is about to be read, in the form a report prints while it // waits: the kind, and the namespace it is narrowed to. diff --git a/operator/internal/upgrade/discover/release.go b/operator/internal/upgrade/discover/release.go index 1dcb4ef1c..4f711e765 100644 --- a/operator/internal/upgrade/discover/release.go +++ b/operator/internal/upgrade/discover/release.go @@ -36,8 +36,12 @@ func HelmRelease() []upgrade.Discoverer { type helmRelease struct{} -func (helmRelease) ID() upgrade.ID { return IDHelmRelease } -func (helmRelease) Requires() []upgrade.ID { return nil } +func (helmRelease) ID() upgrade.ID { + return IDHelmRelease +} +func (helmRelease) Requires() []upgrade.ID { + return nil +} func (helmRelease) Description() string { return "reads the objects the deployed Helm release installed, which §12 hands over before the upgrade prunes them" diff --git a/operator/internal/upgrade/finding.go b/operator/internal/upgrade/finding.go index 9418a4234..6eafe3439 100644 --- a/operator/internal/upgrade/finding.go +++ b/operator/internal/upgrade/finding.go @@ -66,7 +66,9 @@ type Finding struct { } // Error reports whether the finding blocks. -func (f Finding) Error() bool { return f.Severity == SeverityError } +func (f Finding) Error() bool { + return f.Severity == SeverityError +} // String renders the finding as the preflight prints it. func (f Finding) String() string { @@ -103,7 +105,9 @@ func (f Findings) Errors() Findings { } // Blocked reports whether anything in the set stops the run. -func (f Findings) Blocked() bool { return len(f.Errors()) > 0 } +func (f Findings) Blocked() bool { + return len(f.Errors()) > 0 +} // String renders every finding, one after another. It is what a report prints // and what a test compares two runs with. diff --git a/operator/internal/upgrade/helm/config.go b/operator/internal/upgrade/helm/config.go index 4538e2559..ea54ea48f 100644 --- a/operator/internal/upgrade/helm/config.go +++ b/operator/internal/upgrade/helm/config.go @@ -50,7 +50,9 @@ func New(config *rest.Config, namespace string, log func(string, ...any)) (*Clie } // Namespace is where this client's releases live. -func (c *Client) Namespace() string { return c.namespace } +func (c *Client) Namespace() string { + return c.namespace +} // getter answers Helm's questions about which cluster to talk to, with the // connection it was handed. @@ -65,7 +67,9 @@ type getter struct { } // ToRESTConfig returns the connection this was built over. -func (g *getter) ToRESTConfig() (*rest.Config, error) { return g.config, nil } +func (g *getter) ToRESTConfig() (*rest.Config, error) { + return g.config, nil +} // ToDiscoveryClient returns a discovery client that caches in memory. Helm asks // what the API server serves once per action and again per object, and an diff --git a/operator/internal/upgrade/keys/keys.go b/operator/internal/upgrade/keys/keys.go index fd48804aa..e77ad1482 100644 --- a/operator/internal/upgrade/keys/keys.go +++ b/operator/internal/upgrade/keys/keys.go @@ -51,10 +51,14 @@ type Key struct { } // Old is the spelling that shipped. -func (k Key) Old() string { return OldPrefix + k.Name } +func (k Key) Old() string { + return OldPrefix + k.Name +} // New is the spelling the target model uses. -func (k Key) New() string { return NewPrefix + k.Name } +func (k Key) New() string { + return NewPrefix + k.Name +} // LegacyOld is the pre-OldPrefix spelling, and the empty string for a key that // never had one. diff --git a/operator/internal/upgrade/phase.go b/operator/internal/upgrade/phase.go index 8e7bd619c..22ab68ae9 100644 --- a/operator/internal/upgrade/phase.go +++ b/operator/internal/upgrade/phase.go @@ -141,4 +141,6 @@ func (p Phase) Describe() string { } // Terminal reports whether a phase ends the walk. -func (p Phase) Terminal() bool { return p == PhaseCompleted || p == PhaseFailed } +func (p Phase) Terminal() bool { + return p == PhaseCompleted || p == PhaseFailed +} diff --git a/operator/internal/upgrade/plan.go b/operator/internal/upgrade/plan.go index 7bf1180e0..5af5bfa16 100644 --- a/operator/internal/upgrade/plan.go +++ b/operator/internal/upgrade/plan.go @@ -200,10 +200,14 @@ func (p *Plan) InPhase(phase Phase) []Task { } // Record appends findings. -func (p *Plan) Record(findings ...Finding) { p.Findings = append(p.Findings, findings...) } +func (p *Plan) Record(findings ...Finding) { + p.Findings = append(p.Findings, findings...) +} // Blocked reports whether the plan's checks refuse the stage. -func (p *Plan) Blocked() bool { return p.Findings.Blocked() } +func (p *Plan) Blocked() bool { + return p.Findings.Blocked() +} // Unimplemented reports the tasks this build describes and cannot perform, // which is what stops a stage before it starts. diff --git a/operator/internal/upgrade/position.go b/operator/internal/upgrade/position.go index 3c1cf8ed9..c63d09dd3 100644 --- a/operator/internal/upgrade/position.go +++ b/operator/internal/upgrade/position.go @@ -37,7 +37,9 @@ type ConvertingKind struct { } // CRDName is the CustomResourceDefinition that defines this kind. -func (c ConvertingKind) CRDName() string { return c.Plural + "." + APIGroup } +func (c ConvertingKind) CRDName() string { + return c.Plural + "." + APIGroup +} // ConvertingKinds are the seven of §7.2. Ten of the seventeen registered kinds // are absent: five keep one version and are then removed, and five are outside @@ -77,7 +79,9 @@ type Position struct { // Partial reports a CRD set that was applied to some kinds and not others, // which leaves the operator reconciling one kind at each version. -func (p Position) Partial() bool { return len(p.Converted) > 0 && len(p.Pending) > 0 } +func (p Position) Partial() bool { + return len(p.Converted) > 0 && len(p.Pending) > 0 +} // Positioned reads the cluster and reports which command it is ready for. // diff --git a/operator/internal/upgrade/readonly.go b/operator/internal/upgrade/readonly.go index e1c754e7b..a93611d7b 100644 --- a/operator/internal/upgrade/readonly.go +++ b/operator/internal/upgrade/readonly.go @@ -27,7 +27,9 @@ type ReadOnlyClient struct { } // NewReadOnlyClient wraps a client so that nothing it is handed to can write. -func NewReadOnlyClient(c client.Client) client.Client { return ReadOnlyClient{Client: c} } +func NewReadOnlyClient(c client.Client) client.Client { + return ReadOnlyClient{Client: c} +} func (r ReadOnlyClient) Create(context.Context, client.Object, ...client.CreateOption) error { return fmt.Errorf("create: %w", ErrReadOnly) diff --git a/operator/internal/upgrade/registry_test.go b/operator/internal/upgrade/registry_test.go index f4647e808..819e927ef 100644 --- a/operator/internal/upgrade/registry_test.go +++ b/operator/internal/upgrade/registry_test.go @@ -17,9 +17,15 @@ type namedRule struct { requires []ID } -func (r namedRule) ID() ID { return r.id } -func (r namedRule) Description() string { return r.summary } -func (r namedRule) Requires() []ID { return r.requires } +func (r namedRule) ID() ID { + return r.id +} +func (r namedRule) Description() string { + return r.summary +} +func (r namedRule) Requires() []ID { + return r.requires +} func rule(id ID, requires ...ID) namedRule { return namedRule{id: id, summary: "a rule called " + string(id), requires: requires} diff --git a/operator/internal/upgrade/release/release.go b/operator/internal/upgrade/release/release.go index a1a8ad365..40cc89270 100644 --- a/operator/internal/upgrade/release/release.go +++ b/operator/internal/upgrade/release/release.go @@ -76,7 +76,9 @@ type ObjectRef struct { } // Kind is the object's kind, which is what a reader groups by. -func (r ObjectRef) Kind() string { return r.GVK.Kind } +func (r ObjectRef) Kind() string { + return r.GVK.Kind +} // String renders the reference the way a plan names it. func (r ObjectRef) String() string { diff --git a/operator/internal/upgrade/report.go b/operator/internal/upgrade/report.go index cce62d787..bd656e90c 100644 --- a/operator/internal/upgrade/report.go +++ b/operator/internal/upgrade/report.go @@ -116,7 +116,9 @@ func (DiscardReporter) Findings(Findings) {} func (DiscardReporter) Action(Action) {} func (DiscardReporter) Plan(Plan) {} func (DiscardReporter) Progress(string, ...any) {} -func (DiscardReporter) Close() error { return nil } +func (DiscardReporter) Close() error { + return nil +} // TextReporter writes the report a person reads in a terminal that is not one, // which is a log file, a CI job, and the Job this tool runs as in a cluster. It @@ -140,7 +142,9 @@ type TextReporter struct { } // NewTextReporter builds a reporter over a writer. -func NewTextReporter(out io.Writer) *TextReporter { return &TextReporter{Out: out} } +func NewTextReporter(out io.Writer) *TextReporter { + return &TextReporter{Out: out} +} func (r *TextReporter) Stage(stage Stage) { r.line("") @@ -294,7 +298,9 @@ func (r *TextReporter) Progress(format string, args ...any) { } // Close writes nothing. A text reporter holds no terminal. -func (r *TextReporter) Close() error { return nil } +func (r *TextReporter) Close() error { + return nil +} // line writes one line under the reporter's lock, so two goroutines cannot // interleave halves of a finding. diff --git a/operator/internal/upgrade/rule.go b/operator/internal/upgrade/rule.go index 5e0903c5a..232cc18b8 100644 --- a/operator/internal/upgrade/rule.go +++ b/operator/internal/upgrade/rule.go @@ -134,7 +134,9 @@ func (r *Registry[T]) Get(id ID) (T, bool) { } // Len reports how many rules the registry holds. -func (r *Registry[T]) Len() int { return len(r.order) } +func (r *Registry[T]) Len() int { + return len(r.order) +} // Select returns the rules the predicate accepts, in registration order. It is // how a runner narrows a catalog to one stage, and how the command line's skip diff --git a/operator/internal/upgrade/runner_test.go b/operator/internal/upgrade/runner_test.go index acba3206a..0332d7e29 100644 --- a/operator/internal/upgrade/runner_test.go +++ b/operator/internal/upgrade/runner_test.go @@ -46,8 +46,12 @@ type recordingStep struct { verified []string } -func (s *recordingStep) Stage() Stage { return s.stage } -func (s *recordingStep) Phase() Phase { return s.phase } +func (s *recordingStep) Stage() Stage { + return s.stage +} +func (s *recordingStep) Phase() Phase { + return s.phase +} func (s *recordingStep) Describe(_ context.Context, _ *Scope, subject Subject) (*Action, error) { if !s.responsible(subject) || s.settled(subject) { diff --git a/operator/internal/upgrade/steps/crds.go b/operator/internal/upgrade/steps/crds.go index c213071c3..9ab2295da 100644 --- a/operator/internal/upgrade/steps/crds.go +++ b/operator/internal/upgrade/steps/crds.go @@ -37,10 +37,18 @@ func crdGVK() schema.GroupVersionKind { // applyCRDs writes the embedded CRDs, one subject per CRD. type applyCRDs struct{} -func (applyCRDs) ID() upgrade.ID { return IDApplyCRDs } -func (applyCRDs) Stage() upgrade.Stage { return upgrade.StageUpgrade } -func (applyCRDs) Phase() upgrade.Phase { return "" } -func (applyCRDs) Requires() []upgrade.ID { return []upgrade.ID{IDSmokeTestWebhook} } +func (applyCRDs) ID() upgrade.ID { + return IDApplyCRDs +} +func (applyCRDs) Stage() upgrade.Stage { + return upgrade.StageUpgrade +} +func (applyCRDs) Phase() upgrade.Phase { + return "" +} +func (applyCRDs) Requires() []upgrade.ID { + return []upgrade.ID{IDSmokeTestWebhook} +} func (applyCRDs) Description() string { return "applies the CRDs this binary carries, so the schemas installed are the ones its conversion code was built against" } @@ -359,8 +367,12 @@ const establishTimeout = time.Minute // what refuses to go on when any of them did not take. type verifyCRDVersions struct{ applyCRDs } -func (verifyCRDVersions) ID() upgrade.ID { return IDVerifyCRDVersions } -func (verifyCRDVersions) Requires() []upgrade.ID { return []upgrade.ID{IDApplyCRDs} } +func (verifyCRDVersions) ID() upgrade.ID { + return IDVerifyCRDVersions +} +func (verifyCRDVersions) Requires() []upgrade.ID { + return []upgrade.ID{IDApplyCRDs} +} func (verifyCRDVersions) Description() string { return "waits for every applied CRD and checks the API server took all of them, since a partly applied set is the worst outcome" diff --git a/operator/internal/upgrade/steps/described.go b/operator/internal/upgrade/steps/described.go index 4e7ecff0f..9efe3a918 100644 --- a/operator/internal/upgrade/steps/described.go +++ b/operator/internal/upgrade/steps/described.go @@ -29,13 +29,19 @@ type described struct { } // BlockedBy is what the runner reads to refuse the stage. -func (d described) BlockedBy() string { return d.blocked } +func (d described) BlockedBy() string { + return d.blocked +} func (d described) Validate(context.Context, *upgrade.Scope, upgrade.Subject) error { return d.refuse() } -func (d described) Apply(context.Context, *upgrade.Scope, upgrade.Subject) error { return d.refuse() } -func (d described) Verify(context.Context, *upgrade.Scope, upgrade.Subject) error { return d.refuse() } +func (d described) Apply(context.Context, *upgrade.Scope, upgrade.Subject) error { + return d.refuse() +} +func (d described) Verify(context.Context, *upgrade.Scope, upgrade.Subject) error { + return d.refuse() +} // refuse is what these return if they are ever reached, which would mean the // runner's refusal had been bypassed. diff --git a/operator/internal/upgrade/steps/migrate.go b/operator/internal/upgrade/steps/migrate.go index e7af9d6ba..ff1e08e01 100644 --- a/operator/internal/upgrade/steps/migrate.go +++ b/operator/internal/upgrade/steps/migrate.go @@ -82,10 +82,18 @@ func Migrate() []upgrade.Step { // before the release that stops. type rewriteKeys struct{} -func (rewriteKeys) ID() upgrade.ID { return IDRewriteKeys } -func (rewriteKeys) Stage() upgrade.Stage { return upgrade.StageMigrate } -func (rewriteKeys) Phase() upgrade.Phase { return upgrade.PhaseTransforming } -func (rewriteKeys) Requires() []upgrade.ID { return nil } +func (rewriteKeys) ID() upgrade.ID { + return IDRewriteKeys +} +func (rewriteKeys) Stage() upgrade.Stage { + return upgrade.StageMigrate +} +func (rewriteKeys) Phase() upgrade.Phase { + return upgrade.PhaseTransforming +} +func (rewriteKeys) Requires() []upgrade.ID { + return nil +} func (rewriteKeys) Description() string { return "writes the storage.simplyblock.io spelling of every key an object still carries under the old prefix" @@ -115,7 +123,9 @@ func (r rewriteKeys) Done(_ context.Context, _ *upgrade.Scope, subject upgrade.S // Validate has nothing to refuse. The write adds a key beside one that is // already there, so there is no state it can arrive in that makes it unsafe. -func (rewriteKeys) Validate(context.Context, *upgrade.Scope, upgrade.Subject) error { return nil } +func (rewriteKeys) Validate(context.Context, *upgrade.Scope, upgrade.Subject) error { + return nil +} // Apply writes the new keys, preserving each value verbatim. func (r rewriteKeys) Apply(ctx context.Context, s *upgrade.Scope, subject upgrade.Subject) error { @@ -225,10 +235,18 @@ func withKey(values map[string]string, key, value string) map[string]string { // the target model keeps. type deleteBackupImports struct{} -func (deleteBackupImports) ID() upgrade.ID { return IDDeleteBackupImport } -func (deleteBackupImports) Stage() upgrade.Stage { return upgrade.StageMigrate } -func (deleteBackupImports) Phase() upgrade.Phase { return upgrade.PhaseDeleting } -func (deleteBackupImports) Requires() []upgrade.ID { return nil } +func (deleteBackupImports) ID() upgrade.ID { + return IDDeleteBackupImport +} +func (deleteBackupImports) Stage() upgrade.Stage { + return upgrade.StageMigrate +} +func (deleteBackupImports) Phase() upgrade.Phase { + return upgrade.PhaseDeleting +} +func (deleteBackupImports) Requires() []upgrade.ID { + return nil +} func (deleteBackupImports) Description() string { return "deletes each BackupImport, which §16.2 retires because the store is the inventory" @@ -289,11 +307,21 @@ type renameKind struct { as string } -func (r renameKind) ID() upgrade.ID { return r.id } -func (r renameKind) Description() string { return r.summary } -func (renameKind) Stage() upgrade.Stage { return upgrade.StageMigrate } -func (renameKind) Phase() upgrade.Phase { return upgrade.PhaseTransforming } -func (renameKind) Requires() []upgrade.ID { return nil } +func (r renameKind) ID() upgrade.ID { + return r.id +} +func (r renameKind) Description() string { + return r.summary +} +func (renameKind) Stage() upgrade.Stage { + return upgrade.StageMigrate +} +func (renameKind) Phase() upgrade.Phase { + return upgrade.PhaseTransforming +} +func (renameKind) Requires() []upgrade.ID { + return nil +} // Describe names the object that would be created, which needs the source and // not the target type. @@ -332,10 +360,18 @@ type normalizeHandles struct { described } -func (n normalizeHandles) ID() upgrade.ID { return n.id } -func (normalizeHandles) Stage() upgrade.Stage { return upgrade.StageMigrate } -func (normalizeHandles) Phase() upgrade.Phase { return upgrade.PhaseHandles } -func (normalizeHandles) Requires() []upgrade.ID { return nil } +func (n normalizeHandles) ID() upgrade.ID { + return n.id +} +func (normalizeHandles) Stage() upgrade.Stage { + return upgrade.StageMigrate +} +func (normalizeHandles) Phase() upgrade.Phase { + return upgrade.PhaseHandles +} +func (normalizeHandles) Requires() []upgrade.ID { + return nil +} func (normalizeHandles) Description() string { return "records the normalized handle on each volume whose pool segment carries a name rather than a UUID" diff --git a/operator/internal/upgrade/steps/ownership.go b/operator/internal/upgrade/steps/ownership.go index 3c89c78e0..030bb04a3 100644 --- a/operator/internal/upgrade/steps/ownership.go +++ b/operator/internal/upgrade/steps/ownership.go @@ -176,11 +176,21 @@ type reparentStep struct { needs []upgrade.ID } -func (r reparentStep) ID() upgrade.ID { return r.id } -func (r reparentStep) Description() string { return r.summary } -func (r reparentStep) Stage() upgrade.Stage { return upgrade.StageMigrate } -func (r reparentStep) Phase() upgrade.Phase { return upgrade.PhaseOwnership } -func (r reparentStep) Requires() []upgrade.ID { return r.needs } +func (r reparentStep) ID() upgrade.ID { + return r.id +} +func (r reparentStep) Description() string { + return r.summary +} +func (r reparentStep) Stage() upgrade.Stage { + return upgrade.StageMigrate +} +func (r reparentStep) Phase() upgrade.Phase { + return upgrade.PhaseOwnership +} +func (r reparentStep) Requires() []upgrade.ID { + return r.needs +} // Describe reports the move, and nothing for a subject already owned by its // cluster. That is not a change to skip: it is a change that does not exist, so @@ -274,15 +284,23 @@ type retireStep struct { needs []upgrade.ID } -func (retireStep) ID() upgrade.ID { return IDRetireNodeSets } -func (retireStep) Stage() upgrade.Stage { return upgrade.StageMigrate } -func (retireStep) Phase() upgrade.Phase { return upgrade.PhaseOwnership } +func (retireStep) ID() upgrade.ID { + return IDRetireNodeSets +} +func (retireStep) Stage() upgrade.Stage { + return upgrade.StageMigrate +} +func (retireStep) Phase() upgrade.Phase { + return upgrade.PhaseOwnership +} func (retireStep) Description() string { return "deletes a StorageNodeSet once everything it held has been reparented" } -func (r retireStep) Requires() []upgrade.ID { return r.needs } +func (r retireStep) Requires() []upgrade.ID { + return r.needs +} // Describe reports the deletion for a set the graph still holds. func (r retireStep) Describe(_ context.Context, s *upgrade.Scope, subject upgrade.Subject) (*upgrade.Action, error) { diff --git a/operator/internal/upgrade/steps/upgrade.go b/operator/internal/upgrade/steps/upgrade.go index 7dd3c4901..ce8a471ba 100644 --- a/operator/internal/upgrade/steps/upgrade.go +++ b/operator/internal/upgrade/steps/upgrade.go @@ -125,12 +125,24 @@ type planned struct { needs []upgrade.ID } -func (p planned) ID() upgrade.ID { return p.id } -func (p planned) Description() string { return p.summary } -func (p planned) Stage() upgrade.Stage { return upgrade.StageUpgrade } -func (p planned) Phase() upgrade.Phase { return "" } -func (p planned) Requires() []upgrade.ID { return p.needs } -func (p planned) BlockedBy() string { return p.blocked } +func (p planned) ID() upgrade.ID { + return p.id +} +func (p planned) Description() string { + return p.summary +} +func (p planned) Stage() upgrade.Stage { + return upgrade.StageUpgrade +} +func (p planned) Phase() upgrade.Phase { + return "" +} +func (p planned) Requires() []upgrade.ID { + return p.needs +} +func (p planned) BlockedBy() string { + return p.blocked +} // Describe reports the work against the upgrade itself, and nothing against any // other subject. @@ -198,9 +210,15 @@ type handOverRelease struct { described } -func (h handOverRelease) ID() upgrade.ID { return h.id } -func (handOverRelease) Stage() upgrade.Stage { return upgrade.StageUpgrade } -func (handOverRelease) Phase() upgrade.Phase { return "" } +func (h handOverRelease) ID() upgrade.ID { + return h.id +} +func (handOverRelease) Stage() upgrade.Stage { + return upgrade.StageUpgrade +} +func (handOverRelease) Phase() upgrade.Phase { + return "" +} func (handOverRelease) Requires() []upgrade.ID { return []upgrade.ID{IDVerifyCRDVersions} diff --git a/operator/internal/upgrade/subject.go b/operator/internal/upgrade/subject.go index 897b6ab2c..4aa31c02f 100644 --- a/operator/internal/upgrade/subject.go +++ b/operator/internal/upgrade/subject.go @@ -50,10 +50,14 @@ type Subject struct { // IsUpgrade reports whether this is the upgrade rather than an object in the // cluster. -func (s Subject) IsUpgrade() bool { return s.Object == nil } +func (s Subject) IsUpgrade() bool { + return s.Object == nil +} // String renders the subject the way a plan names it. -func (s Subject) String() string { return s.Ref.String() } +func (s Subject) String() string { + return s.Ref.String() +} // TheUpgrade is the subject the steps of §9.1 act on. There is exactly one per // run, and it is named for the namespace the operator's own furniture lives in, diff --git a/operator/internal/upgrade/tui/reporter.go b/operator/internal/upgrade/tui/reporter.go index 5a1854747..3a3943e6d 100644 --- a/operator/internal/upgrade/tui/reporter.go +++ b/operator/internal/upgrade/tui/reporter.go @@ -269,7 +269,9 @@ func newModel() *model { } } -func (m *model) Init() tea.Cmd { return m.spinner.Tick } +func (m *model) Init() tea.Cmd { + return m.spinner.Tick +} func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { diff --git a/operator/internal/webhook/certmanager.go b/operator/internal/webhook/certmanager.go index 3afb16306..8fed16d9a 100644 --- a/operator/internal/webhook/certmanager.go +++ b/operator/internal/webhook/certmanager.go @@ -57,7 +57,9 @@ type certManagerProvisioner struct { lastCA []byte } -func (p *certManagerProvisioner) NeedLeaderElection() bool { return false } +func (p *certManagerProvisioner) NeedLeaderElection() bool { + return false +} func (p *certManagerProvisioner) Start(ctx context.Context) error { log := logf.FromContext(ctx).WithName("webhook-cert-manager-provisioner") diff --git a/test/integration/cluster/copy.go b/test/integration/cluster/copy.go index ea2f4b7b4..4ea363b44 100644 --- a/test/integration/cluster/copy.go +++ b/test/integration/cluster/copy.go @@ -70,4 +70,6 @@ func (c *Cluster) CopyTo(ctx context.Context, namespace, pod, local, remote stri } // shellWord makes one value safe as a single shell word. -func shellWord(s string) string { return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'" } +func shellWord(s string) string { + return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'" +} diff --git a/test/integration/cluster/talos.go b/test/integration/cluster/talos.go index f1a1c2636..2ede9ddb1 100644 --- a/test/integration/cluster/talos.go +++ b/test/integration/cluster/talos.go @@ -390,13 +390,19 @@ func (c *Cluster) removeStateDir(ctx context.Context) error { } // Kubeconfig is this cluster's kubeconfig path. -func (c *Cluster) Kubeconfig() string { return c.kubeconfig } +func (c *Cluster) Kubeconfig() string { + return c.kubeconfig +} // Talosconfig is this cluster's talosconfig, for talosctl commands. -func (c *Cluster) Talosconfig() string { return c.talosconfig } +func (c *Cluster) Talosconfig() string { + return c.talosconfig +} // WorkDir holds the kubeconfig, talosconfig and cluster state. -func (c *Cluster) WorkDir() string { return c.workDir } +func (c *Cluster) WorkDir() string { + return c.workDir +} // controlplaneAddresses returns the Talos API addresses of the control plane, // which is what talosctl commands need as --nodes. @@ -429,7 +435,9 @@ func (c *Cluster) controlplaneAddresses(ctx context.Context) ([]string, error) { // Addresses are the cluster's Talos API addresses. They are also what an nvmet // target on a node advertises as its traddr. -func (c *Cluster) Addresses() []string { return c.addresses } +func (c *Cluster) Addresses() []string { + return c.addresses +} // diagnose collects what can still be read from a cluster that failed to come // up. Best effort by design: it runs on a path where something is already wrong, diff --git a/test/integration/controlplane/server.go b/test/integration/controlplane/server.go index 79a88e388..13f34a2cf 100644 --- a/test/integration/controlplane/server.go +++ b/test/integration/controlplane/server.go @@ -89,7 +89,9 @@ func (s *Server) Port() int { } // URL is the base URL for a client in this process. -func (s *Server) URL() string { return fmt.Sprintf("http://127.0.0.1:%d", s.Port()) } +func (s *Server) URL() string { + return fmt.Sprintf("http://127.0.0.1:%d", s.Port()) +} // URLFor is the base URL for a client that reaches this host at addr — the // cluster network's gateway, for something running on a node. @@ -167,7 +169,9 @@ func notImplemented(w http.ResponseWriter, r *http.Request) { // a 404 carrying the message. type hostError string -func (e hostError) Error() string { return string(e) } +func (e hostError) Error() string { + return string(e) +} func poolDTO(p Pool) StoragePoolDTO { return StoragePoolDTO{ diff --git a/test/integration/controlplane/state.go b/test/integration/controlplane/state.go index f2de108f2..1be128729 100644 --- a/test/integration/controlplane/state.go +++ b/test/integration/controlplane/state.go @@ -120,7 +120,9 @@ type Volume struct { // HighAvailability reports whether the volume has more than one node, which is // what ha_type means in the control plane. -func (v Volume) HighAvailability() bool { return len(v.Nodes) > 1 } +func (v Volume) HighAvailability() bool { + return len(v.Nodes) > 1 +} // allowedHost finds a volume's ACL entry for a host NQN. func (v Volume) allowedHost(nqn string) (AllowedHost, bool) { diff --git a/test/integration/fabric/nodeshell.go b/test/integration/fabric/nodeshell.go index db4baf2d2..6b534c592 100644 --- a/test/integration/fabric/nodeshell.go +++ b/test/integration/fabric/nodeshell.go @@ -178,13 +178,17 @@ func (s *Shell) LoadedModules(ctx context.Context) (string, error) { } // Node is the node this shell runs on. -func (s *Shell) Node() string { return s.node } +func (s *Shell) Node() string { + return s.node +} // Pod is the pod this shell runs as. A caller that has to address the pod // directly, rather than through Run, asks for it here: deriving the name from // the node a second time is a second copy of the sanitizing rule, and the two // stop agreeing the first time the rule changes. -func (s *Shell) Pod() string { return s.pod } +func (s *Shell) Pod() string { + return s.pod +} // unnameable matches every run of characters a Kubernetes name may not hold. // diff --git a/test/integration/fabric/target.go b/test/integration/fabric/target.go index ceb33b949..20f25c64d 100644 --- a/test/integration/fabric/target.go +++ b/test/integration/fabric/target.go @@ -220,13 +220,17 @@ func (t *Target) DisableNamespace(ctx context.Context, nsid int) error { } // NQN is the subsystem NQN. -func (t *Target) NQN() string { return t.spec.NQN } +func (t *Target) NQN() string { + return t.spec.NQN +} // NamespaceDevice is the loop device backing namespace nsid on the target's // node, or "" when AddNamespace never created it. It is the target-side handle // on the namespace's bytes: writing a filesystem onto it before (or after) the // host connects is how a test puts real data behind the fabric. -func (t *Target) NamespaceDevice(nsid int) string { return t.namespaces[nsid] } +func (t *Target) NamespaceDevice(nsid int) string { + return t.namespaces[nsid] +} // UnlinkPort withdraws the subsystem from its port: the running association // drops, the host's controller goes into reconnecting, and — this being the @@ -253,7 +257,9 @@ func (t *Target) RelinkPort(ctx context.Context) error { } // Endpoint is the address:port an initiator connects to. -func (t *Target) Endpoint() (string, int) { return t.spec.Addr, t.spec.Port } +func (t *Target) Endpoint() (string, int) { + return t.spec.Addr, t.spec.Port +} // Close removes the port, the namespaces and the subsystem, and detaches the // loop devices. @@ -283,7 +289,9 @@ func (t *Target) Close(ctx context.Context) error { return nil } -func (t *Target) subsysDir() string { return t.root + "/subsystems/" + t.spec.NQN } +func (t *Target) subsysDir() string { + return t.root + "/subsystems/" + t.spec.NQN +} func (t *Target) portDir() string { return t.root + "/ports/" + strconv.Itoa(t.spec.PortID) } diff --git a/test/integration/onnode/volstack_test.go b/test/integration/onnode/volstack_test.go index e39747f37..ff71a3efa 100644 --- a/test/integration/onnode/volstack_test.go +++ b/test/integration/onnode/volstack_test.go @@ -189,7 +189,9 @@ func hideTheTargetBacking(t *testing.T) { } // runner is the runner under test, recording to this run's own directory. -func (h *harness) runner() *volstack.Runner { return volstack.NewRunner(volstack.NewStore(h.records)) } +func (h *harness) runner() *volstack.Runner { + return volstack.NewRunner(volstack.NewStore(h.records)) +} // blank makes these namespaces read as empty volumes again. // @@ -275,7 +277,9 @@ func zero(t *testing.T, dev blockdev.Device) { } // handle names this volume's stack in the record. -func (h *harness) handle() string { return h.volume.UUID } +func (h *harness) handle() string { + return h.volume.UUID +} // up brings the plan up and fails the test if it could not. func (h *harness) up(ctx context.Context, plan volstack.Plan) volstack.Artifact { diff --git a/test/integration/suites/volstack_test.go b/test/integration/suites/volstack_test.go index b6053b166..5e7e0ecfc 100644 --- a/test/integration/suites/volstack_test.go +++ b/test/integration/suites/volstack_test.go @@ -251,4 +251,6 @@ func exportEnv(env map[string]string) string { } // shellValue makes one value safe as a single shell word. -func shellValue(s string) string { return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'" } +func shellValue(s string) string { + return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'" +} From e76bd4e9b54c1b7548ae2da2ece272105da16f19 Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Thu, 10 Sep 2026 22:02:46 +0200 Subject: [PATCH 12/19] build(hack): add the onelinefunc rule, in two front ends The rule the previous commit satisfies, written down so it stays satisfied: a function declaration's body does not share a line with its signature. Nothing off the shelf expresses it. revive carries no such check among its rules, and neither gofmt nor gofumpt reformats `func f() int { return 1 }`. Both were run against a fixture rather than taken on trust, and both left it alone. It is an AST rule rather than a pattern because a signature can legally hold a brace before the body's. `func f() map[string]struct{} { return nil }` is in the test fixture for exactly that reason, and a regex reading left to right finds the wrong brace. go/ast finds the right one. Two front ends over one analyzer: - cmd/onelinefunc runs it alone, through singlechecker, so a Makefile target or a CI step can enforce it without rebuilding golangci-lint. It also reaches test/integration, which repo_lint.yaml's matrix does not lint at all. - plugin.go registers it as a golangci-lint module plugin, for the route that puts it beside the linters each component's .golangci.yml already enables. Neither is wired up yet, deliberately. The golangci-lint route costs more than it looks: $(GOLANGCI_LINT) is a release binary pinned by sha256 in scripts/tools.lock and used by three Makefiles, a plugin cannot be downloaded, and atlas-lib has no .golangci.yml to enable it in. It lives in hack/ with a go.mod of its own because golangci-lint compiles a plugin into a bespoke binary and nothing here imports it. The house rule against new modules is about shared code the operator and the CSI driver would each need a replace directive for, which a build-time tool neither of them links is not. --- hack/golangci-onelinefunc/analyzer.go | 56 +++++++++++++++++++ .../cmd/onelinefunc/main.go | 18 ++++++ hack/golangci-onelinefunc/go.mod | 13 +++++ hack/golangci-onelinefunc/go.sum | 10 ++++ hack/golangci-onelinefunc/plugin.go | 38 +++++++++++++ 5 files changed, 135 insertions(+) create mode 100644 hack/golangci-onelinefunc/analyzer.go create mode 100644 hack/golangci-onelinefunc/cmd/onelinefunc/main.go create mode 100644 hack/golangci-onelinefunc/go.mod create mode 100644 hack/golangci-onelinefunc/go.sum create mode 100644 hack/golangci-onelinefunc/plugin.go diff --git a/hack/golangci-onelinefunc/analyzer.go b/hack/golangci-onelinefunc/analyzer.go new file mode 100644 index 000000000..4384a5d3b --- /dev/null +++ b/hack/golangci-onelinefunc/analyzer.go @@ -0,0 +1,56 @@ +// The rule: a function declaration's body does not share a line with its +// signature. +// +// No existing linter expresses it. revive has no such check among its rules, +// and neither gofmt nor gofumpt reformats `func f() int { return 1 }`, both of +// which were verified rather than assumed. So it is written here once and used +// from two front ends: golangci-lint loads it as a module plugin, and +// cmd/onelinefunc runs the same analyzer on its own. +package onelinefunc + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" +) + +// Analyzer reports every function declaration whose body opens and closes on +// one line, provided the body holds at least one statement. +// +// Two things are deliberately not reported. An empty body is idiomatic, because +// `func (noopReporter) Report() {}` is how a no-op implementation of an +// interface is written and spreading it over three lines says less. And a +// function literal is not a declaration: a closure passed to a Ginkgo `It`, or +// a `defer func() { cancel() }()`, reads better on one line, which is why this +// is about declarations only. +var Analyzer = &analysis.Analyzer{ + Name: "onelinefunc", + Doc: "a function declaration's body does not share a line with its signature", + Run: run, +} + +func run(pass *analysis.Pass) (any, error) { + for _, file := range pass.Files { + for _, decl := range file.Decls { + fn, ok := decl.(*ast.FuncDecl) + if !ok || fn.Body == nil || len(fn.Body.List) == 0 { + continue + } + + open := pass.Fset.Position(fn.Body.Lbrace) + closing := pass.Fset.Position(fn.Body.Rbrace) + if open.Line != closing.Line { + continue + } + + pass.Report(analysis.Diagnostic{ + Pos: fn.Body.Lbrace, + End: fn.Body.Rbrace, + Category: "onelinefunc", + Message: "function body shares a line with its signature, put it on its own lines", + }) + } + } + + return nil, nil +} diff --git a/hack/golangci-onelinefunc/cmd/onelinefunc/main.go b/hack/golangci-onelinefunc/cmd/onelinefunc/main.go new file mode 100644 index 000000000..b8d227e08 --- /dev/null +++ b/hack/golangci-onelinefunc/cmd/onelinefunc/main.go @@ -0,0 +1,18 @@ +// Runs the onelinefunc rule on its own, without golangci-lint. +// +// This is the front end a Makefile target or a CI step uses: +// +// go run ./hack/golangci-onelinefunc/cmd/onelinefunc ./... +// +// It exists so the rule can be enforced without rebuilding golangci-lint, and +// so it can cover test/integration, which repo_lint.yaml's matrix does not. +package main + +import ( + "github.com/simplyblock/golangci-onelinefunc" + "golang.org/x/tools/go/analysis/singlechecker" +) + +func main() { + singlechecker.Main(onelinefunc.Analyzer) +} diff --git a/hack/golangci-onelinefunc/go.mod b/hack/golangci-onelinefunc/go.mod new file mode 100644 index 000000000..41c3c50dc --- /dev/null +++ b/hack/golangci-onelinefunc/go.mod @@ -0,0 +1,13 @@ +module github.com/simplyblock/golangci-onelinefunc + +go 1.26.5 + +require ( + github.com/golangci/plugin-module-register v0.1.1 + golang.org/x/tools v0.31.0 +) + +require ( + golang.org/x/mod v0.24.0 // indirect + golang.org/x/sync v0.12.0 // indirect +) diff --git a/hack/golangci-onelinefunc/go.sum b/hack/golangci-onelinefunc/go.sum new file mode 100644 index 000000000..58abf7586 --- /dev/null +++ b/hack/golangci-onelinefunc/go.sum @@ -0,0 +1,10 @@ +github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= +github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= diff --git a/hack/golangci-onelinefunc/plugin.go b/hack/golangci-onelinefunc/plugin.go new file mode 100644 index 000000000..7ef7a5f00 --- /dev/null +++ b/hack/golangci-onelinefunc/plugin.go @@ -0,0 +1,38 @@ +// Registers the onelinefunc rule as a golangci-lint module plugin. +// +// This package needs a go.mod of its own because golangci-lint compiles a +// plugin into a bespoke binary, and nothing in this repository imports it. The +// house rule against new modules is about shared code the operator and the CSI +// driver would each need a replace directive for; a build-time tool neither of +// them links is not that. +package onelinefunc + +import ( + "github.com/golangci/plugin-module-register/register" + "golang.org/x/tools/go/analysis" +) + +func init() { + register.Plugin("onelinefunc", New) +} + +// Plugin carries no settings. The rule has nothing to configure: a body either +// shares its signature's line or it does not. +type Plugin struct{} + +// New builds the plugin. golangci-lint hands over the `settings` block from +// .golangci.yml, and this rule reads none of it. +func New(_ any) (register.LinterPlugin, error) { + return &Plugin{}, nil +} + +// BuildAnalyzers returns the single analyzer this plugin provides. +func (p *Plugin) BuildAnalyzers() ([]*analysis.Analyzer, error) { + return []*analysis.Analyzer{Analyzer}, nil +} + +// GetLoadMode asks for syntax only. The rule reads brace positions and needs no +// type information. +func (p *Plugin) GetLoadMode() string { + return register.LoadModeSyntax +} From 7859f202fd53476b82965a30949ca9286336a754 Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Thu, 10 Sep 2026 22:13:28 +0200 Subject: [PATCH 13/19] docs(go): bring the comments to the house style 394 fixes across 81 files, all of them in comments. The house style gate reports 608 errors over the repository's 698 non-generated Go files; this clears the ones that are prose, and leaves the ones that are not. Applied: British spellings (behaviour, cancelled, initialise, honour, dialled, signalled, backwards, centre and the rest), the comma that e.g. and i.e. take, the adverb that is not hyphenated to its adjective, product names spelled the way their owners spell them (K8s, Docker, JSON, I/O, Kubernetes, Kubebuilder, Slack), and the brand lowercase mid-sentence. Not applied, deliberately: all 144 quoted-mark findings. The rule puts a comma or a full stop inside the closing quotation mark, which is right for a quoted phrase and wrong for a quoted value, and a Go comment documenting a field is full of quoted values. Left to the fixer it rewrote `"^sb-fio-baseline-.*".` into a regex with an extra character, `("",` into `(","`, a `fmt.Sprintf("/api/v2/clusters/%s/storage-pools/%s",` into a format string ending in a comma, and `"live", "connecting", "resetting",` into a list whose separators had moved inside the values. The house style already answers this: a value takes backticks rather than quotation marks. Doing that is a separate pass, and until then these stay as they are. Eighteen fixes were applied and then reverted, because the checkers cannot tell prose from a name: - `[nvme]` and `[nvmeof]` are godoc links, on lines that still said [nvme.Siblings] and [nvmeof.WaitForDevice] after the rewrite. - `nqn` names a parameter in seventeen doc comments, and godoc refers to a parameter by the name it has. - `/nics` is an endpoint, not a plural. - `public.ecr.aws/simply-block` is a registry path, which the adverb-hyphen rule turned into `simply block`. - `"xfs"` is what mkfs is passed, and `delete(hostDomains, ip)` is an expression quoted in a comment. That no code changed is not an assumption: every one of the 81 files was tokenized before and after with go/scanner, comments skipped, and the token streams compared. They are identical. `go vet ./...` passes in all four modules and gofmt is clean. --- atlas-lib/link/dial.go | 2 +- atlas-lib/link/doc.go | 4 +-- atlas-lib/link/peer.go | 2 +- atlas-lib/link/registry.go | 2 +- atlas-lib/link/session.go | 4 +-- atlas-lib/storage/storagerpc/convert.go | 2 +- atlas-lib/storage/storagerpc/convert_test.go | 2 +- atlas-lib/storage/storagerpc/doc.go | 2 +- operator/api/v1alpha1/backuppolicy_types.go | 8 ++--- operator/api/v1alpha1/backuprestore_types.go | 2 +- operator/api/v1alpha1/controlplane_types.go | 4 +-- operator/api/v1alpha1/replicationops_types.go | 6 ++-- .../api/v1alpha1/replicationpolicy_types.go | 2 +- operator/api/v1alpha1/storagebackup_types.go | 6 ++-- .../api/v1alpha1/storageclusterops_types.go | 4 +-- operator/api/v1alpha1/storagenode_types.go | 12 +++---- operator/api/v1alpha1/storagenodeset_types.go | 10 +++--- operator/api/v1alpha1/storagepool_types.go | 4 +-- operator/api/v1alpha1/task_types.go | 2 +- .../api/v1alpha1/volumemigration_types.go | 2 +- operator/cmd/simplyblock-rebalancer/main.go | 8 ++--- .../validate_migration.go | 8 ++--- .../validate_migration_test.go | 2 +- operator/internal/autoplacement/baseline.go | 2 +- .../autoplacement/baseline_strategy.go | 4 +-- .../autoplacement/logical_volume_selector.go | 8 ++--- operator/internal/autoplacement/rebalancer.go | 4 +-- .../autoplacement/storage_node_selector.go | 12 +++---- operator/internal/autoplacement/types.go | 2 +- operator/internal/autoplacement/utils.go | 6 ++-- .../controller/backuppolicy_controller.go | 16 +++++----- .../backuppolicy_controller_unit_test.go | 2 +- .../controller/benchmark_provisioner.go | 2 +- .../controller/nodedrain_controller.go | 20 ++++++------ .../nodedrain_controller_unit_test.go | 2 +- .../persistentvolumeclaim_controller.go | 4 +-- .../replicationpolicy_controller.go | 2 +- .../controller/replicationslot_controller.go | 32 +++++++++---------- .../replicationslot_controller_unit_test.go | 6 ++-- .../simplyblockstoragenodeset_controller.go | 12 +++---- ...lockstoragenodeset_controller_unit_test.go | 6 ++-- .../simplyblockstoragenodeset_drain.go | 6 ++-- ...mplyblockstoragenodeset_drain_unit_test.go | 2 +- .../simplyblockstoragepool_controller.go | 8 ++--- .../controller/simplyblocktask_controller.go | 2 +- .../controller/storagebackup_controller.go | 4 +-- .../storagebackupsync_controller.go | 4 +-- .../storagebackupsync_controller_unit_test.go | 4 +-- .../storageclusterops_controller_unit_test.go | 2 +- .../storageclusterops_noderollingrestart.go | 2 +- .../controller/storagenode_controller.go | 4 +-- .../storagenode_latency_controller.go | 8 ++--- .../controller/storagenodeops_controller.go | 12 +++---- .../storagenodeops_controller_unit_test.go | 4 +-- .../controller/volumemigration_controller.go | 12 +++---- .../volumemigration_controller_unit_test.go | 4 +-- .../volumemigration_helpers_test.go | 4 +-- .../volumemigration_migration_paths_test.go | 8 ++--- .../controller/volumerebalancer_controller.go | 4 +-- .../volumerebalancer_realignment_test.go | 2 +- operator/internal/cpinformer/sse.go | 2 +- operator/internal/csilink/hub.go | 4 +-- operator/internal/csilink/hub_test.go | 2 +- operator/internal/tlsutil/tlsutil.go | 4 +-- operator/internal/utils/objects.go | 4 +-- .../internal/volumemigration/config_test.go | 2 +- operator/internal/volumemigration/paths.go | 8 ++--- operator/internal/volumemigration/release.go | 10 +++--- operator/internal/volumemigration/scoring.go | 2 +- operator/internal/volumemigration/utils.go | 2 +- operator/internal/volumemigration/validate.go | 2 +- operator/internal/webapi/client.go | 2 +- operator/internal/webapi/rebalancing.go | 14 ++++---- operator/internal/webhook/certmanager.go | 2 +- operator/internal/webhook/certmanager_test.go | 2 +- .../simplyblock_rebalancer_injector.go | 2 +- .../simplyblock_volume_placement_injector.go | 2 +- ...plyblock_volume_placement_injector_test.go | 2 +- .../internal/webhook/storagenode_validator.go | 2 +- operator/test/e2e/rbac_test.go | 2 +- operator/test/utils/utils.go | 2 +- 81 files changed, 206 insertions(+), 206 deletions(-) diff --git a/atlas-lib/link/dial.go b/atlas-lib/link/dial.go index f517aea90..d6175d22c 100644 --- a/atlas-lib/link/dial.go +++ b/atlas-lib/link/dial.go @@ -13,7 +13,7 @@ import ( // node's identity to anything on the path. cfg supplies the trust roots for the // hub's serving certificate — a nil cfg falls back to the system roots and // derives the expected server name from addr, which is right for a -// publicly-rooted certificate and wrong for the in-cluster CA that normally +// publicly rooted certificate and wrong for the in-cluster CA that normally // signs one. func TLSDialer(addr string, cfg *tls.Config) Dialer { dialer := &tls.Dialer{Config: cfg} diff --git a/atlas-lib/link/doc.go b/atlas-lib/link/doc.go index 1f59f8726..713569803 100644 --- a/atlas-lib/link/doc.go +++ b/atlas-lib/link/doc.go @@ -1,7 +1,7 @@ // Package link carries gRPC between the operator and the CSI driver over // connections the CSI driver opens. // -// # Why the connection runs backwards +// # Why the connection runs backward // // The operator is the one that needs to ask questions — what does this node's // fabric look like, are this volume's paths up — and the answers live on the @@ -16,7 +16,7 @@ // discovered, and the only reachability the deployment needs is the one it // already has — pods reaching a Service. // -// # How a backwards connection still speaks ordinary gRPC +// # How a backward connection still speaks ordinary gRPC // // gRPC assumes the side that dialed is the client. Here it is the server, so // something has to separate the two roles from the direction the TCP connection diff --git a/atlas-lib/link/peer.go b/atlas-lib/link/peer.go index 647f5dfed..9190a8e71 100644 --- a/atlas-lib/link/peer.go +++ b/atlas-lib/link/peer.go @@ -43,7 +43,7 @@ func ControllerPeer(pod string) PeerID { return PeerID{Kind: PeerKindController, Name: pod} } -// String renders the id as "kind/name", e.g. "node/worker-3". +// String renders the id as "kind/name", e.g., "node/worker-3". func (id PeerID) String() string { return string(id.Kind) + "/" + id.Name } diff --git a/atlas-lib/link/registry.go b/atlas-lib/link/registry.go index a73e20a56..9db39d06e 100644 --- a/atlas-lib/link/registry.go +++ b/atlas-lib/link/registry.go @@ -85,7 +85,7 @@ func (r *Registry) Len() int { // register installs p and returns the peer it displaced, if any. // // A peer that links while an entry for it already exists is the normal shape of -// a restart: the pod came back and dialled in before the hub noticed the old +// a restart: the pod came back and dialed in before the hub noticed the old // TCP connection was half-open. The new session wins — it is the one demonstrably // alive — and the caller closes the displaced one. func (r *Registry) register(p *Peer) *Peer { diff --git a/atlas-lib/link/session.go b/atlas-lib/link/session.go index 05af5687a..8d68a0b08 100644 --- a/atlas-lib/link/session.go +++ b/atlas-lib/link/session.go @@ -19,7 +19,7 @@ import ( // // Both ends hold the same thing. Each runs a grpc.Server over the streams the // other opens and a grpc.ClientConn over the streams it opens itself, so which -// end dialled stops mattering the moment the session exists. That symmetry is +// end dialed stops mattering the moment the session exists. That symmetry is // the whole reason for the multiplexer: it is what lets the CSI driver dial out // and still be the one answering calls. type Session struct { @@ -167,7 +167,7 @@ func (s *Session) dial(ctx context.Context, _ string) (net.Conn, error) { return nil, ErrNoSession } - // yamux's Open takes no context, so honour the caller's deadline here + // yamux's Open takes no context, so honor the caller's deadline here // instead of inheriting the multiplexer's stream-open timeout. type opened struct { conn net.Conn diff --git a/atlas-lib/storage/storagerpc/convert.go b/atlas-lib/storage/storagerpc/convert.go index 283a6c0da..f41c837e2 100644 --- a/atlas-lib/storage/storagerpc/convert.go +++ b/atlas-lib/storage/storagerpc/convert.go @@ -10,7 +10,7 @@ import ( // Conversion between the nvme snapshot types and their wire form. // // The mapping is total in both directions: every exported field of every type -// has a wire field, and nothing is derived, summarised or dropped. That is what +// has a wire field, and nothing is derived, summarized or dropped. That is what // lets a caller on the far side of a link ask the same questions of a device as // a caller on the node — Accessible, Siblings, CoTenants and the rest are pure // functions of these fields, so they answer identically once the snapshot diff --git a/atlas-lib/storage/storagerpc/convert_test.go b/atlas-lib/storage/storagerpc/convert_test.go index 0a5fce027..a659e9028 100644 --- a/atlas-lib/storage/storagerpc/convert_test.go +++ b/atlas-lib/storage/storagerpc/convert_test.go @@ -123,7 +123,7 @@ func TestSelectorRoundTrip(t *testing.T) { } // The zero selector means "everything", and must not become something narrower -// by travelling. +// by traveling. func TestZeroSelectorRoundTrip(t *testing.T) { if got := selectorFromProto(selectorToProto(nvme.DeviceSelector{})); !got.IsZero() { t.Errorf("zero selector round-tripped to %s, want the zero selector", got) diff --git a/atlas-lib/storage/storagerpc/doc.go b/atlas-lib/storage/storagerpc/doc.go index d2d69f24b..d26659f43 100644 --- a/atlas-lib/storage/storagerpc/doc.go +++ b/atlas-lib/storage/storagerpc/doc.go @@ -26,7 +26,7 @@ // // # What crosses and what does not // -// The nvme types are immutable snapshots of sysfs with no behaviour attached, +// The nvme types are immutable snapshots of sysfs with no behavior attached, // so the wire form is a complete copy rather than a summary. Everything derived // from a snapshot is therefore just as true on the far side: // [nvme.Device.Accessible], [nvme.IsSibling], [nvme.CoTenants] and the rest are diff --git a/operator/api/v1alpha1/backuppolicy_types.go b/operator/api/v1alpha1/backuppolicy_types.go index ac3171738..2bcc01ee8 100644 --- a/operator/api/v1alpha1/backuppolicy_types.go +++ b/operator/api/v1alpha1/backuppolicy_types.go @@ -40,7 +40,7 @@ type BackupPolicySpec struct { // +k8s:immutable MaxVersions int `json:"maxVersions,omitempty"` - // MaxAge is the maximum age of backups to retain (e.g. "7d", "12h", "30m"). + // MaxAge is the maximum age of backups to retain (e.g., "7d", "12h", "30m"). // Backups older than this are merged. Accepts m, h, d, w suffixes. // +optional // +kubebuilder:validation:Pattern=`^[1-9]\d*[mhdw]$` @@ -49,7 +49,7 @@ type BackupPolicySpec struct { MaxAge string `json:"maxAge,omitempty"` // Schedule defines the tiered backup schedule as a space-separated list of - // interval,keep_count pairs (e.g. "15m,4 60m,11 24h,7"). + // interval,keep_count pairs (e.g., "15m,4 60m,11 24h,7"). // Intervals must be strictly increasing. Supported units: m, h, d, w. // +optional // +kubebuilder:validation:Pattern=`^(\d+[mhdw],\d+)( +\d+[mhdw],\d+)*$` @@ -104,8 +104,8 @@ type BackupPolicyStatus struct { // // simplyblock.io/backup-policy: // -// The deprecated simplybk/backup-policy annotation is still honoured for -// backwards compatibility; when both are set, simplyblock.io/backup-policy +// The deprecated simplybk/backup-policy annotation is still honored for +// backward compatibility; when both are set, simplyblock.io/backup-policy // takes precedence. // // The BackupPolicy must be in the same namespace as the annotated PVC. diff --git a/operator/api/v1alpha1/backuprestore_types.go b/operator/api/v1alpha1/backuprestore_types.go index e81fd0f18..dbc8ac09e 100644 --- a/operator/api/v1alpha1/backuprestore_types.go +++ b/operator/api/v1alpha1/backuprestore_types.go @@ -106,7 +106,7 @@ type BackupRestoreStatus struct { PoolUUID string `json:"poolUUID,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=status,displayName="Restored LVOL ID" - // RestoredLvolID is the UUID of the newly-created logical volume. + // RestoredLvolID is the UUID of the newly created logical volume. RestoredLvolID string `json:"restoredLvolID,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=status,displayName="Persistent Volume" diff --git a/operator/api/v1alpha1/controlplane_types.go b/operator/api/v1alpha1/controlplane_types.go index e5a061c6b..abca90999 100644 --- a/operator/api/v1alpha1/controlplane_types.go +++ b/operator/api/v1alpha1/controlplane_types.go @@ -24,7 +24,7 @@ import ( // created by the Helm chart. type ControlPlaneSpec struct { // Image is the container image used for all simplyblock control-plane and - // storage-node workloads (e.g. quay.io/simplyblock-io/simplyblock:26.2.2). + // storage-node workloads (e.g., quay.io/simplyblock-io/simplyblock:26.2.2). // StorageNodeSet CRs that omit spec.clusterImage inherit this value. // Must reference one of the trusted registries (quay.io/simplyblock-io, docker.io/simplyblock, public.ecr.aws/simply-block); digest pinning (@sha256:...) is recommended. // +optional @@ -41,7 +41,7 @@ type ControlPlaneStatus struct { Phase string `json:"phase,omitempty"` // Message contains a human-readable explanation of the current phase, - // for example the FDB error returned by the health endpoint. + // for example, the FDB error returned by the health endpoint. Message string `json:"message,omitempty"` // LastChecked is the timestamp of the most recent FDB health probe. diff --git a/operator/api/v1alpha1/replicationops_types.go b/operator/api/v1alpha1/replicationops_types.go index 54d48bb66..132054171 100644 --- a/operator/api/v1alpha1/replicationops_types.go +++ b/operator/api/v1alpha1/replicationops_types.go @@ -60,8 +60,8 @@ type ReplicationOpsResult struct { // ReplicationOpsSpec defines the desired state of a ReplicationOps. type ReplicationOpsSpec struct { // Action is the operation to perform. Immutable. - // failover: unplanned — promote target clone, source may be down. - // failback: restore source as primary after a prior failover. + // failover: unplanned — promote target clone, source may be down. + // failback: restore source as primary after a prior failover. // migration: planned cutover — calls replication_commit per volume; both clusters stay up. // State progression: replicating → cutover_pending → cutover_done. // +kubebuilder:validation:Enum=failover;failback;migration @@ -102,7 +102,7 @@ type ReplicationOpsStatus struct { Phase string `json:"phase,omitempty"` // Subphase describes what the operation is currently doing within the phase - // (e.g. "TriggeringFailover", "UpdatingSlotStatuses", "ReleasingLock"). + // (e.g., "TriggeringFailover", "UpdatingSlotStatuses", "ReleasingLock"). // +optional Subphase string `json:"subphase,omitempty"` diff --git a/operator/api/v1alpha1/replicationpolicy_types.go b/operator/api/v1alpha1/replicationpolicy_types.go index 5ed2e528b..bd68c329d 100644 --- a/operator/api/v1alpha1/replicationpolicy_types.go +++ b/operator/api/v1alpha1/replicationpolicy_types.go @@ -35,7 +35,7 @@ type ReplicationPolicySpec struct { // +optional Mode string `json:"mode,omitempty"` - // Interval is how often a replication snapshot is taken (e.g. "5m", "1h"). + // Interval is how often a replication snapshot is taken (e.g., "5m", "1h"). // +kubebuilder:default="5m" // +optional Interval string `json:"interval,omitempty"` diff --git a/operator/api/v1alpha1/storagebackup_types.go b/operator/api/v1alpha1/storagebackup_types.go index 170f0481b..6ef3387a7 100644 --- a/operator/api/v1alpha1/storagebackup_types.go +++ b/operator/api/v1alpha1/storagebackup_types.go @@ -50,7 +50,7 @@ type StorageBackupSpec struct { // +optional PVCRef *PersistentVolumeClaimRef `json:"pvcRef,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Snapshot Name" - // SnapshotName optionally overrides the internally-created snapshot name. + // SnapshotName optionally overrides the internally created snapshot name. // +optional SnapshotName string `json:"snapshotName,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Source Cluster UUID" @@ -86,12 +86,12 @@ type StorageBackupStatus struct { LvolID string `json:"lvolID,omitempty"` // LvolName is the backend logical volume name. LvolName string `json:"lvolName,omitempty"` - // FSType is the filesystem type of the source PersistentVolume (e.g. "ext4", + // FSType is the filesystem type of the source PersistentVolume (e.g., "ext4", // "xfs"), captured at backup time so a restore can preserve it regardless of // which StorageClass the restored PVC ends up using. FSType string `json:"fsType,omitempty"` - // SnapshotID is the internally-created snapshot UUID used for the backup request. + // SnapshotID is the internally created snapshot UUID used for the backup request. SnapshotID string `json:"snapshotID,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=status,displayName="Snapshot Name" // SnapshotName is the snapshot name used for the backup request. diff --git a/operator/api/v1alpha1/storageclusterops_types.go b/operator/api/v1alpha1/storageclusterops_types.go index a42a078cb..020b0ae37 100644 --- a/operator/api/v1alpha1/storageclusterops_types.go +++ b/operator/api/v1alpha1/storageclusterops_types.go @@ -46,7 +46,7 @@ type NodeRollingRestartStatus struct { PhaseTriggered bool `json:"phaseTriggered,omitempty"` } -// NodeRollingRestartSpec configures the node-rolling-restart action behaviour. +// NodeRollingRestartSpec configures the node-rolling-restart action behavior. type NodeRollingRestartSpec struct { // RefreshSNodeAPI restarts the storage-node DaemonSet pod on each node // after the backend node is shut down and before it is restarted, ensuring @@ -68,7 +68,7 @@ type StorageClusterOpsSpec struct { // +kubebuilder:validation:Required Action string `json:"action"` - // NodeRollingRestart configures behaviour specific to the node-rolling-restart action. + // NodeRollingRestart configures behavior specific to the node-rolling-restart action. // Ignored for all other actions. // +optional NodeRollingRestart *NodeRollingRestartSpec `json:"nodeRollingRestart,omitempty"` diff --git a/operator/api/v1alpha1/storagenode_types.go b/operator/api/v1alpha1/storagenode_types.go index f11e5eb3c..df3167edb 100644 --- a/operator/api/v1alpha1/storagenode_types.go +++ b/operator/api/v1alpha1/storagenode_types.go @@ -30,7 +30,7 @@ import ( // of StorageNodeSetSpec. When a field is set here it takes precedence over the // fleet default; when omitted the fleet default applies. type StorageNodeOverrides struct { - // SpdkImage overrides the SPDK image for this node (e.g. for phased rollouts). + // SpdkImage overrides the SPDK image for this node (e.g., for phased rollouts). // +optional SpdkImage string `json:"spdkImage,omitempty"` @@ -39,7 +39,7 @@ type StorageNodeOverrides struct { SpdkProxyImage string `json:"spdkProxyImage,omitempty"` // SpdkSystemMemory overrides the SPDK huge-page memory allocation for this node - // (e.g. "4G", "512M"). + // (e.g., "4G", "512M"). // +kubebuilder:validation:Pattern=`^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$` // +optional SpdkSystemMemory string `json:"spdkSystemMemory,omitempty"` @@ -65,7 +65,7 @@ type StorageNodeOverrides struct { DriveSizeRange string `json:"driveSizeRange,omitempty"` // DeviceNames explicitly defines the NVMe namespace names to use on this node - // (e.g. ["nvme0n1","nvme1n1"]). + // (e.g., ["nvme0n1","nvme1n1"]). // +optional DeviceNames []string `json:"deviceNames,omitempty"` @@ -94,7 +94,7 @@ type StorageNodeOverrides struct { FailureDomain *int32 `json:"failureDomain,omitempty"` // Expand marks this node as a cluster-expansion add. When true the backend - // node-add endpoint receives expand=true, triggering rebalancing behaviour + // node-add endpoint receives expand=true, triggering rebalancing behavior // appropriate for in-place cluster growth. Overrides StorageNodeSet.spec.expand. // +optional Expand *bool `json:"expand,omitempty"` @@ -114,7 +114,7 @@ type StorageNodeSpec struct { // +kubebuilder:validation:Required WorkerNode string `json:"workerNode"` - // SocketID is the NUMA socket identifier from spec.socketsToUse (e.g. "0", "1"). Immutable. + // SocketID is the NUMA socket identifier from spec.socketsToUse (e.g., "0", "1"). Immutable. // +k8s:immutable // +optional SocketID string `json:"socketId,omitempty"` @@ -209,7 +209,7 @@ type StorageNodeStatus struct { // +optional UUID string `json:"uuid,omitempty"` - // Status is the backend-reported node status (e.g. online, suspended, offline). + // Status is the backend-reported node status (e.g., online, suspended, offline). // +optional Status string `json:"status,omitempty"` diff --git a/operator/api/v1alpha1/storagenodeset_types.go b/operator/api/v1alpha1/storagenodeset_types.go index 5f5fe2766..ce17f4281 100644 --- a/operator/api/v1alpha1/storagenodeset_types.go +++ b/operator/api/v1alpha1/storagenodeset_types.go @@ -22,7 +22,7 @@ import ( ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. +// NOTE: JSON tags are required. Any new fields you add must have JSON tags for the fields to be serialized. // JournalManagerSpec defines journal manager tuning parameters. type JournalManagerSpec struct { @@ -104,7 +104,7 @@ type StorageNodeSetSpec struct { // set to this value. Defaults to "worker" when unset. OpenShiftMachineConfigPool string `json:"openShiftMachineConfigPool,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Device Names" - // DeviceNames explicitly defines a comma separated list of nvme namespace names like nvme0n1,nvme1n1... + // DeviceNames explicitly defines a comma separated list of NVMe namespace names like nvme0n1,nvme1n1... DeviceNames []string `json:"deviceNames,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Ubuntu Host" // UbuntuHost indicates the node host OS is Ubuntu. @@ -124,7 +124,7 @@ type StorageNodeSetSpec struct { ReservedSystemCPU string `json:"reservedSystemCPU,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="SPDK System Memory" // +kubebuilder:validation:Pattern=`^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$` - // SpdkSystemMemory is the amount of memory reserved for SPDK system use (e.g. "4G", "512M"). + // SpdkSystemMemory is the amount of memory reserved for SPDK system use (e.g., "4G", "512M"). // When omitted the backend default is used. SpdkSystemMemory string `json:"spdkSystemMemory,omitempty"` @@ -141,7 +141,7 @@ type StorageNodeSetSpec struct { MaxParallelNodeAdds *int32 `json:"maxParallelNodeAdds,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Container Resources" // ContainerResources sets CPU and memory requests/limits for the main storage-node container. - // When omitted no limits are enforced, which preserves the previous behaviour. + // When omitted no limits are enforced, which preserves the previous behavior. ContainerResources corev1.ResourceRequirements `json:"containerResources,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Init Container Resources" // InitContainerResources sets CPU and memory requests/limits for the init container. @@ -165,7 +165,7 @@ type StorageNodeSetSpec struct { // Expand indicates that storage nodes added from this StorageNodeSet are being // added to expand an already-active cluster. When true the backend node-add // endpoint receives expand=true, which triggers the appropriate rebalancing - // behaviour for in-place cluster growth. + // behavior for in-place cluster growth. // +optional Expand *bool `json:"expand,omitempty"` diff --git a/operator/api/v1alpha1/storagepool_types.go b/operator/api/v1alpha1/storagepool_types.go index 1b45cf191..cc1f69351 100644 --- a/operator/api/v1alpha1/storagepool_types.go +++ b/operator/api/v1alpha1/storagepool_types.go @@ -21,7 +21,7 @@ import ( ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. +// NOTE: JSON tags are required. Any new fields you add must have JSON tags for the fields to be serialized. // StoragePoolQoSThroughputSpec defines throughput QosSpec limits in MiB/s. type StoragePoolQoSThroughputSpec struct { @@ -87,7 +87,7 @@ type StorageClassParameters struct { // +kubebuilder:default=false Encryption *bool `json:"encryption,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Fabric" - // Fabric is the transport fabric (e.g. tcp). + // Fabric is the transport fabric (e.g., TCP). // +kubebuilder:default=tcp Fabric string `json:"fabric,omitempty"` // MaxNamespacePerSubsys limits namespaces per NVMf subsystem. diff --git a/operator/api/v1alpha1/task_types.go b/operator/api/v1alpha1/task_types.go index 39cbfeb88..0c97b5841 100644 --- a/operator/api/v1alpha1/task_types.go +++ b/operator/api/v1alpha1/task_types.go @@ -21,7 +21,7 @@ import ( ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. +// NOTE: JSON tags are required. Any new fields you add must have JSON tags for the fields to be serialized. // TaskSpec defines the desired state of Task type TaskSpec struct { diff --git a/operator/api/v1alpha1/volumemigration_types.go b/operator/api/v1alpha1/volumemigration_types.go index 297afa0e6..0ef484032 100644 --- a/operator/api/v1alpha1/volumemigration_types.go +++ b/operator/api/v1alpha1/volumemigration_types.go @@ -22,7 +22,7 @@ const ( VolumeMigrationPhaseCompleted VolumeMigrationPhase = "Completed" // VolumeMigrationPhaseFailed means the migration finished with an error. VolumeMigrationPhaseFailed VolumeMigrationPhase = "Failed" - // VolumeMigrationPhaseAborted means the migration was cancelled via spec.abort. + // VolumeMigrationPhaseAborted means the migration was canceled via spec.abort. VolumeMigrationPhaseAborted VolumeMigrationPhase = "Aborted" ) diff --git a/operator/cmd/simplyblock-rebalancer/main.go b/operator/cmd/simplyblock-rebalancer/main.go index 9806409cf..10a0b5608 100644 --- a/operator/cmd/simplyblock-rebalancer/main.go +++ b/operator/cmd/simplyblock-rebalancer/main.go @@ -77,7 +77,7 @@ type fioOutput struct { } `json:"jobs"` } -// ── nvme list JSON structures ────────────────────────────────────────────────── +// ── NVMe list JSON structures ────────────────────────────────────────────────── type nvmeListOutput struct { Devices []struct { @@ -280,7 +280,7 @@ func probe(configFile, metricsAddr string, interval time.Duration) { var wg sync.WaitGroup // running maps each active node UUID to the cancel func that stops its probe. // The set is reconciled against the config every loop: new nodes are started and - // nodes that have disappeared (e.g. removed, or a cluster reinstall replaced the + // nodes that have disappeared (e.g., removed, or a cluster reinstall replaced the // ConfigMap which is keyed by the constant cluster *name*) are stopped — otherwise // a dead node keeps getting probed forever and leaves a stale gauge in Prometheus. running := make(map[string]context.CancelFunc) @@ -424,7 +424,7 @@ func connectAndWait(ctx context.Context, conn connConfig) (device string, discon // runs (the Job/probe can start before the subsystem listener is ready), so the // first attempt fails fast with "connection refused" / "no such subsystem". Without // this, a baseline Job errors out and only succeeds after several controller-driven - // recreations — the long-standing "jobs need multiple iterations" behaviour. + // recreations — the long-standing "jobs need multiple iterations" behavior. var connErr error connected := false for i := range 30 { @@ -453,7 +453,7 @@ func connectAndWait(ctx context.Context, conn connConfig) (device string, discon log.Printf("found %s (attempt %d)", path, i+1) return path, disconnect, nil } - // Device node exists but is not a block device (e.g. a stub left by + // Device node exists but is not a block device (e.g., a stub left by // SPDK). Remove it so the host's devtmpfs/udevd can create the // proper block special file in its place. log.Printf("found %s but it is not a block device — removing stub (attempt %d)", path, i+1) diff --git a/operator/cmd/simplyblock-rebalancer/validate_migration.go b/operator/cmd/simplyblock-rebalancer/validate_migration.go index d1f365145..63083a918 100644 --- a/operator/cmd/simplyblock-rebalancer/validate_migration.go +++ b/operator/cmd/simplyblock-rebalancer/validate_migration.go @@ -126,7 +126,7 @@ func (v validationRun) run( log.Printf("path %s to %s already present before connecting", addr, nqn) } - // The freshly-connected target path can lag behind: nvme connect may return before + // The freshly connected target path can lag behind: NVMe connect may return before // its controller is live and the ANA log page settles. Retry the connect+verify // cycle a few times before giving up so a transient lag is not mistaken for a // missing path. Already connected paths are a no-op in ensurePaths, so re-running @@ -191,7 +191,7 @@ func (v validationRun) reap(ctx context.Context, sysRoot, nqn string) { // release disconnects the migration's target paths, logging what went. Failures are // logged and swallowed: the run has already failed and the exit code must report that -// failure rather than this one, which would only mask why the migration was cancelled. +// failure rather than this one, which would only mask why the migration was canceled. func (v validationRun) release( ctx context.Context, sysRoot, nqn string, @@ -231,7 +231,7 @@ func validateMigration() { // validating anything. // // It is the mode the operator runs on nodes whose own validation passed. Those never -// learn that the migration was cancelled — another node's Job failed, or the operator +// learn that the migration was canceled — another node's Job failed, or the operator // gave up waiting — so their Job exited successfully with the target paths connected and // nothing on the node will ever release them. Every other failure path releases in the // Job that failed; this one exists because a success cannot. @@ -285,7 +285,7 @@ func validateAttempts() int { } // validateRetryDelay returns the delay between attempts, overridable via -// VMIG_VALIDATE_RETRY_DELAY (a Go duration, e.g. "2s"). Invalid values fall back +// VMIG_VALIDATE_RETRY_DELAY (a Go duration, e.g., "2s"). Invalid values fall back // to the default. func validateRetryDelay() time.Duration { if v := os.Getenv("VMIG_VALIDATE_RETRY_DELAY"); v != "" { diff --git a/operator/cmd/simplyblock-rebalancer/validate_migration_test.go b/operator/cmd/simplyblock-rebalancer/validate_migration_test.go index 7e380bd3e..c623b481e 100644 --- a/operator/cmd/simplyblock-rebalancer/validate_migration_test.go +++ b/operator/cmd/simplyblock-rebalancer/validate_migration_test.go @@ -406,7 +406,7 @@ func TestValidationRun_SkippedNodeIsNotTouched(t *testing.T) { } // Neither cleanup may change the outcome. A reap that fails still lets the validation -// decide, and a release that fails must not mask why the migration was cancelled. +// decide, and a release that fails must not mask why the migration was canceled. func TestValidationRun_CleanupFailuresDoNotChangeTheOutcome(t *testing.T) { t.Run("a failed reap still validates", func(t *testing.T) { rec := &recorder{present: true, reapErr: errors.New("delete_controller: device busy")} diff --git a/operator/internal/autoplacement/baseline.go b/operator/internal/autoplacement/baseline.go index 91ab33691..75ad3ba19 100644 --- a/operator/internal/autoplacement/baseline.go +++ b/operator/internal/autoplacement/baseline.go @@ -18,7 +18,7 @@ const ( // robustBaselineNS reduces a set of latency samples (ns) to a single baseline using the // Hampel identifier: samples further than k·1.4826·MAD from the window median are rejected // as outliers, and the median of the survivors is returned. The Hampel identifier has the -// highest possible breakdown point (50%) — both its centre (median) and its scale (MAD) are +// highest possible breakdown point (50%) — both its center (median) and its scale (MAD) are // themselves robust, so the extreme journal/EC/HA spikes it is meant to reject cannot inflate // the threshold and hide themselves. // diff --git a/operator/internal/autoplacement/baseline_strategy.go b/operator/internal/autoplacement/baseline_strategy.go index 8827513f4..44855ca45 100644 --- a/operator/internal/autoplacement/baseline_strategy.go +++ b/operator/internal/autoplacement/baseline_strategy.go @@ -48,7 +48,7 @@ func (b *benchmarkBaselineProvider) BaselineNS( var snodeList simplyblockv1alpha1.StorageNodeSetList if err := b.client.List(ctx, &snodeList, client.InNamespace(input.Namespace)); err != nil { // Stay resilient to a transient list error: skip this namespace rather than - // failing the whole evaluation cycle (matches the previous CR-read behaviour). + // failing the whole evaluation cycle (matches the previous CR-read behavior). continue } for _, snode := range snodeList.Items { @@ -107,7 +107,7 @@ type nodeBaseline struct { // reduceWindowedBaselines reduces per-node windowed samples to a single robust baseline each, // applying the cold-start policy. It is pure (no Prometheus, no metrics) so the cold-start -// and estimator behaviour can be tested directly. A node is dropped when it is under-sampled +// and estimator behavior can be tested directly. A node is dropped when it is under-sampled // under the "defer" policy, or when no positive baseline can be computed from its samples. func reduceWindowedBaselines( windowed map[string]map[string][]float64, diff --git a/operator/internal/autoplacement/logical_volume_selector.go b/operator/internal/autoplacement/logical_volume_selector.go index 2a07a174e..52ea6b95b 100644 --- a/operator/internal/autoplacement/logical_volume_selector.go +++ b/operator/internal/autoplacement/logical_volume_selector.go @@ -25,7 +25,7 @@ type VolumePlacement struct { PoolUUID string } -// RankedCandidate pairs an eligible VolumePlacement with its computed IO score, +// RankedCandidate pairs an eligible VolumePlacement with its computed I/O score, // used to order migration candidates within a source node (highest score first). type RankedCandidate struct { // Vol is the volume and its pool association. @@ -102,7 +102,7 @@ func (lvs *LogicalVolumeSelector) SelectVolumesForMigration( continue } - // Rank by IO score descending — highest load migrated first. + // Rank by I/O score descending — highest load migrated first. ranked := make([]RankedCandidate, 0, len(eligible)) for _, vp := range eligible { score := volumemigration.VolumeIOScore(vp.IOPS, vp.ThroughputBytesPerSec, cfg.IopsWeight, cfg.ThroughputWeight) @@ -127,7 +127,7 @@ func (lvs *LogicalVolumeSelector) CollectVolumes( // The rebalancer only ever acts on PV/PVC-managed volumes. Resolve the set of // simplyblock CSI-managed volume UUIDs up front and restrict collection to them - // so backend-only volumes (e.g. benchmark probes) never enter the candidate pool. + // so backend-only volumes (e.g., benchmark probes) never enter the candidate pool. managed, err := lvs.BuildCSIManagedVolumes(ctx, input.ClusterUUID) if err != nil { return nil, fmt.Errorf("build CSI-managed volume set: %w", err) @@ -293,7 +293,7 @@ type managedVolume struct { // BuildCSIManagedVolumes returns the simplyblock CSI-managed volumes in the given // cluster, derived from the PersistentVolumes. The rebalancer only ever acts on -// PV/PVC-managed volumes; backend-only volumes (e.g. the per-node benchmark probes +// PV/PVC-managed volumes; backend-only volumes (e.g., the per-node benchmark probes // "simplyblock-rebalancer-", which have no PV) are therefore never // candidates. Pass an empty clusterUUID to include all clusters. // diff --git a/operator/internal/autoplacement/rebalancer.go b/operator/internal/autoplacement/rebalancer.go index 716b463f2..5f2272ebe 100644 --- a/operator/internal/autoplacement/rebalancer.go +++ b/operator/internal/autoplacement/rebalancer.go @@ -17,7 +17,7 @@ type MigrationCandidate struct { TargetClusterUUID string // TargetNodeUUID is the chosen migration destination node. TargetNodeUUID string - // Volume is the volume to migrate, including its pool association and IO metrics. + // Volume is the volume to migrate, including its pool association and I/O metrics. Volume VolumePlacement } @@ -153,7 +153,7 @@ func (rb *Rebalancer) SelectMigrations( } // hotNodesAllPinned reports whether any hot node hosts at least one volume and -// every volume it hosts is pinned — i.e. the node is hot but rebalancing is +// every volume it hosts is pinned — i.e., the node is hot but rebalancing is // blocked purely by pin policy. volumesByNode contains all volumes (pinning is // applied downstream), so it is the correct set to test against pinned. func hotNodesAllPinned(hotNodes []string, volumesByNode map[string][]VolumePlacement, pinned map[string]bool) bool { diff --git a/operator/internal/autoplacement/storage_node_selector.go b/operator/internal/autoplacement/storage_node_selector.go index d41a36ccb..4664dfe5a 100644 --- a/operator/internal/autoplacement/storage_node_selector.go +++ b/operator/internal/autoplacement/storage_node_selector.go @@ -11,7 +11,7 @@ import ( // StorageNodeSelectorInput groups the storage nodes belonging to a single Kubernetes // namespace. Multiple inputs can be passed to SelectStorageNodes when nodes span -// more than one namespace (e.g. multi-tenant deployments). +// more than one namespace (e.g., multi-tenant deployments). type StorageNodeSelectorInput struct { // Namespace is the Kubernetes namespace that owns these storage nodes. // Used to scope StorageNode CR lookups when reading baseline latency. @@ -102,7 +102,7 @@ type nodeRef struct { // deviation exceeds cfg.ImbalanceThreshold. Each hot node is paired with the coolest // eligible target that is at least cfg.MinHotColdDifferencePct percentage points cooler; // when no such target exists the hot node produces no pair (migrating between -// near-equally-loaded nodes yields no benefit). +// near-equally loaded nodes yields no benefit). // // Selection runs over a flat pool of all nodes regardless of cluster; the source/target // cluster relationship is decided by isMigrationTargetEligible (intra-cluster only @@ -137,7 +137,7 @@ func (sns *StorageNodeSelector) SelectStorageNodes( nodes = append(nodes, nodeRef{ClusterUUID: clusterUUID, NodeUUID: nodeUUID, DeviationPct: dev}) } - // Pair every hot node with the coolest eligible, sufficiently-cooler target. + // Pair every hot node with the coolest eligible, sufficiently cooler target. pairs := make([]NodeMigrationPair, 0, len(nodes)) for _, src := range nodes { if src.DeviationPct < cfg.ImbalanceThreshold { @@ -162,7 +162,7 @@ func (sns *StorageNodeSelector) SelectStorageNodes( // (migration-target selection), there is no source node to compare against and no // MinHotColdDifferencePct gate: placement always wants the single best candidate, however // small its lead over the second-best. A node absent from the current Prometheus reading -// (e.g. freshly onboarded, not yet scraped) is treated as deviation 0 — the best possible +// (e.g., freshly onboarded, not yet scraped) is treated as deviation 0 — the best possible // score — rather than excluded, so it isn't systematically avoided. func (sns *StorageNodeSelector) SelectBestNode( ctx context.Context, @@ -219,7 +219,7 @@ func pickColdTarget(src nodeRef, pool []nodeRef, cfg RebalancingConfig) (nodeRef // isMigrationTargetEligible reports whether cand may receive a volume migrated from src. // Migration is intra-cluster only today, so the target must be in the source's cluster. -// Cross-cluster migration is a planned follow-up: relaxing this predicate (e.g. gated by +// Cross-cluster migration is a planned follow-up: relaxing this predicate (e.g., gated by // a future cfg flag) is the single change needed here to allow cross-cluster targets. func isMigrationTargetEligible(src, cand nodeRef, _ RebalancingConfig) bool { return cand.ClusterUUID == src.ClusterUUID @@ -341,7 +341,7 @@ func deviationStats( minDev float64 hottest string // nodeUUID with highest deviation coolest string // nodeUUID with lowest deviation - first bool // sentinel to initialise min/max on first sample + first bool // sentinel to initialize min/max on first sample } acc := make(map[string]*accumulator) diff --git a/operator/internal/autoplacement/types.go b/operator/internal/autoplacement/types.go index a036cb77b..76d8b2769 100644 --- a/operator/internal/autoplacement/types.go +++ b/operator/internal/autoplacement/types.go @@ -1,6 +1,6 @@ package autoplacement -// NodeConfig is one element of the JSON array the operator writes per k8s hostname to the +// NodeConfig is one element of the JSON array the operator writes per K8s hostname to the // simplyblock-rebalancer ConfigMap. The rebalancer probe (--config) iterates the array to // benchmark every NUMA node on its host independently. type NodeConfig struct { diff --git a/operator/internal/autoplacement/utils.go b/operator/internal/autoplacement/utils.go index 8aa22dc09..fe13d7d80 100644 --- a/operator/internal/autoplacement/utils.go +++ b/operator/internal/autoplacement/utils.go @@ -10,7 +10,7 @@ import ( const ( // DefaultEvaluationInterval is how often the rebalancer evaluates load when the spec - // does not override it. Exported so callers can fall back to it (e.g. for requeue + // does not override it. Exported so callers can fall back to it (e.g., for requeue // timing) before a RebalancingConfig has been resolved. DefaultEvaluationInterval = 60 * time.Second @@ -18,7 +18,7 @@ const ( defaultImbalanceThresholdPct = 80 // defaultMinHotColdDifferencePct is the minimum latency-deviation gap (in // percentage points) a target node must have below the hot source before a - // migration is worthwhile — prevents shuffling load between near-equally-loaded + // migration is worthwhile — prevents shuffling load between near-equally loaded // nodes. defaultMinHotColdDifferencePct = 20 defaultCoolDownSeconds = 600 @@ -42,7 +42,7 @@ const ( // it must match the cadence at which the probe sidecar publishes latency samples. defaultBaselineStep = 5 * time.Minute - // migrationBudgetFraction is the fraction of the source node's total volume IO score + // migrationBudgetFraction is the fraction of the source node's total volume I/O score // that may be migrated in a single evaluation cycle. migrationBudgetFraction = 0.10 diff --git a/operator/internal/controller/backuppolicy_controller.go b/operator/internal/controller/backuppolicy_controller.go index d7858bb3a..e8ad304d4 100644 --- a/operator/internal/controller/backuppolicy_controller.go +++ b/operator/internal/controller/backuppolicy_controller.go @@ -53,7 +53,7 @@ const ( // simplyblock.io/backup-policy: // // deprecatedPvcBackupPolicyAnnotation is the legacy spelling of the same - // annotation. It is still honoured for backwards compatibility, but + // annotation. It is still honored for backward compatibility, but // pvcBackupPolicyAnnotation wins when both are present. pvcBackupPolicyAnnotation = "simplyblock.io/backup-policy" deprecatedPvcBackupPolicyAnnotation = "simplybk/backup-policy" @@ -70,7 +70,7 @@ const ( ) // schedulePattern matches a space-separated list of interval,keep_count pairs -// (e.g. "15m,4 60m,11 24h,7"). Supported interval units: m, h, d, w. Pairs are +// (e.g., "15m,4 60m,11 24h,7"). Supported interval units: m, h, d, w. Pairs are // separated by literal spaces only (not the \s class) so a tab/newline can't // be smuggled in as a separator. var schedulePattern = regexp.MustCompile(`^(\d+[mhdw],\d+)( +\d+[mhdw],\d+)*$`) @@ -289,7 +289,7 @@ func (r *BackupPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { // backupPolicyNameFromAnnotations returns the BackupPolicy name referenced by // the given PVC annotations, or "" when neither annotation is set. Both the -// current and the deprecated annotation are honoured; the current spelling +// current and the deprecated annotation are honored; the current spelling // wins when both carry a non-empty value. func backupPolicyNameFromAnnotations(annotations map[string]string) string { if name := annotations[pvcBackupPolicyAnnotation]; name != "" { @@ -343,7 +343,7 @@ func (r *BackupPolicyReconciler) handleDeletion( if clusterUUID != "" && policyID != "" { apiClient := r.apiClient() - // Detach from every currently-attached lvol. + // Detach from every currently attached lvol. for _, a := range policyCR.Status.AttachedLvols { if err := r.detachPolicy(ctx, apiClient, clusterUUID, policyID, a.LvolID); err != nil { log.Error(err, "Failed to detach policy from lvol during deletion", @@ -589,7 +589,7 @@ func (r *BackupPolicyReconciler) computeDesiredAttachments( return desired, nil } -// resolvePVCLvolID extracts the Simplyblock lvol UUID from a PVC. +// resolvePVCLvolID extracts the simplyblock lvol UUID from a PVC. // It reads the PV volume handle and validates that the PVC belongs to the // expected cluster. The simplybk/lvol-id annotation may be used in place of // the handle, but only when it agrees with the handle — a mismatch is rejected @@ -661,7 +661,7 @@ func (r *BackupPolicyReconciler) apiClient() *webapi.Client { return webapi.NewClient() } -// policyBackendName returns the name used for the policy in the Simplyblock +// policyBackendName returns the name used for the policy in the simplyblock // backend. It is derived from the CR name alone, since the Kubernetes CR is // already namespace-scoped and users typically intend the policy name to be // human-readable. @@ -709,9 +709,9 @@ func attachmentKey(a simplyblockv1alpha1.AttachedLvol) string { // non-empty human-readable message when a field value is present but does not // match the expected format, and an empty string when the spec is valid. // -// The kubebuilder Pattern markers on the CRD provide admission-time enforcement, +// The Kubebuilder Pattern markers on the CRD provide admission-time enforcement, // but this runtime check is the last line of defense against malformed or -// injected values reaching the backend (e.g. clusters upgraded before the new +// injected values reaching the backend (e.g., clusters upgraded before the new // CRD schema was applied, or direct etcd writes). func validateBackupPolicySpec(spec simplyblockv1alpha1.BackupPolicySpec) string { if spec.Schedule != "" && !schedulePattern.MatchString(spec.Schedule) { diff --git a/operator/internal/controller/backuppolicy_controller_unit_test.go b/operator/internal/controller/backuppolicy_controller_unit_test.go index dd7961acf..020b2e8e6 100644 --- a/operator/internal/controller/backuppolicy_controller_unit_test.go +++ b/operator/internal/controller/backuppolicy_controller_unit_test.go @@ -455,7 +455,7 @@ func TestRemoveAttachment(t *testing.T) { } // Removing by PVC key alone must not drop an entry that shares the name but has -// a different lvolID (e.g. after a rebind, the new attachment must survive). +// a different lvolID (e.g., after a rebind, the new attachment must survive). func TestRemoveAttachment_DoesNotMatchDifferentLvol(t *testing.T) { slice := []simplyblockv1alpha1.AttachedLvol{ lvol("ns", "pvc1", "lvol-new"), diff --git a/operator/internal/controller/benchmark_provisioner.go b/operator/internal/controller/benchmark_provisioner.go index 331d72618..71c003ee3 100644 --- a/operator/internal/controller/benchmark_provisioner.go +++ b/operator/internal/controller/benchmark_provisioner.go @@ -67,7 +67,7 @@ func (*AutomaticBenchmarkProvisioner) BenchmarkNQN(clusterNQN, volumeUUID string return fmt.Sprintf("%s:lvol:%s", clusterNQN, volumeUUID) } -// WebAPIBenchmarkProvisioner creates benchmark resources via the SimplyBlock REST API. +// WebAPIBenchmarkProvisioner creates benchmark resources via the simplyblock REST API. // Intended for test environments where the storage pool and benchmark volumes are not // automatically provisioned during cluster setup. // diff --git a/operator/internal/controller/nodedrain_controller.go b/operator/internal/controller/nodedrain_controller.go index aef5ca062..062b52ee4 100644 --- a/operator/internal/controller/nodedrain_controller.go +++ b/operator/internal/controller/nodedrain_controller.go @@ -68,7 +68,7 @@ const ( // The controller implements a requeue-based state machine tracked in // StorageNodeSet.status.drainCoordination. The full per-node flow is: // -// 1. Detect – k8s node cordoned (spec.unschedulable=true); wait for drain slot +// 1. Detect – K8s node cordoned (spec.unschedulable=true); wait for drain slot // 2. Shutdown – label storage pod, create blocking PDB (maxUnavailable=0), // call simplyblock shutdown API // 3. Confirm – poll until backend node status == nodeStatusOffline @@ -165,7 +165,7 @@ func (r *NodeDrainCoordinatorReconciler) Reconcile(ctx context.Context, req ctrl // more than one domain at once can no longer be assumed safe — see // fdDrainGate's chunksPerDomain calculation. 0 means "unknown" (scheme not // yet reported); fdDrainGate currently clamps that to chunksPerDomain=1 (the - // same as a fully-disjoint layout) rather than treating it conservatively. + // same as a fully disjoint layout) rather than treating it conservatively. domainsNeededForFullDisjoint := 0 // npcs is the failure-domain risk budget fdDrainGate spends against — // see fdDrainGate for the full accounting rule. @@ -192,7 +192,7 @@ func (r *NodeDrainCoordinatorReconciler) Reconcile(ctx context.Context, req ctrl // backend. This protects SPDK/FDB/webappapi pods from being evicted by MCP // before the drain state machine fires, while avoiding blocking the kubelet // reboot that is required when a new node applies its KubeletConfig/MachineConfig - // for the first time (i.e. node add flow). + // for the first time (i.e., node add flow). for _, workerName := range snCR.Spec.WorkerNodes { // Skip nodes that are already in an active drain — their PDB lifecycle // is managed by the drain state machine (deleted after offline confirmed). @@ -297,7 +297,7 @@ func (r *NodeDrainCoordinatorReconciler) processWorker( return r.processUncordoned(ctx, snCR, workerName, state, apiClient, clusterUUID) } - // Node is cordoned: initialise state if first observation. + // Node is cordoned: initialize state if first observation. if state == nil { // Do not start drain coordination for a node that has never been online. // MCP cordons new nodes for the initial KubeletConfig/MachineConfig reboot @@ -458,7 +458,7 @@ func (r *NodeDrainCoordinatorReconciler) advanceStateMachine( // // See activeDrainWorkers/activeDrainDomainCounts. // -// Importantly, the storage pod is labelled and a blocking PDB (maxUnavailable=0) +// Importantly, the storage pod is labeled and a blocking PDB (maxUnavailable=0) // is created BEFORE the slot check, so that MCP/kubectl-drain cannot evict the // pod while this node is queued behind another drain in progress. func (r *NodeDrainCoordinatorReconciler) handleDetected( @@ -947,7 +947,7 @@ func (r *NodeDrainCoordinatorReconciler) labelStoragePod( continue } if pod.Labels[drainNodeLabelKey] == sanitizeLabelValue(nodeName) { - continue // already labelled + continue // already labeled } patch := client.MergeFrom(pod.DeepCopy()) if pod.Labels == nil { @@ -1068,9 +1068,9 @@ func (r *NodeDrainCoordinatorReconciler) cleanupManagerPDBIfStale(ctx context.Co } } -// SetupWithManager wires the controller to watch StorageNodeSet CRs, k8s Nodes, +// SetupWithManager wires the controller to watch StorageNodeSet CRs, K8s Nodes, // and the pods that labelStoragePod tracks. Watching pods ensures that when a -// tracked pod is recreated (e.g. after a crash) the reconcile fires immediately +// tracked pod is recreated (e.g., after a crash) the reconcile fires immediately // to re-apply the drain label, keeping PDB protection continuous. func (r *NodeDrainCoordinatorReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). @@ -1127,7 +1127,7 @@ func (r *NodeDrainCoordinatorReconciler) trackedPodToStorageNodeSetRequests( return requests } -// nodeToStorageNodeSetRequests maps a k8s Node event to the StorageNodeSet CR(s) +// nodeToStorageNodeSetRequests maps a K8s Node event to the StorageNodeSet CR(s) // that list the node in spec.workerNodes. func (r *NodeDrainCoordinatorReconciler) nodeToStorageNodeSetRequests( ctx context.Context, @@ -1441,7 +1441,7 @@ func findAllNodeUUIDs(snCR *simplyblockv1alpha1.StorageNodeSet, hostname string) return uuids } -// nextUUIDInList returns the element immediately after current in uuids, +// nextUUIDInList returns the element immediately after current in UUIDs, // or an empty string if current is the last element or not found. func nextUUIDInList(uuids []string, current string) string { for i, u := range uuids { diff --git a/operator/internal/controller/nodedrain_controller_unit_test.go b/operator/internal/controller/nodedrain_controller_unit_test.go index edfbb4140..23102acfb 100644 --- a/operator/internal/controller/nodedrain_controller_unit_test.go +++ b/operator/internal/controller/nodedrain_controller_unit_test.go @@ -1040,7 +1040,7 @@ func TestHandleRestartCalledCompletesWhenNotRebalancing(t *testing.T) { // controller would silently revert to the pre-reconcile state. // // Setup: one worker already in DrainPhaseComplete (no backend HTTP calls -// needed) so processWorker is a pure no-op. The interesting behaviour is in +// needed) so processWorker is a pure no-op. The interesting behavior is in // the final patch: the interceptor returns 409 on the first attempt and // succeeds on the second, verifying that RetryOnConflict re-reads and retries // rather than logging and returning the 5-second requeue. diff --git a/operator/internal/controller/persistentvolumeclaim_controller.go b/operator/internal/controller/persistentvolumeclaim_controller.go index d7a457c64..51f50964d 100644 --- a/operator/internal/controller/persistentvolumeclaim_controller.go +++ b/operator/internal/controller/persistentvolumeclaim_controller.go @@ -58,7 +58,7 @@ const ( // pinned-volume value differs from the AnnotationPinnedVolumeApplied marker it // writes after acting, so its own annotation writes do not re-trigger a // migration. A validating admission webhook rejects an unknown storage node at -// write time; the re-validation here is a defense-in-depth backstop (e.g. for +// write time; the re-validation here is a defense-in-depth backstop (e.g., for // values that predate the webhook, or a node removed after the pin was set). type PersistentVolumeClaimReconciler struct { client.Client @@ -157,7 +157,7 @@ func (r *PersistentVolumeClaimReconciler) Reconcile( } // Serialize per PV: wait for any in-flight pin migration to finish before - // requesting another (e.g. when the target changed while one was running). + // requesting another (e.g., when the target changed while one was running). active, err := r.hasActiveMigration(ctx, cluster.Namespace, pv.Name) if err != nil { return ctrl.Result{}, err diff --git a/operator/internal/controller/replicationpolicy_controller.go b/operator/internal/controller/replicationpolicy_controller.go index d355b8f56..4876eb40b 100644 --- a/operator/internal/controller/replicationpolicy_controller.go +++ b/operator/internal/controller/replicationpolicy_controller.go @@ -251,7 +251,7 @@ func (r *ReplicationPolicyReconciler) ensureBackendPolicy( return created.ID, nil } -// parseDurationToMinutes converts a Go duration string (e.g. "5m", "1h") to +// parseDurationToMinutes converts a Go duration string (e.g., "5m", "1h") to // whole minutes, clamped to a minimum of 1. func parseDurationToMinutes(s string) (int, error) { d, err := time.ParseDuration(s) diff --git a/operator/internal/controller/replicationslot_controller.go b/operator/internal/controller/replicationslot_controller.go index 6bf28ddd7..c611c90c2 100644 --- a/operator/internal/controller/replicationslot_controller.go +++ b/operator/internal/controller/replicationslot_controller.go @@ -94,7 +94,7 @@ type ReplicationSlotReconciler struct { Scheme *runtime.Scheme Recorder events.EventRecorder // apiReader is an uncached reader for consumer-pod lookups; a stale cache - // could miss a running pod and skip preconnect on an actively-used volume. + // could miss a running pod and skip preconnect on an actively used volume. apiReader client.Reader } @@ -280,14 +280,14 @@ func (r *ReplicationSlotReconciler) reconcileReplicating( changed = true case backendStateFailedOver: if !status.IsSource { - // Failback completed — IO returned to this volume; treat as replicating/source. + // Failback completed — I/O returned to this volume; treat as replicating/source. slot.Status.State = string(simplyblockv1alpha1.ReplicationSlotStateReplicating) slot.Status.Direction = string(simplyblockv1alpha1.ReplicationSlotDirectionSource) slot.Status.Message = replMsgSlotReplicating } else if slot.Status.Direction == string(simplyblockv1alpha1.ReplicationSlotDirectionSource) { // Slot is already on the source side — the source cluster is reporting a // stale failed_over record from before the failback completed. The failback - // ANA flip has already moved IO back here; don't revert the slot. + // ANA flip has already moved I/O back here; don't revert the slot. log.Info("Source cluster reports stale failed_over; slot already replicating/source — ignoring", "slot", slot.Name) // The cutover_done window on the source cluster is short (~10 s, before the @@ -327,7 +327,7 @@ func (r *ReplicationSlotReconciler) reconcileReplicating( case backendStateCutoverDone: // The 30 s cutover_pending window already closed before we polled. // Attempt a late preconnect so the CSI node gets target NVMe paths even - // after the ANA flip — this limits IO downtime to ctrl_loss_tmo instead + // after the ANA flip — this limits I/O downtime to ctrl_loss_tmo instead // of indefinite path loss. r.reconcilePreconnect(ctx, slot, apiClient, clusterID, volumeID, clusterID, poolID, volumeID) } @@ -376,7 +376,7 @@ func (r *ReplicationSlotReconciler) reconcileSyncStatus( case backendStateCutoverDone, backendStateFailedOver: if !status.IsSource { // This volume is the TARGET of a completed failback: it received the - // replicated data and the ANA flip moved IO back here. From the slot's + // replicated data and the ANA flip moved I/O back here. From the slot's // perspective the volume is live on the source cluster again. slot.Status.State = string(simplyblockv1alpha1.ReplicationSlotStateReplicating) slot.Status.Direction = string(simplyblockv1alpha1.ReplicationSlotDirectionSource) @@ -557,7 +557,7 @@ func (r *ReplicationSlotReconciler) reconcileCutoverPending( // placed in cutover_pending K8s state — spanning the entire shrink phase — // until _prepare_cutover creates the new cutover_pending record. Treating // failed_over as "ANA flip done" here would transition the slot to - // replicating/source while IO is still on the target cluster and shrink rounds + // replicating/source while I/O is still on the target cluster and shrink rounds // are still running. Return and wait until cutover_pending or cutover_done // actually appears. if proceedClusterID != clusterID && status != nil && status.State == backendStateFailedOver { @@ -566,7 +566,7 @@ func (r *ReplicationSlotReconciler) reconcileCutoverPending( // Backend advanced past cutover_pending before the preconnect Job could be // created (safety timer fired, or we missed the window entirely). Attempt a // late preconnect so the CSI node gets source-cluster NVMe paths even after - // the ANA flip — limits IO downtime to ctrl_loss_tmo instead of indefinite. + // the ANA flip — limits I/O downtime to ctrl_loss_tmo instead of indefinite. if status != nil && status.State == backendStateCutoverDone { r.reconcilePreconnect(ctx, slot, apiClient, clusterID, volumeID, clusterID, poolID, volumeID) } @@ -600,7 +600,7 @@ func (r *ReplicationSlotReconciler) reconcileCutoverPending( } // Job not found — either the signal was already sent and the job was deleted, - // or the job was never created yet. If we already signalled, don't create a new + // or the job was never created yet. If we already signaled, don't create a new // job: just wait for the backend to advance (a new job can't help at this point). if slot.Annotations[annotCutoverProceedSignaled] == annotCutoverProceedSignaledValue { return ctrl.Result{RequeueAfter: 5 * time.Second}, nil @@ -668,11 +668,11 @@ func (r *ReplicationSlotReconciler) reconcileCutoverPending( // signals the backend to proceed once the Job has completed (pass or fail). // // We always re-check the job state even when the annotation is set, so that a -// previously-failed callCutoverProceed is retried rather than silently dropped: +// previously failed callCutoverProceed is retried rather than silently dropped: // the backend waits indefinitely for the proceed signal, so a missed call leaves // the slot permanently stuck. // -// The annotation is written before the API call to serialise concurrent reconciles: +// The annotation is written before the API call to serialize concurrent reconciles: // the one whose patch wins becomes the sole caller of callCutoverProceed; the // loser's patch fails with a conflict and falls back to the 5 s wait. func (r *ReplicationSlotReconciler) handleExistingPreconnectJob( @@ -711,7 +711,7 @@ func (r *ReplicationSlotReconciler) handleExistingPreconnectJob( // reconcilePreconnect creates the preconnect Job that connects target NVMe paths // on the consumer node. Called both during cutover_pending and as a fallback when // the backend has already flipped to cutover_done (the "late" case — ANA already -// happened, limiting IO downtime to ctrl_loss_tmo instead of indefinitely). +// happened, limiting I/O downtime to ctrl_loss_tmo instead of indefinitely). // Errors are logged but not returned — the caller still advances slot state. // // clusterID/volumeID identify the slot's own volume (job naming, consumer node @@ -729,7 +729,7 @@ func (r *ReplicationSlotReconciler) reconcilePreconnect( log := logf.FromContext(ctx) jobName := replSlotPreconnectJobName(volumeID) - // Skip if the Job was already created (e.g. by a concurrent reconcile). + // Skip if the Job was already created (e.g., by a concurrent reconcile). var existing batchv1.Job if err := r.Get(ctx, types.NamespacedName{Namespace: slot.Namespace, Name: jobName}, &existing); err == nil { return @@ -824,7 +824,7 @@ func (r *ReplicationSlotReconciler) callCutoverProceed( if err != nil { return err } - // 200/204 = signalled; 404 = no cutover_pending record (already advanced). + // 200/204 = signaled; 404 = no cutover_pending record (already advanced). // The Flask backend may return 200 OK rather than 204 No Content. if status == http.StatusOK || status == http.StatusNoContent || status == http.StatusNotFound { return nil @@ -881,7 +881,7 @@ func (r *ReplicationSlotReconciler) applyAdvancedBackendState( // applyAdvancedBackendStateForFailback is the failback-aware variant called from // reconcileCutoverPending when the slot was initiated by a failback ReplicationOps. // We poll the TARGET cluster (where the task runs), so cutover_done/is_source=true -// means the ANA flip moved IO back to the original source — the slot is done and +// means the ANA flip moved I/O back to the original source — the slot is done and // should return to replicating/source. For non-failback paths (isFailback=false) // it delegates to the regular applyAdvancedBackendState. func (r *ReplicationSlotReconciler) applyAdvancedBackendStateForFailback( @@ -902,7 +902,7 @@ func (r *ReplicationSlotReconciler) applyAdvancedBackendStateForFailback( switch status.State { case backendStateCutoverDone: - // Failback ANA flip confirmed — IO is back on the original source cluster. + // Failback ANA flip confirmed — I/O is back on the original source cluster. // Clear the failback annotations first (metadata patch), then update status. // Doing them in one r.Status().Patch() would silently discard the annotation // changes because the status subresource ignores metadata writes. @@ -932,7 +932,7 @@ func (r *ReplicationSlotReconciler) applyAdvancedBackendStateForFailback( "slot", slot.Name) return ctrl.Result{RequeueAfter: 5 * time.Second}, nil default: - // Still in an intermediate state (e.g. backend not yet cutover_done); wait. + // Still in an intermediate state (e.g., backend not yet cutover_done); wait. log.Info("Failback: unexpected backend state while waiting for cutover_done", "slot", slot.Name, "backendState", status.State) return ctrl.Result{RequeueAfter: 5 * time.Second}, nil diff --git a/operator/internal/controller/replicationslot_controller_unit_test.go b/operator/internal/controller/replicationslot_controller_unit_test.go index 1db8fd674..67ade00c7 100644 --- a/operator/internal/controller/replicationslot_controller_unit_test.go +++ b/operator/internal/controller/replicationslot_controller_unit_test.go @@ -826,7 +826,7 @@ func TestSlot_CutoverPending_JobFailed_SignalsAnyway(t *testing.T) { } } -// ---------- reconcileCutoverPending: job still running → waits without signalling ---------- +// ---------- reconcileCutoverPending: job still running → waits without signaling ---------- func TestSlot_CutoverPending_JobStillRunning_Waits(t *testing.T) { pol := readyReplicationPolicy() @@ -870,7 +870,7 @@ func TestSlot_CutoverPending_JobStillRunning_Waits(t *testing.T) { } } -// ---------- reconcileCutoverPending: previously-failed proceed retried when job completes again ---------- +// ---------- reconcileCutoverPending: previously failed proceed retried when job completes again ---------- func TestSlot_CutoverPending_ProceedRetried_WhenJobTerminates(t *testing.T) { pol := readyReplicationPolicy() @@ -966,7 +966,7 @@ func TestSlot_ReconcileReplicating_DetectsCutoverDone_SetsDirectionTarget(t *tes switch { case req.Method == http.MethodGet && strings.HasSuffix(path, "/replication"): // is_source=true: this slot is the migration source; cutover_done means - // IO has moved to the target cluster (forward migration path). + // I/O has moved to the target cluster (forward migration path). _ = json.NewEncoder(w).Encode(replVolumeReplicationStatus{ State: backendStateCutoverDone, TargetNQN: "nqn.target", IsSource: true, }) diff --git a/operator/internal/controller/simplyblockstoragenodeset_controller.go b/operator/internal/controller/simplyblockstoragenodeset_controller.go index ab1945818..20e2ef5ec 100644 --- a/operator/internal/controller/simplyblockstoragenodeset_controller.go +++ b/operator/internal/controller/simplyblockstoragenodeset_controller.go @@ -124,7 +124,7 @@ var ( // +kubebuilder:rbac:groups="",resources=persistentvolumes,verbs=get;list;watch // +kubebuilder:rbac:groups="",resources=persistentvolumeclaims,verbs=get;list;watch;update;patch -// Reconcile is part of the main kubernetes reconciliation loop which aims to +// Reconcile is part of the main Kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. // TODO(user): Modify the Reconcile function to compare the state specified by // the StorageNodeSet object against the actual cluster state, and then @@ -441,7 +441,7 @@ func (r *StorageNodeSetReconciler) ensureFinalizer( return true, r.Update(ctx, snCR) } -// storageNodeUUIDLabelPrefix marks a worker Node with the SimplyBlock storage-node +// storageNodeUUIDLabelPrefix marks a worker Node with the simplyblock storage-node // instance(s) co-located on it. The label KEY is "." // and must stay stable for the Node's lifetime — Kubernetes' external-provisioner // caches the *set* of topology keys in the CSINode object at node-plugin @@ -451,7 +451,7 @@ func (r *StorageNodeSetReconciler) ensureFinalizer( // when a node is replaced) since values are always read fresh — only the key must // never depend on anything that can change post-registration. Cluster-scoping the // key (not just the value) also stops a worker hosting instances from more than one -// SimplyBlock cluster from having one cluster's slot collide with another's. +// simplyblock cluster from having one cluster's slot collide with another's. // Consumed by the CSI node plugin (csi-driver/internal/csi/node) to advertise // CSI topology, and by the CSI controller (createVolume) to co-locate a new volume's // primary with whichever worker the consuming Pod is scheduled to. Keep this literal @@ -474,7 +474,7 @@ func labelWorkerNodes( extraWorkers ...string, ) error { // Collect all workers: spec.workerNodes, any explicitly requested extras - // (e.g. a migration target not yet in the spec), plus any manually created + // (e.g., a migration target not yet in the spec), plus any manually created // StorageNode CRs that reference this StorageNodeSet but are not in spec.workerNodes. workers := make(map[string]struct{}, len(sn.Spec.WorkerNodes)+len(extraWorkers)) for _, w := range sn.Spec.WorkerNodes { @@ -556,7 +556,7 @@ func labelWorkerNodes( slot := strings.TrimPrefix(k, storageNodeUUIDLabelPrefix) sep := strings.LastIndex(slot, ".") if sep < 0 || slot[:sep] != clusterUUID { - // Slot belongs to a different SimplyBlock cluster (a worker can host + // Slot belongs to a different simplyblock cluster (a worker can host // storage-node instances from more than one) or is malformed — leave // it untouched; this reconcile only owns clusterUUID's slots. continue @@ -1086,7 +1086,7 @@ func (r *StorageNodeSetReconciler) reconcileWorkerNodes( return ctrl.Result{}, err } // Only count the slot if the POST was genuinely sent (PendingNodeAdds - // was set). A transient failure (e.g. checkNodeInfoReachable) clears + // was set). A transient failure (e.g., checkNodeInfoReachable) clears // PendingNodeAdds immediately, so the slot should not be consumed. if !alreadyInFlight && workerIsInFlight(snCR, nodeName) { availableSlots-- diff --git a/operator/internal/controller/simplyblockstoragenodeset_controller_unit_test.go b/operator/internal/controller/simplyblockstoragenodeset_controller_unit_test.go index ca3ed8a17..1a40b1a9e 100644 --- a/operator/internal/controller/simplyblockstoragenodeset_controller_unit_test.go +++ b/operator/internal/controller/simplyblockstoragenodeset_controller_unit_test.go @@ -1500,7 +1500,7 @@ func newStorageNodeSetStateTestReconciler( Image: "test-image:latest", }, } - // Simulate kubebuilder defaults that the API server would apply. + // Simulate Kubebuilder defaults that the API server would apply. for _, obj := range objects { if sn, ok := obj.(*simplyblockv1alpha1.StorageNodeSet); ok && sn.Spec.MaxParallelNodeAdds == nil { v := int32(1) @@ -2045,7 +2045,7 @@ func TestReconcileSpdkProxyEndpointSlices_TransientNotReadyKeepsSlice(t *testing t.Fatalf("expected node-a's endpoint to remain, got %#v", slices.Items[0].Endpoints) } - // Now the pod is genuinely gone -- the slice must be deleted as before. + // Now, the pod is genuinely gone -- the slice must be deleted as before. if err := r.Delete(ctx, pod); err != nil { t.Fatalf("delete pod: %v", err) } @@ -2757,7 +2757,7 @@ func TestMaybeActivateClusterWaitsForFailureDomainReadiness(t *testing.T) { } func TestMaybeActivateClusterProceedsOnceFailureDomainsAreReady(t *testing.T) { - // 4 online/healthy nodes across 4 distinct, equally-sized failure domains + // 4 online/healthy nodes across 4 distinct, equally sized failure domains // for npcs=2 -- satisfies npcs+2 = 4, so the gate must let this through // to the real activation call. cluster, nodeSet := newActivationTestClusterAndNodeSet( diff --git a/operator/internal/controller/simplyblockstoragenodeset_drain.go b/operator/internal/controller/simplyblockstoragenodeset_drain.go index b65bb2240..9fc2e6583 100644 --- a/operator/internal/controller/simplyblockstoragenodeset_drain.go +++ b/operator/internal/controller/simplyblockstoragenodeset_drain.go @@ -54,7 +54,7 @@ const ( ) // fetchPoolVolumes fetches all pools and returns (pools, nodeVolumes, err). -// Callers that need both the pool list (e.g. for cleanup) and the node volumes +// Callers that need both the pool list (e.g., for cleanup) and the node volumes // should call this once and reuse the returned pools, avoiding a second // GetStoragePools round-trip within the same reconcile. func fetchPoolVolumes( @@ -88,7 +88,7 @@ func fetchPoolVolumes( } // listNodeVolumes returns volumes on nodeUUID. Use fetchPoolVolumes when the -// pool list is also needed (e.g. drainVerify cleanup) to avoid a double fetch. +// pool list is also needed (e.g., drainVerify cleanup) to avoid a double fetch. func listNodeVolumes( ctx context.Context, apiClient *webapi.Client, @@ -109,7 +109,7 @@ func listNodeVolumes( // matchVolumesToPVs classifies each backend volume into pvManaged, pinned, or // unmanaged buckets. System volumes matching filterRegex are skipped entirely. // -// Note: if the PVC fetch for a PV-backed volume fails (e.g. API server +// Note: if the PVC fetch for a PV-backed volume fails (e.g., API server // temporarily unavailable), that volume is conservatively placed in the // unmanaged bucket. This will block drain with an UnmanagedVolumeBlocking // event until the next reconcile succeeds. It is a transient false-positive, diff --git a/operator/internal/controller/simplyblockstoragenodeset_drain_unit_test.go b/operator/internal/controller/simplyblockstoragenodeset_drain_unit_test.go index 447ef47d8..a65b460b6 100644 --- a/operator/internal/controller/simplyblockstoragenodeset_drain_unit_test.go +++ b/operator/internal/controller/simplyblockstoragenodeset_drain_unit_test.go @@ -260,7 +260,7 @@ func TestMatchVolumesToPVs_OnlySystemVolumes(t *testing.T) { func TestDrainMigrationNameNoCollisionOnLongPVNames(t *testing.T) { // Two PV names that share a 60+ char common prefix must produce distinct CR - // names after sanitisation and truncation (collision guard via FNV suffix). + // names after sanitization and truncation (collision guard via FNV suffix). longBase := "pvc-" + strings.Repeat("a", 55) // 59 chars — produces a 63-char name when prefixed pv1 := longBase + "1" pv2 := longBase + "2" diff --git a/operator/internal/controller/simplyblockstoragepool_controller.go b/operator/internal/controller/simplyblockstoragepool_controller.go index 141bbf239..0984a407a 100644 --- a/operator/internal/controller/simplyblockstoragepool_controller.go +++ b/operator/internal/controller/simplyblockstoragepool_controller.go @@ -239,13 +239,13 @@ func (r *StoragePoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) // // --- Handle update --- // updateParams := utils.PoolUpdateParams{ - // Name: storagePoolCR.Name, + // Name: storagePoolCR.Name, // PoolMax: utils.IntPtrOrDefault(storagePoolCR.Spec.RWLimit, 0), // // VolumeMaxSize: storagePoolCR.Spec.CapacityLimitIntPtr(), // MaxRwIOPS: utils.IntPtrOrDefault(storagePoolCR.Spec.QoSIOPSLimit, 0), - // MaxRwMB: utils.IntPtrOrDefault(storagePoolCR.Spec.RWLimit, 0), - // MaxRMB: utils.IntPtrOrDefault(storagePoolCR.Spec.RLimit, 0), - // MaxWMB: utils.IntPtrOrDefault(storagePoolCR.Spec.WLimit, 0), + // MaxRwMB: utils.IntPtrOrDefault(storagePoolCR.Spec.RWLimit, 0), + // MaxRMB: utils.IntPtrOrDefault(storagePoolCR.Spec.RLimit, 0), + // MaxWMB: utils.IntPtrOrDefault(storagePoolCR.Spec.WLimit, 0), // } // endpoint := fmt.Sprintf("/api/v2/clusters/%s/storage-pools/%s", clusterUUID, storagePoolCR.Status.UUID) diff --git a/operator/internal/controller/simplyblocktask_controller.go b/operator/internal/controller/simplyblocktask_controller.go index 5e5dc4379..75313a159 100644 --- a/operator/internal/controller/simplyblocktask_controller.go +++ b/operator/internal/controller/simplyblocktask_controller.go @@ -57,7 +57,7 @@ type ClusterTaskAPIResponse []struct { // +kubebuilder:rbac:groups=storage.simplyblock.io,resources=storageclusters,verbs=get;list;watch // +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch -// Reconcile is part of the main kubernetes reconciliation loop which aims to +// Reconcile is part of the main Kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. // TODO(user): Modify the Reconcile function to compare the state specified by // the Task object against the actual cluster state, and then diff --git a/operator/internal/controller/storagebackup_controller.go b/operator/internal/controller/storagebackup_controller.go index ea270ab43..5ce9054a0 100644 --- a/operator/internal/controller/storagebackup_controller.go +++ b/operator/internal/controller/storagebackup_controller.go @@ -70,7 +70,7 @@ const ( eventReasonBackupClusterLookupError = "BackupClusterLookupError" // eventReasonBackupSourceResolutionError is emitted when the PVC/PV source - // cannot be resolved (e.g. PVC not found, not bound, or missing lvol metadata). + // cannot be resolved (e.g., PVC not found, not bound, or missing lvol metadata). eventReasonBackupSourceResolutionError = "BackupSourceResolutionError" // eventReasonBackupPoolLookupError is emitted when the storage pool UUID @@ -205,7 +205,7 @@ func (r *StorageBackupReconciler) Reconcile(ctx context.Context, req ctrl.Reques } // Imported backups are managed externally: poll status directly by // BackupID/ClusterUUID instead of resolving a PVC-based source, since the - // originating PVC/lvol may no longer exist (e.g. after the source pool was + // originating PVC/lvol may no longer exist (e.g., after the source pool was // deleted and recreated) even though the backend backup and its data // remain valid and restorable. Calling resolveBackupSource here would // hard-fail on the missing PVC and overwrite the imported status back to diff --git a/operator/internal/controller/storagebackupsync_controller.go b/operator/internal/controller/storagebackupsync_controller.go index 3d319249f..4e6839a92 100644 --- a/operator/internal/controller/storagebackupsync_controller.go +++ b/operator/internal/controller/storagebackupsync_controller.go @@ -157,7 +157,7 @@ func (r *StorageBackupSyncReconciler) Reconcile(ctx context.Context, req ctrl.Re } // A PVC match is best-effort only: the originating PVC/lvol may no longer - // exist (e.g. the source pool was deleted and recreated), but the backend + // exist (e.g., the source pool was deleted and recreated), but the backend // backup and its data remain valid and restorable, so it must still be // imported as a CR — otherwise a BackupRestore referencing it can never // find it and gets stuck at Pending/NotFound forever. @@ -246,7 +246,7 @@ func (r *StorageBackupSyncReconciler) SetupWithManager(mgr ctrl.Manager) error { } // buildLvolToPVCMap scans all bound PVCs in the given namespace and returns a -// map from Simplyblock lvol UUID to (pvcName, pvcNamespace). +// map from simplyblock lvol UUID to (pvcName, pvcNamespace). // Only PVCs whose CSI volume handle belongs to the expected cluster are included. func (r *StorageBackupSyncReconciler) buildLvolToPVCMap( ctx context.Context, diff --git a/operator/internal/controller/storagebackupsync_controller_unit_test.go b/operator/internal/controller/storagebackupsync_controller_unit_test.go index a9f256ea5..97a9b56b8 100644 --- a/operator/internal/controller/storagebackupsync_controller_unit_test.go +++ b/operator/internal/controller/storagebackupsync_controller_unit_test.go @@ -176,7 +176,7 @@ func TestStorageBackupSyncImportsBackup(t *testing.T) { // TestStorageBackupSyncImportsWhenNoPVCMatches verifies that a backend backup // is still imported as a StorageBackup CR even when its originating lvol has -// no matching PVC (e.g. the source pool was deleted and recreated) — the +// no matching PVC (e.g., the source pool was deleted and recreated) — the // backend backup and its data remain valid and restorable, so it must not be // silently dropped forever. func TestStorageBackupSyncImportsWhenNoPVCMatches(t *testing.T) { @@ -365,7 +365,7 @@ func TestStorageBackupSyncRetriesStatusPatchForOrphanedCRWithoutPVCRef(t *testin // TestStorageBackupSyncImportsWithoutPVCRefOnAnnotationMismatch verifies that // a PVC whose lvol annotation disagrees with its PV's volume handle is -// treated as "no match" (never used as a possibly-wrong PVCRef), but the +// treated as "no match" (never used as a possibly wrong PVCRef), but the // backend backup is still imported rather than silently dropped. func TestStorageBackupSyncImportsWithoutPVCRefOnAnnotationMismatch(t *testing.T) { srv := syncTestBackupServer(t) diff --git a/operator/internal/controller/storageclusterops_controller_unit_test.go b/operator/internal/controller/storageclusterops_controller_unit_test.go index 27c5905ab..9217a88bf 100644 --- a/operator/internal/controller/storageclusterops_controller_unit_test.go +++ b/operator/internal/controller/storageclusterops_controller_unit_test.go @@ -385,7 +385,7 @@ func TestReconcileActivateProceedsOnceFailureDomainsAreReady(t *testing.T) { cluster.Spec.EnableFailureDomains = ptr.To(true) cluster.Spec.StripeSpec = &simplyblockv1alpha1.StripeSpec{ParityChunks: ptr.To(int32(2))} - // 4 distinct, equally-sized domains for npcs=2 -- satisfies npcs+2 = 4, + // 4 distinct, equally sized domains for npcs=2 -- satisfies npcs+2 = 4, // so the gate must let this through to the real activate attempt. nodeSet := &simplyblockv1alpha1.StorageNodeSet{ ObjectMeta: metav1.ObjectMeta{Name: "set-fd-ready", Namespace: scopsTestNS}, diff --git a/operator/internal/controller/storageclusterops_noderollingrestart.go b/operator/internal/controller/storageclusterops_noderollingrestart.go index 28f276dd3..f9f1825e8 100644 --- a/operator/internal/controller/storageclusterops_noderollingrestart.go +++ b/operator/internal/controller/storageclusterops_noderollingrestart.go @@ -65,7 +65,7 @@ func (r *StorageClusterOpsReconciler) reconcileNodeRollingRestart( return r.failOps(ctx, ops, cluster, fmt.Sprintf("resolve cluster UUID: %v", err)) } - // Discover all nodes on first reconcile after initialisation. + // Discover all nodes on first reconcile after initialization. if ops.Status.NodeRollingRestartStatus == nil { nodes, err := listClusterStorageNodeSets(ctx, apiClient, clusterUUID) if err != nil { diff --git a/operator/internal/controller/storagenode_controller.go b/operator/internal/controller/storagenode_controller.go index 8f2dd5102..381f8f6ea 100644 --- a/operator/internal/controller/storagenode_controller.go +++ b/operator/internal/controller/storagenode_controller.go @@ -1002,7 +1002,7 @@ func effectiveFailureDomainSet(sn *simplyblockv1alpha1.StorageNode, sns *simplyb // StorageNode.spec.overrides.failureDomain takes precedence over // StorageNodeSet.spec.nodeFailureDomains[worker]. Only meaningful when // effectiveFailureDomainSet reports true -- the zero return here also covers -// "unset", so callers that must distinguish the two (e.g. anything crossing +// "unset", so callers that must distinguish the two (e.g., anything crossing // a JSON boundary, where 0 and absent are different wire values) should use // effectiveFailureDomainPtr instead. func effectiveFailureDomain(sn *simplyblockv1alpha1.StorageNode, sns *simplyblockv1alpha1.StorageNodeSet) int { @@ -1019,7 +1019,7 @@ func effectiveFailureDomain(sn *simplyblockv1alpha1.StorageNode, sns *simplybloc // but as *int so "domain 0" and "not configured" stay distinguishable across // a JSON boundary (nil is omitted by `omitempty`; Ptr(0) serializes as 0). // Use this instead of effectiveFailureDomain wherever the result crosses -// such a boundary, e.g. StorageNodeSetAddParams.FailureDomain. +// such a boundary, e.g., StorageNodeSetAddParams.FailureDomain. func effectiveFailureDomainPtr(sn *simplyblockv1alpha1.StorageNode, sns *simplyblockv1alpha1.StorageNodeSet) *int { if !effectiveFailureDomainSet(sn, sns) { return nil diff --git a/operator/internal/controller/storagenode_latency_controller.go b/operator/internal/controller/storagenode_latency_controller.go index ee4704271..ac255f38b 100644 --- a/operator/internal/controller/storagenode_latency_controller.go +++ b/operator/internal/controller/storagenode_latency_controller.go @@ -68,7 +68,7 @@ type StorageNodeLatencyReconciler struct { // Set to WebAPIBenchmarkProvisioner for test environments that require explicit provisioning. Provisioner BenchmarkProvisioner - // APIClient queries the SimplyBlock REST API to resolve a storage node's + // APIClient queries the simplyblock REST API to resolve a storage node's // data-network IP (the /nics endpoint). Independent of the provisioner. APIClient *webapi.Client } @@ -138,7 +138,7 @@ func (r *StorageNodeLatencyReconciler) Reconcile(ctx context.Context, req ctrl.R } // One baseline Job per node UUID. On NUMA hosts multiple backend nodes share the - // same k8s hostname but have independent NVMe devices and independent latency + // same K8s hostname but have independent NVMe devices and independent latency // characteristics, so every node UUID is measured separately. nodesByUUID := map[string]simplyblockv1alpha1.NodeStatus{} for _, n := range snode.Status.Nodes { @@ -151,7 +151,7 @@ func (r *StorageNodeLatencyReconciler) Reconcile(ctx context.Context, req ctrl.R } latencyMetrics := r.copyLatencyMetrics(snode.Status.LatencyMetrics) - // hostConfigs accumulates per-node configs keyed by k8s hostname so the sidecar + // hostConfigs accumulates per-node configs keyed by K8s hostname so the sidecar // (one pod per host) receives a JSON array covering all NUMA nodes on its host. hostConfigs := map[string][]autoplacement.NodeConfig{} changed := false @@ -452,7 +452,7 @@ func (r *StorageNodeLatencyReconciler) readJobResult(ctx context.Context, job *b return nil, fmt.Errorf("no termination message for job %s", job.Name) } -// reconcileConfigMap creates or updates the per-cluster ConfigMap that maps k8s +// reconcileConfigMap creates or updates the per-cluster ConfigMap that maps K8s // node hostname → benchmark volume config JSON consumed by the simplyblock-rebalancer sidecar. func (r *StorageNodeLatencyReconciler) reconcileConfigMap( ctx context.Context, diff --git a/operator/internal/controller/storagenodeops_controller.go b/operator/internal/controller/storagenodeops_controller.go index 49ac69a4f..2b6e9a5be 100644 --- a/operator/internal/controller/storagenodeops_controller.go +++ b/operator/internal/controller/storagenodeops_controller.go @@ -56,7 +56,7 @@ type StorageNodeOpsReconciler struct { Recorder events.EventRecorder // apiReader is an uncached reader (mgr.GetAPIReader) used for the migrate // DNS gate (endpointSliceHasWorker). A stale informer cache could otherwise - // miss the target worker's freshly-published storage-node-api endpoint and + // miss the target worker's freshly published storage-node-api endpoint and // wedge the migration in Preparing on "waiting for DNS" indefinitely. apiReader client.Reader } @@ -583,7 +583,7 @@ func (r *StorageNodeOpsReconciler) migrateWaitingAfter( // endpointSliceHasWorker reports whether the storage-node-api EndpointSlice // publishes the given worker's per-pod DNS hostname with at least one address — -// i.e. whether .simplyblock-storage-node-api..svc resolves. +// i.e., whether .simplyblock-storage-node-api..svc resolves. func (r *StorageNodeOpsReconciler) endpointSliceHasWorker( ctx context.Context, namespace, storageNodeSetName, worker string, @@ -591,7 +591,7 @@ func (r *StorageNodeOpsReconciler) endpointSliceHasWorker( log := logf.FromContext(ctx) var eps discoveryv1.EndpointSlice // Uncached read via the APIReader: this is a liveness gate — if it reads a - // stale slice and misses the target's freshly-published endpoint, the + // stale slice and misses the target's freshly published endpoint, the // migration wedges in Preparing on "waiting for DNS" with no error. Reading // straight from the API server removes any dependence on informer freshness. if err := r.apiReader.Get(ctx, types.NamespacedName{ @@ -1069,7 +1069,7 @@ func (r *StorageNodeOpsReconciler) drainValidate( // just notice and proceed. Restoring failure-domain balance never does // -- it needs a deliberate cluster-wide change (add a host, or remove a // different node instead) that this ops has no way to detect on its - // own, so silently polling every 60s would leave a permanently-stuck + // own, so silently polling every 60s would leave a permanently stuck // Running ops easy to miss in `kubectl get storagenodeops`. Failing is // safe here specifically because handleDeletion (storagenode_controller.go) // now refuses to remove the StorageNode's finalizer while its remove @@ -1092,12 +1092,12 @@ func (r *StorageNodeOpsReconciler) drainValidate( // fdRemovalBalanceCheck reports whether removing sn would violate the // cluster's failure-domain balance rule, mirroring the backend's // check_fd_admission_for_remove (simplyblock_core), including its very -// first early-out: a no-op when the cluster doesn't have failure domains +// first early out: a no-op when the cluster doesn't have failure domains // enabled at all. Re-fetches the parent StorageNodeSet (and StorageCluster) // rather than threading them through runDrain's whole dispatch chain -- // Validating is the only sub-phase that needs them. Returns ("", nil) when // removal is fine (including when FD data isn't populated yet, same as the -// backend's own early-outs); a non-empty reason means drainValidate must +// backend's own early outs); a non-empty reason means drainValidate must // fail rather than advance to Suspending. func (r *StorageNodeOpsReconciler) fdRemovalBalanceCheck( ctx context.Context, sn *simplyblockv1alpha1.StorageNode, diff --git a/operator/internal/controller/storagenodeops_controller_unit_test.go b/operator/internal/controller/storagenodeops_controller_unit_test.go index 26a3f0616..f445c1a72 100644 --- a/operator/internal/controller/storagenodeops_controller_unit_test.go +++ b/operator/internal/controller/storagenodeops_controller_unit_test.go @@ -132,7 +132,7 @@ func TestAcquireLock_RequeuesWhenAnotherOpsActive(t *testing.T) { // FD1=2/FD2=2/FD3=3. Removing a node from the already-smallest domain (FD1) // drops it to 1 while FD3 stays at 3 -- a spread the backend's own // check_fd_admission_for_remove correctly refuses (populations {1,2,3}). -// Removing instead from the domain with slack (FD3) leaves 2/2/2, which is +// Removing instead from the domain with Slack (FD3) leaves 2/2/2, which is // fine. This is the gate that must fire in drainValidate BEFORE Suspending, // so an infeasible removal never suspends the node in the first place. @@ -218,7 +218,7 @@ func TestFdRemovalBalanceCheck_RemovingFromThinDomainBlocked(t *testing.T) { } // TestFdRemovalBalanceCheck_NoOpWhenFailureDomainsDisabled locks in the -// gate's very first early-out, mirroring check_fd_admission_for_remove's +// gate's very first early out, mirroring check_fd_admission_for_remove's // own first line (simplyblock_core): with EnableFailureDomains unset/false, // this must never block a removal, regardless of topology -- the exact // same 1/2/3 split that TestFdRemovalBalanceCheck_RemovingFromThinDomainBlocked diff --git a/operator/internal/controller/volumemigration_controller.go b/operator/internal/controller/volumemigration_controller.go index 3ed43ff1b..0387efcf4 100644 --- a/operator/internal/controller/volumemigration_controller.go +++ b/operator/internal/controller/volumemigration_controller.go @@ -53,7 +53,7 @@ type VolumeMigrationReconciler struct { coreClient corev1client.CoreV1Interface // apiReader is an uncached reader (mgr.GetAPIReader) used for the // "is this volume actively consumed?" decision. A stale informer cache could - // otherwise miss a genuinely-running consumer and cause validation to be + // otherwise miss a genuinely running consumer and cause validation to be // skipped for a live volume, breaking its I/O path after cutover. apiReader client.Reader } @@ -82,7 +82,7 @@ const maxMigrationDeferral = 10 * time.Minute // validationJobDeadline per Job (Jobs run in parallel, so that is not additive). const ( // maxConsumerWait bounds how long we wait for every consumer pod of the - // subsystem to be Running. Past it the migration is cancelled rather than + // subsystem to be Running. Past it the migration is canceled rather than // continued with an unvalidated node — a consumer that starts mid-migration // stages against the source and is stranded at cutover. maxConsumerWait = 60 * time.Second @@ -99,7 +99,7 @@ const ( ) // errConsumerNotReady indicates that a pod references the volume's PVC but is not -// Running yet (e.g. Pending or scheduling). A consumer is coming, so validation +// Running yet (e.g., Pending or scheduling). A consumer is coming, so validation // must NOT be skipped: the caller should wait and validate on the consumer's node // once it is Running, rather than continuing the migration unvalidated. var errConsumerNotReady = errors.New("volume has a consumer that is not running yet") @@ -592,7 +592,7 @@ func (r *VolumeMigrationReconciler) pollValidationJobs( // // It is what closes the gap a per-node release cannot. The Job that fails releases its // own paths on the way out, but the nodes whose validation *passed* exited successfully -// and are never told that the migration was cancelled anyway — by another node's failure, +// and are never told that the migration was canceled anyway — by another node's failure, // or by the operator giving up on a consumer that never started. Their target paths stay // connected, retry a target that has stopped answering for them, and settle into the husk // that blocks the next migration of the subsystem. Before this, nothing on the node ever @@ -762,7 +762,7 @@ func (r *VolumeMigrationReconciler) performMigration( m, err := r.apiClient.GetMigration(ctx, vm.Status.ClusterUUID, vm.Status.SubsystemNQN, vm.Status.MigrationUUID) if err != nil { - // Transient read failure: requeue without failing or cancelling. + // Transient read failure: requeue without failing or canceling. log.Error(err, "Cannot read migration before continue; requeuing", "migration", vm.Status.MigrationUUID) return ctrl.Result{RequeueAfter: 15 * time.Second}, nil } @@ -777,7 +777,7 @@ func (r *VolumeMigrationReconciler) performMigration( if err := r.apiClient.ContinueMigration(ctx, vm.Status.ClusterUUID, vm.Status.SubsystemNQN, vm.Status.MigrationUUID); err != nil { // The continue may have taken effect despite the error. Only a // migration still stuck in pre_created is a genuine start failure - // worth cancelling; anything else means it already advanced. + // worth canceling; anything else means it already advanced. if m2, gerr := r.apiClient.GetMigration(ctx, vm.Status.ClusterUUID, vm.Status.SubsystemNQN, vm.Status.MigrationUUID); gerr == nil && m2.Phase == webapi.MigrationPhasePreCreated { // Best-effort: the CR fails either way, but a failed cancel leaves // target-side objects behind, so it must not be silent. diff --git a/operator/internal/controller/volumemigration_controller_unit_test.go b/operator/internal/controller/volumemigration_controller_unit_test.go index f8ab05223..418d6bc0b 100644 --- a/operator/internal/controller/volumemigration_controller_unit_test.go +++ b/operator/internal/controller/volumemigration_controller_unit_test.go @@ -47,7 +47,7 @@ const ( // that must never reach the storage API. const unreachableAPI = "http://127.0.0.1:1" -// newVMReconciler builds a VolumeMigrationReconciler backed by a fake k8s client +// newVMReconciler builds a VolumeMigrationReconciler backed by a fake K8s client // (with VolumeMigration status subresource enabled) and a webapi client pointed // at apiURL. Pass unreachableAPI when the API must not be called. func newVMReconciler(t *testing.T, apiURL string, objs ...client.Object) (*VolumeMigrationReconciler, client.Client) { @@ -1262,7 +1262,7 @@ func TestPerformMigration_AlreadyContinued_SkipsContinueAndCancel(t *testing.T) } // When ContinueMigration itself reports an error but the migration is genuinely -// still stuck in pre_created (a real start failure), the migration is cancelled +// still stuck in pre_created (a real start failure), the migration is canceled // and marked Failed. func TestPerformMigration_ContinueFails_StillPreCreated_CancelsAndFails(t *testing.T) { var cancelCalled bool diff --git a/operator/internal/controller/volumemigration_helpers_test.go b/operator/internal/controller/volumemigration_helpers_test.go index 2f43c9001..c4194402e 100644 --- a/operator/internal/controller/volumemigration_helpers_test.go +++ b/operator/internal/controller/volumemigration_helpers_test.go @@ -72,7 +72,7 @@ func TestNodeSuffix(t *testing.T) { // ---- connection conversion ---- // The Job receives these as JSON and passes them to `nvme connect`; a dropped field -// changes the resulting path's behaviour, so the mapping is asserted whole. +// changes the resulting path's behavior, so the mapping is asserted whole. func TestConnectionsToValidation(t *testing.T) { in := []simplyblockv1alpha1.MigrationConnection{{ NQN: "nqn.x", IP: "10.0.0.1", Port: 4420, Transport: "tcp", @@ -280,7 +280,7 @@ func releaseJobs(t *testing.T, cl client.Client) map[string]batchv1.Job { } // The gap a per-node release cannot close: a node whose own validation passed exits -// successfully and is never told the migration was cancelled, so the operator has to +// successfully and is never told the migration was canceled, so the operator has to // release for it. Every recorded node is asked, not only the ones that passed. func TestReconcileAbort_ReleasesTargetPathsOnEveryNode(t *testing.T) { srv := newAPIServer(t, func(w http.ResponseWriter, r *http.Request) { diff --git a/operator/internal/controller/volumemigration_migration_paths_test.go b/operator/internal/controller/volumemigration_migration_paths_test.go index 158efbf9e..d1b2bebe1 100644 --- a/operator/internal/controller/volumemigration_migration_paths_test.go +++ b/operator/internal/controller/volumemigration_migration_paths_test.go @@ -139,7 +139,7 @@ func TestReconcileStart_AcceptedAfterDeferral_ClearsDeferredSince(t *testing.T) } // Every connect parameter has to reach the validation Job: the job passes them to -// `nvme connect`, and a dropped timeout changes the path's failure behaviour. +// `nvme connect`, and a dropped timeout changes the path's failure behavior. // // One is not passed through. ctrl_loss_tmo is replaced with vmigration.CtrlLossTmoSec, // because a migration target path becomes the volume's data path at cutover and must @@ -186,7 +186,7 @@ func TestReconcileStart_RecordsEveryConnectionField(t *testing.T) { // ---- performMigration: the branches around the continue call ---- // A migration that reached a terminal state on its own must not be continued or -// cancelled; it advances to Running so the polling path classifies the outcome. +// canceled; it advances to Running so the polling path classifies the outcome. func TestPerformMigration_AlreadyTerminal_AdvancesForClassification(t *testing.T) { srv := newAPIServer(t, func(w http.ResponseWriter, r *http.Request) { if serveSubsystemMembers(w, r) { @@ -214,7 +214,7 @@ func TestPerformMigration_AlreadyTerminal_AdvancesForClassification(t *testing.T } // The continue call can report an error after having taken effect. If the migration -// has moved past pre_created, it started — cancelling it would kill a healthy move. +// has moved past pre_created, it started — canceling it would kill a healthy move. func TestPerformMigration_ContinueErroredButAdvanced_TreatedAsContinued(t *testing.T) { var cancelCalled bool calls := 0 @@ -382,7 +382,7 @@ func TestReconcileRunning_WithinInitialDelay_DoesNotPoll(t *testing.T) { } // A migration still in flight past the stuck timeout is surfaced as a warning while -// polling continues — it may yet finish, and cancelling it is the operator's call. +// polling continues — it may yet finish, and canceling it is the operator's call. func TestReconcileRunning_PastStuckTimeout_WarnsAndKeepsPolling(t *testing.T) { srv := newAPIServer(t, func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte(`{"id":"` + testMigrationUUID + `","phase":"snap_copy","status":"running"}`)) diff --git a/operator/internal/controller/volumerebalancer_controller.go b/operator/internal/controller/volumerebalancer_controller.go index 8eafc63e9..9d182e0a2 100644 --- a/operator/internal/controller/volumerebalancer_controller.go +++ b/operator/internal/controller/volumerebalancer_controller.go @@ -203,7 +203,7 @@ func (r *VolumeRebalancerReconciler) Reconcile( // Dry-run: when migration creation is disabled the rebalancer still evaluated load and // emitted deviation metrics above; we log the candidates it *would* migrate but create - // no VolumeMigration CRs (e.g. to run workload tests without rebalancer interference). + // no VolumeMigration CRs (e.g., to run workload tests without rebalancer interference). if !cfg.MigrationEnabled { for _, mc := range toMigrate { log.Info("migrationEnabled=false; skipping migration (dry-run)", @@ -522,7 +522,7 @@ func (r *VolumeRebalancerReconciler) reconcileDataRealignment( } // movingVolumes names the VolumeMigrations for this cluster that the control plane has -// accepted and not yet finished, i.e. the ones that may be moving data right now. +// accepted and not yet finished, i.e., the ones that may be moving data right now. // // Deliberately keyed on MigrationUUID rather than phase alone. A CR whose submission // was refused — which is exactly what happens while a realignment is running, since the diff --git a/operator/internal/controller/volumerebalancer_realignment_test.go b/operator/internal/controller/volumerebalancer_realignment_test.go index 62a942439..40e406c7d 100644 --- a/operator/internal/controller/volumerebalancer_realignment_test.go +++ b/operator/internal/controller/volumerebalancer_realignment_test.go @@ -168,7 +168,7 @@ func TestNextRequeue(t *testing.T) { // reconcileDataRealignment — behavior + negative cases. // --------------------------------------------------------------------------- -// realignFixture wires a reconciler to a counting HTTP stub and a fake k8s client. +// realignFixture wires a reconciler to a counting HTTP stub and a fake K8s client. type realignFixture struct { r *VolumeRebalancerReconciler cl client.Client diff --git a/operator/internal/cpinformer/sse.go b/operator/internal/cpinformer/sse.go index df6935468..ec50a8100 100644 --- a/operator/internal/cpinformer/sse.go +++ b/operator/internal/cpinformer/sse.go @@ -94,7 +94,7 @@ func decodeSSE(r io.Reader, onEvent func(sseEvent) error, onComment func()) erro // openStream issues the watch request for one resource path and returns the // live response. The caller owns resp.Body and must close it. The request -// carries the stream's lifetime via ctx; cancelling ctx aborts the in-flight read. +// carries the stream's lifetime via ctx; canceling ctx aborts the in-flight read. func openStream(ctx context.Context, cfg StreamConfig, path string) (*http.Response, error) { url := strings.TrimRight(cfg.Endpoint, "/") + path req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) diff --git a/operator/internal/csilink/hub.go b/operator/internal/csilink/hub.go index 889d1d3a9..d4cbc9fa1 100644 --- a/operator/internal/csilink/hub.go +++ b/operator/internal/csilink/hub.go @@ -3,7 +3,7 @@ // The CSI node and controller pods dial the operator and hold the connection; // the operator issues its RPCs back down it. Nothing listens on a node, so no // per-node ingress or discovery is needed — see the atlas link package for why -// the connection runs backwards and how gRPC still works over it. +// the connection runs backward and how gRPC still works over it. // // Setup adds the hub to the manager and hands back the registry the reconcilers // read. A peer that is not currently linked is normal, not exceptional: expect @@ -30,7 +30,7 @@ import ( // Config configures the operator's hub. type Config struct { - // BindAddress is where peers dial, e.g. ":9500". + // BindAddress is where peers dial, e.g., ":9500". BindAddress string // CertFile and KeyFile are the hub's serving certificate. Both are diff --git a/operator/internal/csilink/hub_test.go b/operator/internal/csilink/hub_test.go index 90158bae0..4c9571738 100644 --- a/operator/internal/csilink/hub_test.go +++ b/operator/internal/csilink/hub_test.go @@ -1,5 +1,5 @@ // Tests for the hub's certificate reloader, the part of this package with -// behaviour worth pinning: it decides when a keypair on disk has been rotated, +// behavior worth pinning: it decides when a keypair on disk has been rotated, // and getting that decision wrong means a long-lived listener serves an expired // certificate until the operator happens to restart. package csilink diff --git a/operator/internal/tlsutil/tlsutil.go b/operator/internal/tlsutil/tlsutil.go index eccbbf6e1..d6f39e058 100644 --- a/operator/internal/tlsutil/tlsutil.go +++ b/operator/internal/tlsutil/tlsutil.go @@ -19,12 +19,12 @@ import ( var ServiceCABundlePath = "/etc/simplyblock/tls/ca.crt" // ServiceClientCertificatePath is where the operator pod mounts its client -// certificate for mutually-authenticated TLS to the simplyblock webapp. +// certificate for mutually authenticated TLS to the simplyblock webapp. // Overridable for tests. var ServiceClientCertificatePath = "/etc/simplyblock/tls/tls.crt" // ServiceClientKeyPath is where the operator pod mounts its client private key -// for mutually-authenticated TLS to the simplyblock webapp. Overridable for +// for mutually authenticated TLS to the simplyblock webapp. Overridable for // tests. var ServiceClientKeyPath = "/etc/simplyblock/tls/tls.key" diff --git a/operator/internal/utils/objects.go b/operator/internal/utils/objects.go index 16147aded..e8587e558 100644 --- a/operator/internal/utils/objects.go +++ b/operator/internal/utils/objects.go @@ -137,7 +137,7 @@ func ResolveClusterIdentifier(ctx context.Context, k8sClient client.Client, name } // ResolveClusterCRByUUID finds the StorageCluster CR in namespace whose backend -// UUID matches uuid. Used to go from a cross-cluster reference (which only +// UUID matches UUID. Used to go from a cross-cluster reference (which only // carries the backend UUID) back to the CR, to read config the backend doesn't // expose, such as a cluster's backup credentials secret. func ResolveClusterCRByUUID( @@ -488,7 +488,7 @@ func RequiredNodesFromErasureCodingScheme(scheme string) (int, error) { } // ParityChunksFromErasureCodingScheme returns just npcs (the parity-chunk -// count, e.g. "2x1" -> 1) from a StorageCluster's erasureCodingScheme. This +// count, e.g., "2x1" -> 1) from a StorageCluster's erasureCodingScheme. This // is the failure-domain risk budget the drain coordinator's fdDrainGate // spends against — see RequiredNodesFromErasureCodingScheme for the sibling // ndcs+npcs total. diff --git a/operator/internal/volumemigration/config_test.go b/operator/internal/volumemigration/config_test.go index eb1b22e5f..8b9a87169 100644 --- a/operator/internal/volumemigration/config_test.go +++ b/operator/internal/volumemigration/config_test.go @@ -16,7 +16,7 @@ func TestDefaultRebalancerImage(t *testing.T) { }) // The Helm chart sets this to the operator's own tag so the rebalancer image and - // the operator stay in lockstep; honouring it is what keeps them from drifting. + // the operator stay in lockstep; honoring it is what keeps them from drifting. t.Run("environment override wins", func(t *testing.T) { t.Setenv(RebalancerImageEnvVar, "registry.example.com/rebalancer:v9") if got := defaultRebalancerImage(); got != "registry.example.com/rebalancer:v9" { diff --git a/operator/internal/volumemigration/paths.go b/operator/internal/volumemigration/paths.go index 35ed41ad5..0073f371d 100644 --- a/operator/internal/volumemigration/paths.go +++ b/operator/internal/volumemigration/paths.go @@ -147,15 +147,15 @@ func PresentAddresses(ctx context.Context, sysRoot, nqn string) (map[string]bool // // Inspect is what names the defects a connect cannot see — a live controller that // serves no namespace at all, or serves the subsystem's other namespaces but not -// this one, or an NQN answered by two kernel subsystem instances at once. Those are +// this one, or an nqn answered by two kernel subsystem instances at once. Those are // the states in which every expected path looks established while the volume has // nothing to take over at cutover, and diagnosing them from what the kernel already -// publishes is what atlas centralises. +// publishes is what atlas centralizes. // // It is asked once per exported namespace rather than once for the subsystem, and // that is the whole reason it can say anything here: its controller-level check // needs to know which namespace is meant, and stands down when a selector matches -// several — which a bare NQN does on exactly the multi-namespace subsystems this +// several — which a bare nqn does on exactly the multi-namespace subsystems this // package migrates. // // No target list is passed. Targets are how Inspect tells an attached endpoint the @@ -205,7 +205,7 @@ func diagnose(ctx context.Context, sysRoot string, s nvme.Subsystem) []string { } // VerifyMigrationPaths checks that every expected target path is established on this -// host and parked, i.e. ready to take over at cutover but not serving yet. +// host and parked, i.e., ready to take over at cutover but not serving yet. // // Each path must be: // diff --git a/operator/internal/volumemigration/release.go b/operator/internal/volumemigration/release.go index fa507a92c..12ba1ab70 100644 --- a/operator/internal/volumemigration/release.go +++ b/operator/internal/volumemigration/release.go @@ -12,7 +12,7 @@ import ( ) // Released names one controller that was disconnected, for the Job log and the -// operator's post-mortem. The address is what an operator recognises a path by; the +// operator's post-mortem. The address is what an operator recognizes a path by; the // controller ID is what the kernel logged it under. type Released struct { Controller string // "nvme7" @@ -100,7 +100,7 @@ func migrationPathVictims(s nvme.Subsystem, conns []Connection) []nvme.Controlle // prevent, so the state is declined rather than acted on. // // What makes this safe to decline is that it costs nothing but a round: a leak is - // recognisable precisely because some path is serving while the migration's are not, + // recognizable precisely because some path is serving while the migration's are not, // so a real one is still there to release on the next attempt, and the husk it leaves // is ReapDeadControllers' to clear — that pass reads namespace legs rather than ANA // states and is unaffected by the window. @@ -207,7 +207,7 @@ func releaseMigrationPaths( return nil, err } if s.NQN == "" { - // Nothing attached for this NQN: the paths are already gone. + // Nothing attached for this nqn: the paths are already gone. return nil, nil } return detach(ctx, d, migrationPathVictims(s, conns), @@ -305,9 +305,9 @@ func reapDeadControllers( // the other thing worth reusing here — and when the controller serves no namespace at // all. See reapableKind for why that last test is this caller's and not atlas's. // -// Inspect is asked once per exported namespace as well as once for the bare NQN, because +// Inspect is asked once per exported namespace as well as once for the bare nqn, because // its controller-level check needs to know which namespace is meant and stands down when -// a selector matches several — which a bare NQN does on exactly the multi-namespace +// a selector matches several — which a bare nqn does on exactly the multi-namespace // subsystems this package migrates. That is the same reason diagnose asks that way. func reapableDefects(ctx context.Context, sysRoot string, s nvme.Subsystem) []nvmeof.Defect { subs := snapshot{s} diff --git a/operator/internal/volumemigration/scoring.go b/operator/internal/volumemigration/scoring.go index 386174295..0d2396b47 100644 --- a/operator/internal/volumemigration/scoring.go +++ b/operator/internal/volumemigration/scoring.go @@ -22,7 +22,7 @@ func ComputeLatencyDeviationPct( // A higher score means the volume contributes more I/O load and should be // migrated first. // -// throughputBytesPerSec is normalised to MB/s before weighting so both terms +// throughputBytesPerSec is normalized to MB/s before weighting so both terms // are on a comparable numerical scale. Sensible defaults: iopsWeight=1.0, // throughputMBWeight=0.1 (1 MB/s ≈ 0.1 of a IOPS unit). func VolumeIOScore( diff --git a/operator/internal/volumemigration/utils.go b/operator/internal/volumemigration/utils.go index bc341b6ff..67b017f91 100644 --- a/operator/internal/volumemigration/utils.go +++ b/operator/internal/volumemigration/utils.go @@ -90,7 +90,7 @@ func findPVForVolume( // PollMigrationResult is returned by PollMigration. type PollMigrationResult struct { // Done is true when the migration has reached a terminal status - // (done, failed, or cancelled). + // (done, failed, or canceled). Done bool // Succeeded is true when the migration reached the "done" status. Succeeded bool diff --git a/operator/internal/volumemigration/validate.go b/operator/internal/volumemigration/validate.go index 2fb14fd8b..2ef09113f 100644 --- a/operator/internal/volumemigration/validate.go +++ b/operator/internal/volumemigration/validate.go @@ -117,7 +117,7 @@ func connector(sysRoot string) *nvmeof.CLIConnector { // // The connect's own success is still not proof that the path is usable: a // controller can be live and serve no namespace. VerifyMigrationPaths establishes -// that, by reading the host's own view afterwards rather than trusting this report. +// that, by reading the host's own view afterward rather than trusting this report. // // Connections are grouped by NQN because atlas attaches one subsystem at a time. A // migration moves a single subsystem, so in practice there is one group. diff --git a/operator/internal/webapi/client.go b/operator/internal/webapi/client.go index 849621e8d..71e524662 100644 --- a/operator/internal/webapi/client.go +++ b/operator/internal/webapi/client.go @@ -17,7 +17,7 @@ var ServiceAccountTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/tok type Client struct { BaseURL string HttpClient *http.Client - // initErr captures any setup error (e.g. failure to load the TLS CA + // initErr captures any setup error (e.g., failure to load the TLS CA // bundle when TLS is enabled). It is surfaced from request methods so // callers see a real error instead of silently dropping back to a // non-functional client. diff --git a/operator/internal/webapi/rebalancing.go b/operator/internal/webapi/rebalancing.go index ad03bb05d..0b4e0c703 100644 --- a/operator/internal/webapi/rebalancing.go +++ b/operator/internal/webapi/rebalancing.go @@ -34,7 +34,7 @@ type CapacityStat struct { type VolumeInfo struct { UUID string `json:"id"` Name string `json:"name"` - // NQN is the volume's NVMe subsystem NQN. Namespaced volumes share it with + // nqn is the volume's NVMe subsystem nqn. Namespaced volumes share it with // their siblings, and it is the identity a batch migration is addressed by // (see MigrationRef). NQN string `json:"nqn"` @@ -59,7 +59,7 @@ type ContinueMigrationParams struct { DeadlineSeconds int `json:"deadline_seconds,omitempty"` } -// Migrations are addressed by cluster and NVMe subsystem NQN: the control plane +// Migrations are addressed by cluster and NVMe subsystem nqn: the control plane // migrates a whole subsystem at once, covering both a single-namespace subsystem // and a namespaced one, where several volumes share it. // @@ -125,7 +125,7 @@ type MigrationDTO struct { // normalize fills in what a single-namespace migration's response leaves out. // Such a migration still moves exactly one volume, so reporting 0 members would // make "how many volumes did this move" wrong for every non-namespaced volume; -// and it is addressed under the subsystem the caller asked for, so that NQN is +// and it is addressed under the subsystem the caller asked for, so that nqn is // the subsystem being migrated whether or not the response repeats it. func (m *MigrationDTO) normalize(nqn string) { if m.MemberCount <= 0 { @@ -168,7 +168,7 @@ const ( ) // MigrationIsTerminal reports whether a migration status is terminal -// (done, failed, or cancelled) and therefore no longer in flight. +// (done, failed, or canceled) and therefore no longer in flight. func MigrationIsTerminal(status string) bool { switch status { case MigrationStatusDone, MigrationStatusFailed, MigrationStatusCancelled: @@ -243,7 +243,7 @@ func (c *Client) GetPoolVolumes( // single-namespace subsystem that is one volume; for a namespaced one it is the // volume and its siblings. // -// The control plane has no volume-by-NQN lookup and its batch-migration DTO reports +// The control plane has no volume-by-nqn lookup and its batch-migration DTO reports // only a member *count*, so membership is derived here by scanning the cluster's // pools. Pools are scanned rather than assuming the subsystem's members live in the // pool of any one member: a subsystem is scoped to a storage node, not to a pool. @@ -308,7 +308,7 @@ func (c *Client) GetVolume( // StorageNodeNIC is one network interface entry returned by the storage-node // /nics endpoint. Address is the data-network IP the lvol subsystem listens on -// (the management IP is reported separately). Field tags match the capitalised, +// (the management IP is reported separately). Field tags match the capitalized, // space-containing keys the control plane emits for this endpoint. type StorageNodeNIC struct { ID string `json:"ID"` @@ -346,7 +346,7 @@ func (c *Client) GetStorageNodeNICs( // validate those paths before calling ContinueMigration. // // If the API reports that a migration already exists for the subsystem, any -// existing migrations are cancelled and the request is retried once. The API +// existing migrations are canceled and the request is retried once. The API // signals this as either 409 or 400 with an "...already exists... Cancel it // first" detail depending on deployment, so both are handled. func (c *Client) CreateMigration( diff --git a/operator/internal/webhook/certmanager.go b/operator/internal/webhook/certmanager.go index 8fed16d9a..06866ffac 100644 --- a/operator/internal/webhook/certmanager.go +++ b/operator/internal/webhook/certmanager.go @@ -32,7 +32,7 @@ const ( ) // certManagerProvisioner runs when SB_TLS_PROVIDER=cert-manager. It creates a -// cert-manager Certificate for the webhook Service, then materialises the issued +// cert-manager Certificate for the webhook Service, then materializes the issued // Secret onto disk (utils.WebhookCertDir) and injects the CA bundle into the // MutatingWebhookConfiguration — the same responsibilities cert-controller's // rotator handles in self-signed mode, but sourced from cert-manager. It keeps diff --git a/operator/internal/webhook/certmanager_test.go b/operator/internal/webhook/certmanager_test.go index 5cb9831c6..8afdf145a 100644 --- a/operator/internal/webhook/certmanager_test.go +++ b/operator/internal/webhook/certmanager_test.go @@ -93,7 +93,7 @@ func TestReconcileCertWritesFilesAndInjectsCA(t *testing.T) { t.Fatalf("validating caBundle = %q, want %q", got, "CA-DATA") } - // readiness signalled. + // readiness signaled. select { case <-p.ready: default: diff --git a/operator/internal/webhook/simplyblock_rebalancer_injector.go b/operator/internal/webhook/simplyblock_rebalancer_injector.go index 7ae536a68..2b59d9622 100644 --- a/operator/internal/webhook/simplyblock_rebalancer_injector.go +++ b/operator/internal/webhook/simplyblock_rebalancer_injector.go @@ -30,7 +30,7 @@ const ( // +kubebuilder:webhook:path=/mutate-v1-pod-simplyblock-rebalancer,mutating=true,failurePolicy=ignore,sideEffects=None,groups="",resources=pods,verbs=create,versions=v1,name=simplyblock-rebalancer-injector.simplyblock.io,admissionReviewVersions=v1 // SimplyblockRebalancerInjector is a mutating admission webhook that injects the simplyblock-rebalancer -// sidecar into any pod labelled role=simplyblock-storage-node, provided the associated +// sidecar into any pod labeled role=simplyblock-storage-node, provided the associated // StorageCluster has latency benchmarking enabled. failurePolicy=ignore ensures that // webhook unavailability never blocks storage node pod creation. type SimplyblockRebalancerInjector struct { diff --git a/operator/internal/webhook/simplyblock_volume_placement_injector.go b/operator/internal/webhook/simplyblock_volume_placement_injector.go index dc07db222..206243e27 100644 --- a/operator/internal/webhook/simplyblock_volume_placement_injector.go +++ b/operator/internal/webhook/simplyblock_volume_placement_injector.go @@ -45,7 +45,7 @@ type primaryNodeSelector interface { // SimplyblockVolumePlacementInjector is a mutating admission webhook that computes the // least-loaded eligible storage node for a new PVC's primary volume — using the same // latency-deviation signal the auto-rebalancer (Issue #130) uses — and stamps it onto the -// PVC as the simplyblock.io/host-id annotation, which spdk-csi already reads and forwards +// PVC as the simplyblock.io/host-id annotation, which spdk-csi already reads and forward // as host_id on CreateVolume. failurePolicy=ignore, and every skip/error path below allows // the PVC unmodified, so this can never block volume provisioning: sbcli's own // weighted-random pick (_get_next_3_nodes) runs as the fallback exactly as it does today. diff --git a/operator/internal/webhook/simplyblock_volume_placement_injector_test.go b/operator/internal/webhook/simplyblock_volume_placement_injector_test.go index bb97eca03..b804214e5 100644 --- a/operator/internal/webhook/simplyblock_volume_placement_injector_test.go +++ b/operator/internal/webhook/simplyblock_volume_placement_injector_test.go @@ -64,7 +64,7 @@ func makePlacementCluster(autoRebalancing *simplyblockv1alpha1.VolumeAutoPlaceme } // applyPVCPatches applies the RFC6902 patch set produced by Handle to the original PVC -// via a real JSON-patch library, mirroring what the k8s apiserver does — avoids having to +// via a real JSON-patch library, mirroring what the K8s apiserver does — avoids having to // guess the exact path granularity the diff library chose for the annotations map. func applyPVCPatches(t *testing.T, pvc *corev1.PersistentVolumeClaim, patches []jsonpatch.JsonPatchOperation) *corev1.PersistentVolumeClaim { t.Helper() diff --git a/operator/internal/webhook/storagenode_validator.go b/operator/internal/webhook/storagenode_validator.go index dd8214fd2..28a8d26ad 100644 --- a/operator/internal/webhook/storagenode_validator.go +++ b/operator/internal/webhook/storagenode_validator.go @@ -34,7 +34,7 @@ import ( // gates pod creation, which must not block on webhook availability.) type StorageNodeValidator struct { // OperatorNamespace is the namespace the operator runs in. Any service - // account in this namespace (i.e. the operator itself) is permitted to change + // account in this namespace (i.e., the operator itself) is permitted to change // spec.workerNode; every other identity is rejected. OperatorNamespace string } diff --git a/operator/test/e2e/rbac_test.go b/operator/test/e2e/rbac_test.go index d27c4ad60..6b8257695 100644 --- a/operator/test/e2e/rbac_test.go +++ b/operator/test/e2e/rbac_test.go @@ -195,7 +195,7 @@ subjects: // K8s RBAC's `resourceNames` filter applies only to verbs that target a // named object (get/update/patch/delete). For list/watch/create the // filter is ignored, so a resourceNames-only Role cannot grant them. - // We assert this behaviour here so we notice if it ever changes. + // We assert this behavior here so we notice if it ever changes. expectCanI(rbacFooNS, rbacScopedSA, "list", "storageclusters.storage.simplyblock.io", "", false) expectCanI(rbacFooNS, rbacScopedSA, "watch", "storageclusters.storage.simplyblock.io", "", false) }) diff --git a/operator/test/utils/utils.go b/operator/test/utils/utils.go index b3b8d16be..e6a57b068 100644 --- a/operator/test/utils/utils.go +++ b/operator/test/utils/utils.go @@ -133,7 +133,7 @@ func IsCertManagerCRDsInstalled() bool { return false } -// LoadImageToKindClusterWithName loads a local docker image to the kind cluster +// LoadImageToKindClusterWithName loads a local Docker image to the kind cluster func LoadImageToKindClusterWithName(name string) error { cluster := defaultKindCluster if v, ok := os.LookupEnv("KIND_CLUSTER"); ok { From c8bfa10c3de6570c9b39ecba4e75a57b95a39946 Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Thu, 10 Sep 2026 22:21:53 +0200 Subject: [PATCH 14/19] docs(go): put quoted values in backticks The quoted-mark rule puts a comma or a full stop inside the closing quotation mark. That is right for a quoted phrase and wrong for a quoted value, and the 144 findings it raised over the Go comments were nearly all values: the status strings a StorageNode reports, the durations and sizes a field accepts, device paths, name templates, a regex. The house style answers this rather than arguing with it: a value or an identifier takes backticks instead of quotation marks. 1164 comment lines in this repository already do, `nvme0n1` and `ng0n1` among them, so 62 more follow the convention rather than inventing one. // OnlineNodes is the count of StorageNode CRs with status `online`. // MaxAge is the maximum age of backups to retain (e.g., `7d`, `12h`, `30m`). // Defaults to `^sb-fio-baseline-.*`. Seven were genuine quotations and took the rule as written, the mark moving inside: "the peer that can answer for node worker-3," "realign when nothing has moved," "was this address here before." Twenty findings are left and none of them is prose. Six comment lines hold commented-out Go or a literal document, and the rule is reading their string literals: // log.Error(err, "StoragePool update failed", "status", status, ...) // endpoint := fmt.Sprintf("/api/v2/clusters/%s/storage-pools/%s", ...) // {"status":"success","data":{"resultType":"vector","result":[...]}} // (e.g., ["nvme0n1","nvme1n1"]). Moving a comma into any of those would corrupt what the line is showing, so they stay. Teaching the checker to skip a comment line that parses as code is the fix, and it belongs in the checker. Every one of the 43 files was tokenized before and after with comments skipped, and the token streams are identical. `go vet ./...` passes in all four modules and gofmt is clean. --- atlas-lib/link/kubeauth.go | 2 +- atlas-lib/link/peer.go | 6 +++--- atlas-lib/link/registry.go | 2 +- atlas-lib/storage/storagerpc/client.go | 2 +- atlas-lib/storage/storagerpc/convert_test.go | 2 +- operator/api/v1alpha1/backuppolicy_types.go | 2 +- operator/api/v1alpha1/replicationops_types.go | 2 +- operator/api/v1alpha1/replicationpolicy_types.go | 2 +- operator/api/v1alpha1/storagebackup_types.go | 2 +- operator/api/v1alpha1/storagenode_types.go | 4 ++-- operator/api/v1alpha1/storagenodeops_types.go | 4 ++-- operator/api/v1alpha1/storagenodeset_types.go | 12 ++++++------ operator/api/v1alpha1/storagepool_types.go | 2 +- operator/cmd/simplyblock-rebalancer/main.go | 2 +- .../autoplacement/logical_volume_selector.go | 2 +- operator/internal/autoplacement/utils.go | 2 +- .../internal/controller/backuppolicy_controller.go | 2 +- .../internal/controller/benchmark_provisioner.go | 4 ++-- .../controller/nodedrain_controller_unit_test.go | 2 +- .../internal/controller/replicationops_controller.go | 2 +- .../replicationops_controller_unit_test.go | 2 +- .../controller/replicationpolicy_controller.go | 2 +- .../replicationslot_controller_unit_test.go | 2 +- .../simplyblockstoragenodeset_controller.go | 2 +- .../controller/storageclusterops_controller.go | 4 ++-- .../internal/controller/storagenode_controller.go | 2 +- .../controller/storagenode_latency_controller.go | 2 +- .../internal/controller/storagenodeops_controller.go | 2 +- .../storagenodeops_controller_unit_test.go | 2 +- .../controller/volumemigration_controller.go | 2 +- .../volumemigration_controller_unit_test.go | 2 +- .../controller/volumemigration_helpers_test.go | 4 ++-- .../controller/volumerebalancer_realignment_test.go | 2 +- operator/internal/cpinformer/sse.go | 2 +- operator/internal/cpinformer/store_test.go | 2 +- operator/internal/csilink/hub.go | 2 +- operator/internal/volumemigration/paths.go | 2 +- operator/internal/volumemigration/paths_test.go | 2 +- operator/internal/volumemigration/subsystem.go | 2 +- operator/internal/volumemigration/subsystem_test.go | 2 +- operator/internal/volumemigration/utils.go | 2 +- operator/internal/volumemigration/validate_test.go | 2 +- .../webhook/simplyblock_rebalancer_injector.go | 2 +- 43 files changed, 55 insertions(+), 55 deletions(-) diff --git a/atlas-lib/link/kubeauth.go b/atlas-lib/link/kubeauth.go index 4b13682f9..cbc1e3867 100644 --- a/atlas-lib/link/kubeauth.go +++ b/atlas-lib/link/kubeauth.go @@ -53,7 +53,7 @@ type KubeAuthenticator struct { Audiences []string // ServiceAccounts, when non-empty, restricts which ServiceAccounts may - // register as which kind, as "namespace/name". Without it any + // register as which kind, as `namespace/name`. Without it any // authenticated ServiceAccount may register as any kind — its *name* is // still verified, so it cannot impersonate another peer, but a node plugin // could register as the controller and take its place in the registry. diff --git a/atlas-lib/link/peer.go b/atlas-lib/link/peer.go index 9190a8e71..754ea93f3 100644 --- a/atlas-lib/link/peer.go +++ b/atlas-lib/link/peer.go @@ -12,7 +12,7 @@ import ( // PeerKind is the role a peer links as. It is part of a peer's identity rather // than a property of it: what the operator wants is "the peer that can answer -// for node worker-3", and the kind is what separates that from a controller +// for node worker-3," and the kind is what separates that from a controller // that happens to run on worker-3. type PeerKind string @@ -43,7 +43,7 @@ func ControllerPeer(pod string) PeerID { return PeerID{Kind: PeerKindController, Name: pod} } -// String renders the id as "kind/name", e.g., "node/worker-3". +// String renders the id as `kind/name`, e.g., `node/worker-3`. func (id PeerID) String() string { return string(id.Kind) + "/" + id.Name } @@ -137,7 +137,7 @@ func (p *Peer) Close() error { return p.session.Close() } -// String renders the peer as "kind/name@instance-uid". +// String renders the peer as `kind/name@instance-uid`. func (p *Peer) String() string { return p.ID.String() + "@" + p.InstanceUID } diff --git a/atlas-lib/link/registry.go b/atlas-lib/link/registry.go index 9db39d06e..4c0106101 100644 --- a/atlas-lib/link/registry.go +++ b/atlas-lib/link/registry.go @@ -12,7 +12,7 @@ import ( // Registry is the set of peers currently linked to a hub — who is reachable // right now, and how to reach them. // -// It is the hub's answer to "call node worker-3", and the place absence is +// It is the hub's answer to "call node worker-3," and the place absence is // expressed: a peer that has not linked, or whose session just dropped, is // simply not in it, and lookups fail with [ErrNoSession]. Registration happens // through the hub as sessions complete their handshake; callers read. diff --git a/atlas-lib/storage/storagerpc/client.go b/atlas-lib/storage/storagerpc/client.go index 292f5671a..a71db65de 100644 --- a/atlas-lib/storage/storagerpc/client.go +++ b/atlas-lib/storage/storagerpc/client.go @@ -128,7 +128,7 @@ func (r *DeviceResolver) ByUUID(ctx context.Context, uuid string) (nvme.Device, return r.device(resp.GetDevice(), "uuid="+uuid) } -// ByDevicePath returns the device for a block node such as "/dev/nvme0n1". It +// ByDevicePath returns the device for a block node such as `/dev/nvme0n1`. It // reports errs.ErrNotFound when nothing matches. func (r *DeviceResolver) ByDevicePath(ctx context.Context, devicePath string) (nvme.Device, error) { resp, err := r.client.GetDeviceByDevicePath(ctx, &storagev1.GetDeviceByDevicePathRequest{ diff --git a/atlas-lib/storage/storagerpc/convert_test.go b/atlas-lib/storage/storagerpc/convert_test.go index a659e9028..1a9ade48f 100644 --- a/atlas-lib/storage/storagerpc/convert_test.go +++ b/atlas-lib/storage/storagerpc/convert_test.go @@ -122,7 +122,7 @@ func TestSelectorRoundTrip(t *testing.T) { } } -// The zero selector means "everything", and must not become something narrower +// The zero selector means "everything," and must not become something narrower // by traveling. func TestZeroSelectorRoundTrip(t *testing.T) { if got := selectorFromProto(selectorToProto(nvme.DeviceSelector{})); !got.IsZero() { diff --git a/operator/api/v1alpha1/backuppolicy_types.go b/operator/api/v1alpha1/backuppolicy_types.go index 2bcc01ee8..3e39f30e8 100644 --- a/operator/api/v1alpha1/backuppolicy_types.go +++ b/operator/api/v1alpha1/backuppolicy_types.go @@ -40,7 +40,7 @@ type BackupPolicySpec struct { // +k8s:immutable MaxVersions int `json:"maxVersions,omitempty"` - // MaxAge is the maximum age of backups to retain (e.g., "7d", "12h", "30m"). + // MaxAge is the maximum age of backups to retain (e.g., `7d`, `12h`, `30m`). // Backups older than this are merged. Accepts m, h, d, w suffixes. // +optional // +kubebuilder:validation:Pattern=`^[1-9]\d*[mhdw]$` diff --git a/operator/api/v1alpha1/replicationops_types.go b/operator/api/v1alpha1/replicationops_types.go index 132054171..4b40b359e 100644 --- a/operator/api/v1alpha1/replicationops_types.go +++ b/operator/api/v1alpha1/replicationops_types.go @@ -102,7 +102,7 @@ type ReplicationOpsStatus struct { Phase string `json:"phase,omitempty"` // Subphase describes what the operation is currently doing within the phase - // (e.g., "TriggeringFailover", "UpdatingSlotStatuses", "ReleasingLock"). + // (e.g., `TriggeringFailover`, `UpdatingSlotStatuses`, `ReleasingLock`). // +optional Subphase string `json:"subphase,omitempty"` diff --git a/operator/api/v1alpha1/replicationpolicy_types.go b/operator/api/v1alpha1/replicationpolicy_types.go index bd68c329d..69d26ba67 100644 --- a/operator/api/v1alpha1/replicationpolicy_types.go +++ b/operator/api/v1alpha1/replicationpolicy_types.go @@ -35,7 +35,7 @@ type ReplicationPolicySpec struct { // +optional Mode string `json:"mode,omitempty"` - // Interval is how often a replication snapshot is taken (e.g., "5m", "1h"). + // Interval is how often a replication snapshot is taken (e.g., `5m`, `1h`). // +kubebuilder:default="5m" // +optional Interval string `json:"interval,omitempty"` diff --git a/operator/api/v1alpha1/storagebackup_types.go b/operator/api/v1alpha1/storagebackup_types.go index 6ef3387a7..5296f59c3 100644 --- a/operator/api/v1alpha1/storagebackup_types.go +++ b/operator/api/v1alpha1/storagebackup_types.go @@ -86,7 +86,7 @@ type StorageBackupStatus struct { LvolID string `json:"lvolID,omitempty"` // LvolName is the backend logical volume name. LvolName string `json:"lvolName,omitempty"` - // FSType is the filesystem type of the source PersistentVolume (e.g., "ext4", + // FSType is the filesystem type of the source PersistentVolume (e.g., `ext4`, // "xfs"), captured at backup time so a restore can preserve it regardless of // which StorageClass the restored PVC ends up using. FSType string `json:"fsType,omitempty"` diff --git a/operator/api/v1alpha1/storagenode_types.go b/operator/api/v1alpha1/storagenode_types.go index df3167edb..8658d2b97 100644 --- a/operator/api/v1alpha1/storagenode_types.go +++ b/operator/api/v1alpha1/storagenode_types.go @@ -39,7 +39,7 @@ type StorageNodeOverrides struct { SpdkProxyImage string `json:"spdkProxyImage,omitempty"` // SpdkSystemMemory overrides the SPDK huge-page memory allocation for this node - // (e.g., "4G", "512M"). + // (e.g., `4G`, `512M`). // +kubebuilder:validation:Pattern=`^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$` // +optional SpdkSystemMemory string `json:"spdkSystemMemory,omitempty"` @@ -114,7 +114,7 @@ type StorageNodeSpec struct { // +kubebuilder:validation:Required WorkerNode string `json:"workerNode"` - // SocketID is the NUMA socket identifier from spec.socketsToUse (e.g., "0", "1"). Immutable. + // SocketID is the NUMA socket identifier from spec.socketsToUse (e.g., `0`, `1`). Immutable. // +k8s:immutable // +optional SocketID string `json:"socketId,omitempty"` diff --git a/operator/api/v1alpha1/storagenodeops_types.go b/operator/api/v1alpha1/storagenodeops_types.go index e7c0f5313..f28fd6302 100644 --- a/operator/api/v1alpha1/storagenodeops_types.go +++ b/operator/api/v1alpha1/storagenodeops_types.go @@ -55,7 +55,7 @@ const ( // restart is asynchronous, so the op only advances to Promoting after the // node has left online (restart started) and returned to online (restart // finished) — issuing /promote earlier races the in-flight restart's node - // writes and leaves the relocated devices stuck in "new". + // writes and leaves the relocated devices stuck in `new`. StorageNodeOpsSubPhaseRestarting StorageNodeOpsSubPhase = "Restarting" // StorageNodeOpsSubPhasePromoting marks that a migrate op has issued the // control-plane /promote for the relocated node (guards against re-promoting). @@ -67,7 +67,7 @@ type DrainOpsSpec struct { // SystemVolumeFilterRegex is a Go regular expression matched against backend // volume names. Matching volumes are treated as system volumes: excluded from // drain migration and deleted inline during the Verifying phase. - // Defaults to "^sb-fio-baseline-.*". + // Defaults to `^sb-fio-baseline-.*`. // +optional SystemVolumeFilterRegex *string `json:"systemVolumeFilterRegex,omitempty"` } diff --git a/operator/api/v1alpha1/storagenodeset_types.go b/operator/api/v1alpha1/storagenodeset_types.go index ce17f4281..bd9b10ed0 100644 --- a/operator/api/v1alpha1/storagenodeset_types.go +++ b/operator/api/v1alpha1/storagenodeset_types.go @@ -124,7 +124,7 @@ type StorageNodeSetSpec struct { ReservedSystemCPU string `json:"reservedSystemCPU,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="SPDK System Memory" // +kubebuilder:validation:Pattern=`^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$` - // SpdkSystemMemory is the amount of memory reserved for SPDK system use (e.g., "4G", "512M"). + // SpdkSystemMemory is the amount of memory reserved for SPDK system use (e.g., `4G`, `512M`). // When omitted the backend default is used. SpdkSystemMemory string `json:"spdkSystemMemory,omitempty"` @@ -240,19 +240,19 @@ type StorageNodeSetStatus struct { // TotalNodes is the total number of owned StorageNode CRs. // +optional TotalNodes int `json:"totalNodes,omitempty"` - // OnlineNodes is the count of StorageNode CRs with status "online". + // OnlineNodes is the count of StorageNode CRs with status `online`. // +optional OnlineNodes int `json:"onlineNodes,omitempty"` - // OfflineNodes is the count of StorageNode CRs with status "offline". + // OfflineNodes is the count of StorageNode CRs with status `offline`. // +optional OfflineNodes int `json:"offlineNodes,omitempty"` - // SuspendedNodes is the count of StorageNode CRs with status "suspended". + // SuspendedNodes is the count of StorageNode CRs with status `suspended`. // +optional SuspendedNodes int `json:"suspendedNodes,omitempty"` - // CreatingNodes is the count of StorageNode CRs with status "in_creation". + // CreatingNodes is the count of StorageNode CRs with status `in_creation`. // +optional CreatingNodes int `json:"creatingNodes,omitempty"` - // RemovedNodes is the count of StorageNode CRs with status "removed". + // RemovedNodes is the count of StorageNode CRs with status `removed`. // +optional RemovedNodes int `json:"removedNodes,omitempty"` diff --git a/operator/api/v1alpha1/storagepool_types.go b/operator/api/v1alpha1/storagepool_types.go index cc1f69351..b6f9237d8 100644 --- a/operator/api/v1alpha1/storagepool_types.go +++ b/operator/api/v1alpha1/storagepool_types.go @@ -98,7 +98,7 @@ type StorageClassParameters struct { // StorageClass that omits tune2fs_reserved_blocks. A default of "0" here would not be a // no-op: it actively runs `tune2fs -m 0` on every volume, since the node plugin only skips // the call when the parameter is empty (see stageVolume in the CSI driver), not when it's - // "0". + // `0`. Tune2fsReservedBlocks string `json:"tune2fsReservedBlocks,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Filesystem" // Filesystem is the filesystem used to format logical volumes of this pool. diff --git a/operator/cmd/simplyblock-rebalancer/main.go b/operator/cmd/simplyblock-rebalancer/main.go index 10a0b5608..97c95d084 100644 --- a/operator/cmd/simplyblock-rebalancer/main.go +++ b/operator/cmd/simplyblock-rebalancer/main.go @@ -422,7 +422,7 @@ func connectAndWait(ctx context.Context, conn connConfig) (device string, discon // Retry the connect rather than failing the whole run on the first attempt. The // volume's NVMe-oF target is often not yet accepting connections the instant this // runs (the Job/probe can start before the subsystem listener is ready), so the - // first attempt fails fast with "connection refused" / "no such subsystem". Without + // first attempt fails fast with `connection refused` / `no such subsystem`. Without // this, a baseline Job errors out and only succeeds after several controller-driven // recreations — the long-standing "jobs need multiple iterations" behavior. var connErr error diff --git a/operator/internal/autoplacement/logical_volume_selector.go b/operator/internal/autoplacement/logical_volume_selector.go index 52ea6b95b..b161503de 100644 --- a/operator/internal/autoplacement/logical_volume_selector.go +++ b/operator/internal/autoplacement/logical_volume_selector.go @@ -294,7 +294,7 @@ type managedVolume struct { // BuildCSIManagedVolumes returns the simplyblock CSI-managed volumes in the given // cluster, derived from the PersistentVolumes. The rebalancer only ever acts on // PV/PVC-managed volumes; backend-only volumes (e.g., the per-node benchmark probes -// "simplyblock-rebalancer-", which have no PV) are therefore never +// `simplyblock-rebalancer-`, which have no PV) are therefore never // candidates. Pass an empty clusterUUID to include all clusters. // // The List is filtered server-side (via the cache field index) to PVs whose CSI diff --git a/operator/internal/autoplacement/utils.go b/operator/internal/autoplacement/utils.go index fe13d7d80..d6e430884 100644 --- a/operator/internal/autoplacement/utils.go +++ b/operator/internal/autoplacement/utils.go @@ -79,7 +79,7 @@ type RebalancingConfig struct { BaselineWindow time.Duration // BaselineStep is the range-query step, matching the probe publish cadence. BaselineStep time.Duration - // BaselineColdStart is the under-sampled-node policy: "partialWindow" (default) or "defer". + // BaselineColdStart is the under-sampled-node policy: `partialWindow` (default) or `defer`. BaselineColdStart string // BaselineMinSamples is the sample count below which a node is treated as under-sampled. BaselineMinSamples int diff --git a/operator/internal/controller/backuppolicy_controller.go b/operator/internal/controller/backuppolicy_controller.go index e8ad304d4..078b91cd5 100644 --- a/operator/internal/controller/backuppolicy_controller.go +++ b/operator/internal/controller/backuppolicy_controller.go @@ -497,7 +497,7 @@ func (r *BackupPolicyReconciler) attachPolicy( // detachPolicy calls the backend to detach the policy from a single lvol. // // The sbcli detach endpoint returns HTTP 400 (not 404) when the attachment -// does not exist, with the body containing "Attachment not found". We treat +// does not exist, with the body containing `Attachment not found`. We treat // this as success to make the operation idempotent — if the attachment is // already gone the desired state is already achieved. func (r *BackupPolicyReconciler) detachPolicy( diff --git a/operator/internal/controller/benchmark_provisioner.go b/operator/internal/controller/benchmark_provisioner.go index 71c003ee3..92a0c8f8d 100644 --- a/operator/internal/controller/benchmark_provisioner.go +++ b/operator/internal/controller/benchmark_provisioner.go @@ -45,14 +45,14 @@ type BenchmarkProvisioner interface { EnsureVolume(ctx context.Context, namespace, clusterName, poolUUID, volumeName, nodeUUID string) (volumeUUID string, err error) // BenchmarkNQN returns the NVMe-oF NQN for the benchmark volume. - // Both implementations use the same formula: "{clusterNQN}:lvol:{volumeUUID}". + // Both implementations use the same formula: `{clusterNQN}:lvol:{volumeUUID}`. BenchmarkNQN(clusterNQN, volumeUUID string) string } // AutomaticBenchmarkProvisioner is the no-op production implementation. // It assumes the storage pool and per-node benchmark volumes are created automatically // during cluster setup. The benchmark volume's logical-volume ID equals the storage node -// UUID, so EnsureVolume returns nodeUUID and EnsurePool returns "". +// UUID, so EnsureVolume returns nodeUUID and EnsurePool returns `""`. type AutomaticBenchmarkProvisioner struct{} func (*AutomaticBenchmarkProvisioner) EnsurePool(_ context.Context, _, _ string) (string, error) { diff --git a/operator/internal/controller/nodedrain_controller_unit_test.go b/operator/internal/controller/nodedrain_controller_unit_test.go index 23102acfb..bf7675414 100644 --- a/operator/internal/controller/nodedrain_controller_unit_test.go +++ b/operator/internal/controller/nodedrain_controller_unit_test.go @@ -356,7 +356,7 @@ func TestFdDrainGate2DomainsWholeDomainPlusOtherIsUnsafe(t *testing.T) { func TestFdDrainGate2DomainsOnePerDomainPlusExtraInEitherIsUnsafe(t *testing.T) { // 1 node down in each of domains 1 and 2 already (the safe combo) -- // piling a SECOND node onto EITHER domain must now be blocked, even - // though that domain is already "active". This is the exact gap the old + // though that domain is already `active`. This is the exact gap the old // unconditional-piling logic missed. counts := map[int32]int{1: 1, 2: 1} if blocked, _ := fdDrainGate(counts, 2, 2, 4, 2); !blocked { diff --git a/operator/internal/controller/replicationops_controller.go b/operator/internal/controller/replicationops_controller.go index 52b6a1b48..2d91fbed9 100644 --- a/operator/internal/controller/replicationops_controller.go +++ b/operator/internal/controller/replicationops_controller.go @@ -576,7 +576,7 @@ func (r *ReplicationOpsReconciler) reconcileFailback( // reconcileFailbackWait polls slots until every one is replicating/source, then // marks the ReplicationOps Succeeded. It is entered on every reconcile when -// subphase == "WaitingForSlots". +// subphase == `WaitingForSlots`. func (r *ReplicationOpsReconciler) reconcileFailbackWait( ctx context.Context, ops *simplyblockv1alpha1.ReplicationOps, diff --git a/operator/internal/controller/replicationops_controller_unit_test.go b/operator/internal/controller/replicationops_controller_unit_test.go index 62412b972..8afb8de33 100644 --- a/operator/internal/controller/replicationops_controller_unit_test.go +++ b/operator/internal/controller/replicationops_controller_unit_test.go @@ -56,7 +56,7 @@ func newOpsReplReconciler(t *testing.T, objects ...client.Object) (*ReplicationO } // readyPairForOps returns a ReplicationPair named "pair1" that is ready. -// All policies created by readyPolicyForOps reference this pair via PairRef: "pair1". +// All policies created by readyPolicyForOps reference this pair via PairRef: `pair1`. func readyPairForOps() *simplyblockv1alpha1.ReplicationPair { return &simplyblockv1alpha1.ReplicationPair{ ObjectMeta: metav1.ObjectMeta{Name: "pair1", Namespace: "default"}, diff --git a/operator/internal/controller/replicationpolicy_controller.go b/operator/internal/controller/replicationpolicy_controller.go index 4876eb40b..91f4f8fa8 100644 --- a/operator/internal/controller/replicationpolicy_controller.go +++ b/operator/internal/controller/replicationpolicy_controller.go @@ -251,7 +251,7 @@ func (r *ReplicationPolicyReconciler) ensureBackendPolicy( return created.ID, nil } -// parseDurationToMinutes converts a Go duration string (e.g., "5m", "1h") to +// parseDurationToMinutes converts a Go duration string (e.g., `5m`, `1h`) to // whole minutes, clamped to a minimum of 1. func parseDurationToMinutes(s string) (int, error) { d, err := time.ParseDuration(s) diff --git a/operator/internal/controller/replicationslot_controller_unit_test.go b/operator/internal/controller/replicationslot_controller_unit_test.go index 67ade00c7..64421c05f 100644 --- a/operator/internal/controller/replicationslot_controller_unit_test.go +++ b/operator/internal/controller/replicationslot_controller_unit_test.go @@ -669,7 +669,7 @@ func TestSlot_CutoverPending_CreatesJobForConsumer(t *testing.T) { slot := newTestSlot(string(simplyblockv1alpha1.ReplicationSlotStateCutoverPending)) // The VolumeID is "cluster-id:pool-id:vol-id"; findConsumerNode looks for - // a PV whose CSI handle's third segment matches "vol-id". + // a PV whose CSI handle's third segment matches `vol-id`. pv := &corev1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{Name: "pv1"}, Spec: corev1.PersistentVolumeSpec{ diff --git a/operator/internal/controller/simplyblockstoragenodeset_controller.go b/operator/internal/controller/simplyblockstoragenodeset_controller.go index 20e2ef5ec..de5a09500 100644 --- a/operator/internal/controller/simplyblockstoragenodeset_controller.go +++ b/operator/internal/controller/simplyblockstoragenodeset_controller.go @@ -861,7 +861,7 @@ func (r *StorageNodeSetReconciler) reconcileSpdkProxyEndpointSlices( // portsWithAnyPod tracks every RPC port that has a matching pod object AT // ALL, ready or not -- computed separately from byPort (ready pods only) // so the delete pass below can tell "pod is genuinely gone" apart from - // "pod exists but isn't ready this instant". RPC_PORT is a static env var + // "pod exists but isn't ready this instant." RPC_PORT is a static env var // on the pod spec, readable the moment the pod is scheduled, well before // it ever becomes ready, so this is safe to compute from the full list. byPort := map[int32][]utils.SpdkProxyEndpoint{} diff --git a/operator/internal/controller/storageclusterops_controller.go b/operator/internal/controller/storageclusterops_controller.go index b134d40ed..092fe37ba 100644 --- a/operator/internal/controller/storageclusterops_controller.go +++ b/operator/internal/controller/storageclusterops_controller.go @@ -460,8 +460,8 @@ func (r *StorageClusterOpsReconciler) reconcileStart( } // reconcileRestart handles a two-phase cluster restart: POST /shutdown, wait -// until the cluster leaves "active", then POST /start and wait until it returns -// to "active". The sub-phase is tracked in ops.Status.Message. +// until the cluster leaves `active`, then POST /start and wait until it returns +// to `active`. The sub-phase is tracked in ops.Status.Message. func (r *StorageClusterOpsReconciler) reconcileRestart( ctx context.Context, ops *simplyblockv1alpha1.StorageClusterOps, diff --git a/operator/internal/controller/storagenode_controller.go b/operator/internal/controller/storagenode_controller.go index 381f8f6ea..1370c67a0 100644 --- a/operator/internal/controller/storagenode_controller.go +++ b/operator/internal/controller/storagenode_controller.go @@ -1002,7 +1002,7 @@ func effectiveFailureDomainSet(sn *simplyblockv1alpha1.StorageNode, sns *simplyb // StorageNode.spec.overrides.failureDomain takes precedence over // StorageNodeSet.spec.nodeFailureDomains[worker]. Only meaningful when // effectiveFailureDomainSet reports true -- the zero return here also covers -// "unset", so callers that must distinguish the two (e.g., anything crossing +// `unset`, so callers that must distinguish the two (e.g., anything crossing // a JSON boundary, where 0 and absent are different wire values) should use // effectiveFailureDomainPtr instead. func effectiveFailureDomain(sn *simplyblockv1alpha1.StorageNode, sns *simplyblockv1alpha1.StorageNodeSet) int { diff --git a/operator/internal/controller/storagenode_latency_controller.go b/operator/internal/controller/storagenode_latency_controller.go index ac255f38b..0a5934137 100644 --- a/operator/internal/controller/storagenode_latency_controller.go +++ b/operator/internal/controller/storagenode_latency_controller.go @@ -220,7 +220,7 @@ func (r *StorageNodeLatencyReconciler) processNodeBaseline( Port: logicalVolumeConnectionPort(node), } // The lvol's NVMe-oF subsystem listens on the node's data NIC, not its management - // IP, so targeting node.MgmtIp fails with "connection refused". Resolve the node's + // IP, so targeting node.MgmtIp fails with `connection refused`. Resolve the node's // data-network address from the /nics endpoint; fall back to the management address // only when it cannot be resolved. if dataAddr, err := r.nodeDataAddr(ctx, clusterCR.Status.UUID, node.UUID); err != nil { diff --git a/operator/internal/controller/storagenodeops_controller.go b/operator/internal/controller/storagenodeops_controller.go index 2b6e9a5be..54f593f4d 100644 --- a/operator/internal/controller/storagenodeops_controller.go +++ b/operator/internal/controller/storagenodeops_controller.go @@ -1095,7 +1095,7 @@ func (r *StorageNodeOpsReconciler) drainValidate( // first early out: a no-op when the cluster doesn't have failure domains // enabled at all. Re-fetches the parent StorageNodeSet (and StorageCluster) // rather than threading them through runDrain's whole dispatch chain -- -// Validating is the only sub-phase that needs them. Returns ("", nil) when +// Validating is the only sub-phase that needs them. Returns (`""`, nil) when // removal is fine (including when FD data isn't populated yet, same as the // backend's own early outs); a non-empty reason means drainValidate must // fail rather than advance to Suspending. diff --git a/operator/internal/controller/storagenodeops_controller_unit_test.go b/operator/internal/controller/storagenodeops_controller_unit_test.go index f445c1a72..76055f9cb 100644 --- a/operator/internal/controller/storagenodeops_controller_unit_test.go +++ b/operator/internal/controller/storagenodeops_controller_unit_test.go @@ -545,7 +545,7 @@ func TestResolveOpsSystemVolumeFilter_InvalidPatternReturnsError(t *testing.T) { // EndpointSlice builder and the migrate flow's DNS gate: a slice built by // BuildStorageNodeSetEndpointSlice must be found by endpointSliceHasWorker. The // two independently encoded the slice name and hostname, and a rename that -// touched only the builder silently wedged migrations at "waiting for DNS". +// touched only the builder silently wedged migrations at `waiting for DNS`. func TestEndpointSliceHasWorker_MatchesBuilderOutput(t *testing.T) { const ns = "test" const worker = "worker-5.ocp.simplyblock.ai" diff --git a/operator/internal/controller/volumemigration_controller.go b/operator/internal/controller/volumemigration_controller.go index 0387efcf4..f9a26507a 100644 --- a/operator/internal/controller/volumemigration_controller.go +++ b/operator/internal/controller/volumemigration_controller.go @@ -601,7 +601,7 @@ func (r *VolumeMigrationReconciler) pollValidationJobs( // Every recorded node is asked, not only the ones that passed. Release is idempotent and // declines to touch a path that is serving, so asking a node that already released costs // one Job and reports nothing; guessing which nodes still hold paths would mean trusting -// Succeeded to mean "connected", which it does not — a Job killed mid-run leaves paths +// Succeeded to mean `connected`, which it does not — a Job killed mid-run leaves paths // with no record of them at all. // // Best effort, and deliberately not waited on: the migration's outcome is already decided diff --git a/operator/internal/controller/volumemigration_controller_unit_test.go b/operator/internal/controller/volumemigration_controller_unit_test.go index 418d6bc0b..109dd1d2c 100644 --- a/operator/internal/controller/volumemigration_controller_unit_test.go +++ b/operator/internal/controller/volumemigration_controller_unit_test.go @@ -509,7 +509,7 @@ func TestReconcileStart_VolumeWithoutNQN_Fails(t *testing.T) { // // Run for both shapes the control plane returns: a migration of a shared subsystem // reports status "running" from the moment it is created, where a single-namespace one -// starts at "new". Neither is terminal, so both must still be continued. +// starts at `new`. Neither is terminal, so both must still be continued. func TestPerformMigration_AddressesMigrationBySubsystem(t *testing.T) { for _, tc := range []struct { name string diff --git a/operator/internal/controller/volumemigration_helpers_test.go b/operator/internal/controller/volumemigration_helpers_test.go index c4194402e..00f19711e 100644 --- a/operator/internal/controller/volumemigration_helpers_test.go +++ b/operator/internal/controller/volumemigration_helpers_test.go @@ -488,7 +488,7 @@ func TestCollectAndLogJobPodLogs_NoPods(t *testing.T) { // Regression test for a validation loop that never converged: each Job was deleted the // moment it passed while its status entry stayed, so the next pass read NotFound, -// called the Job "vanished", dropped the entry and rebuilt it — endlessly. Worse, the +// called the Job `vanished`, dropped the entry and rebuilt it — endlessly. Worse, the // shrinking entry list let the gate declare "all validation jobs succeeded" for a // subset, cutting over with an unvalidated node. // @@ -534,7 +534,7 @@ func TestPollValidationJobs_PassedNodeIsNotRevalidated(t *testing.T) { } // The passed node's Job is now reaped, as its TTL would do. A second pass must - // treat that node as done rather than "vanished", and must not create a new Job. + // treat that node as done rather than `vanished`, and must not create a new Job. if err := cl.Delete(context.Background(), passed); err != nil { t.Fatalf("delete the passed job: %v", err) } diff --git a/operator/internal/controller/volumerebalancer_realignment_test.go b/operator/internal/controller/volumerebalancer_realignment_test.go index 40e406c7d..fe5ddfd78 100644 --- a/operator/internal/controller/volumerebalancer_realignment_test.go +++ b/operator/internal/controller/volumerebalancer_realignment_test.go @@ -112,7 +112,7 @@ func TestResolveDataRealignmentConfig(t *testing.T) { wantMinMoves: 10, }, { - // Zero would mean "realign when nothing has moved", which is not a + // Zero would mean "realign when nothing has moved," which is not a // meaningful request; fall back rather than spin. name: "zero minMoves falls back to default", vms: &simplyblockv1alpha1.VolumeMigrationSettings{ diff --git a/operator/internal/cpinformer/sse.go b/operator/internal/cpinformer/sse.go index ec50a8100..993dc6b10 100644 --- a/operator/internal/cpinformer/sse.go +++ b/operator/internal/cpinformer/sse.go @@ -31,7 +31,7 @@ type sseEvent struct { // // It implements the parsing rules relevant to this contract: `field: value` // lines (a single leading space after the colon is stripped), multi-line -// `data` joined with "\n", comment lines (leading ":"), and dispatch on a blank +// `data` joined with `"\n"`, comment lines (leading `:`), and dispatch on a blank // line. `id:` and `retry:` are accepted and ignored — the contract emits no // `id:`, and reconnect backoff is handled by the caller. A trailing event not // terminated by a blank line is discarded, per the SSE specification. diff --git a/operator/internal/cpinformer/store_test.go b/operator/internal/cpinformer/store_test.go index b85b0b975..29567b1a0 100644 --- a/operator/internal/cpinformer/store_test.go +++ b/operator/internal/cpinformer/store_test.go @@ -57,7 +57,7 @@ func TestStoreReplaceDeltas(t *testing.T) { s.Upsert(scope, item{ID: "b"}) s.Upsert(scope, item{ID: "c"}) - // Snapshot drops "b", keeps "a", adds "d". + // Snapshot drops `b`, keeps `a`, adds `d`. present, removed := s.Replace(scope, []item{{ID: "a"}, {ID: "c"}, {ID: "d"}}) sort.Strings(present) diff --git a/operator/internal/csilink/hub.go b/operator/internal/csilink/hub.go index d4cbc9fa1..d65d305db 100644 --- a/operator/internal/csilink/hub.go +++ b/operator/internal/csilink/hub.go @@ -30,7 +30,7 @@ import ( // Config configures the operator's hub. type Config struct { - // BindAddress is where peers dial, e.g., ":9500". + // BindAddress is where peers dial, e.g., `:9500`. BindAddress string // CertFile and KeyFile are the hub's serving certificate. Both are diff --git a/operator/internal/volumemigration/paths.go b/operator/internal/volumemigration/paths.go index 0073f371d..91ad16a4d 100644 --- a/operator/internal/volumemigration/paths.go +++ b/operator/internal/volumemigration/paths.go @@ -17,7 +17,7 @@ type PathState struct { Address string // ":" // Present is false when no controller for this address exists at all. Present bool - // State is the controller's kernel state: "live", "connecting", "resetting", ... + // State is the controller's kernel state: `live`, `connecting`, `resetting`, ... // Only a live controller can carry I/O. State string // ANAStates are the ANA states of this controller's paths to the subsystem's diff --git a/operator/internal/volumemigration/paths_test.go b/operator/internal/volumemigration/paths_test.go index dbce910a4..348cc5885 100644 --- a/operator/internal/volumemigration/paths_test.go +++ b/operator/internal/volumemigration/paths_test.go @@ -279,7 +279,7 @@ func TestPresentAddresses(t *testing.T) { if err != nil { t.Fatalf("PresentAddresses: %v", err) } - // Both count as present: presence is about "was this address here before", not + // Both count as present: presence is about "was this address here before," not // about health. for _, addr := range []string{"10.0.0.114:4428", "10.0.0.112:4428"} { if !got[addr] { diff --git a/operator/internal/volumemigration/subsystem.go b/operator/internal/volumemigration/subsystem.go index 1432ee2ca..14ff1b751 100644 --- a/operator/internal/volumemigration/subsystem.go +++ b/operator/internal/volumemigration/subsystem.go @@ -13,7 +13,7 @@ import ( // whose consumer disappeared between that decision and the Job starting has nothing // left to validate. // -// A lookup that cannot be trusted is an error rather than "absent", because the two +// A lookup that cannot be trusted is an error rather than `absent`, because the two // are not equally safe: reporting a connected host as unconnected would let the // migration cut over without switching that host's paths, which is the outage this // check exists to prevent. An empty subsystem list is treated the same way — on a node diff --git a/operator/internal/volumemigration/subsystem_test.go b/operator/internal/volumemigration/subsystem_test.go index d003c8743..5f05fd5a3 100644 --- a/operator/internal/volumemigration/subsystem_test.go +++ b/operator/internal/volumemigration/subsystem_test.go @@ -51,7 +51,7 @@ func TestHostHasSubsystem(t *testing.T) { // The dangerous direction: reporting "not connected" for a host that is in fact // connected would let the migration cut over without switching its paths. An - // unreadable or empty sysfs must therefore be an error, never a clean "absent". + // unreadable or empty sysfs must therefore be an error, never a clean `absent`. t.Run("sysfs not visible is an error, not absence", func(t *testing.T) { for _, tc := range []struct { name string diff --git a/operator/internal/volumemigration/utils.go b/operator/internal/volumemigration/utils.go index 67b017f91..ccc4968c2 100644 --- a/operator/internal/volumemigration/utils.go +++ b/operator/internal/volumemigration/utils.go @@ -60,7 +60,7 @@ func StartMigration( // findPVForVolume returns the PV name backing the given simplyblock logical-volume // UUID. simplyblock CSI volume handles have the form -// "::", so the bare volume UUID is matched +// `::`, so the bare volume UUID is matched // against the final ":"-separated segment. An exact match against the whole // handle is also accepted for robustness. func findPVForVolume( diff --git a/operator/internal/volumemigration/validate_test.go b/operator/internal/volumemigration/validate_test.go index f670fe716..56365f8c2 100644 --- a/operator/internal/volumemigration/validate_test.go +++ b/operator/internal/volumemigration/validate_test.go @@ -8,7 +8,7 @@ import ( // The connect flags themselves are atlas's to render; what stays this package's // responsibility is the mapping onto them — in particular that an unset tuning -// value reaches atlas as unset. A zero must mean "leave the kernel default", not +// value reaches atlas as unset. A zero must mean "leave the kernel default," not // "ask for 0": nvme-cli rejects some zero values outright, and 0 is a meaningful // request for the two timeouts (fail I/O immediately), so the difference is not // cosmetic. diff --git a/operator/internal/webhook/simplyblock_rebalancer_injector.go b/operator/internal/webhook/simplyblock_rebalancer_injector.go index 2b59d9622..14d511f27 100644 --- a/operator/internal/webhook/simplyblock_rebalancer_injector.go +++ b/operator/internal/webhook/simplyblock_rebalancer_injector.go @@ -132,7 +132,7 @@ func (h *SimplyblockRebalancerInjector) resolveConfig( } // clusterUUIDFromPodName extracts the cluster UUID prefix from the snode-spdk pod -// name pattern "snode-spdk-pod--". +// name pattern `snode-spdk-pod--`. func clusterUUIDFromPodName( podName string, ) string { From a3d824b2f62c52a43545056f380b3a23ceecb9a8 Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Thu, 10 Sep 2026 22:23:35 +0200 Subject: [PATCH 15/19] ci: leave the MegaLinter workflow untriggered for now The configuration is worth landing and the findings are worth reading, but a job that runs for tens of minutes on every push to main and every pull request, to report roughly a thousand findings nobody is working down yet, teaches people to scroll past it. It runs by hand until that reading has happened. workflow_dispatch keeps it available in the meantime. The file records what it should become, which is a push and a pull_request trigger on main, and says that DISABLE_ERRORS comes out of .mega-linter.yml at the same time so a finding fails the job rather than being noted in it. --- .github/workflows/mega_linter.yml | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/.github/workflows/mega_linter.yml b/.github/workflows/mega_linter.yml index d3c500360..118b5bd56 100644 --- a/.github/workflows/mega_linter.yml +++ b/.github/workflows/mega_linter.yml @@ -10,17 +10,25 @@ --- name: MegaLinter -# A full-codebase run takes tens of minutes, so it is spent on the two places a -# result is acted on: the merge target, and the pull request proposing a merge -# into it. +# Nothing triggers this yet. The gate reports roughly a thousand findings, and +# every one of them still has to be read before a run on main or on a pull +# request tells anybody something they should act on. Running it in the meantime +# would spend tens of minutes per push to print a number nobody is working +# down, which is how a gate teaches people to ignore it. +# +# workflow_dispatch keeps it runnable by hand while that reading happens. What +# it should become is the two places a result is acted on: +# +# on: +# push: +# branches: [main] +# pull_request: +# branches: [main] +# +# and DISABLE_ERRORS should come out of .mega-linter.yml at the same time, so +# that a finding fails the job rather than being noted in it. on: - push: - branches: - - main - - pull_request: - branches: - - main + workflow_dispatch: concurrency: group: ${{ github.ref }}-${{ github.workflow }} From bbffe85f0ce94428819c73ab31646aa0a471439d Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Thu, 10 Sep 2026 22:38:27 +0200 Subject: [PATCH 16/19] chore(operator): regenerate what the comment changes invalidated Three CI checks failed on drift, all of them for the same reason: a doc comment on an API type is not only a comment. controller-gen copies it into the CRD's description, so editing the comments in operator/api/v1alpha1 rewrote the generated schemas, and the checked-in copies no longer matched. Regenerated: operator/config/crd/bases, the embedded manifests under operator/internal/upgrade/crds, the chart's crds/ through `make helm-sync`, and dist/install.yaml. Every changed line is description text. Running the generators a second time changes nothing further. One description is not a straight propagation. `Fabric is the transport fabric (e.g. tcp)` had become `(e.g., TCP)`, which put the protocol in prose next to a field whose own default is `tcp`, and `kubectl explain` shows the two together. It reads `(e.g., `tcp`)` now: the comma the rule asks for, and the value in backticks, which is what the field actually takes. Backticks in a description are not new here. The neighboring text already carried `tune2fs -m 0` that way before this branch. --- ...storage.simplyblock.io_backuppolicies.yaml | 6 +- ...storage.simplyblock.io_backuprestores.yaml | 2 +- .../storage.simplyblock.io_controlplanes.yaml | 4 +- ...storage.simplyblock.io_replicationops.yaml | 6 +- ...ge.simplyblock.io_replicationpolicies.yaml | 2 +- ...storage.simplyblock.io_storagebackups.yaml | 6 +- ...rage.simplyblock.io_storageclusterops.yaml | 2 +- ...storage.simplyblock.io_storagenodeops.yaml | 2 +- .../storage.simplyblock.io_storagenodes.yaml | 12 ++-- ...torage.simplyblock.io_storagenodesets.yaml | 26 +++---- .../storage.simplyblock.io_storagepools.yaml | 4 +- operator/api/v1alpha1/storagepool_types.go | 2 +- ...storage.simplyblock.io_backuppolicies.yaml | 6 +- ...storage.simplyblock.io_backuprestores.yaml | 2 +- .../storage.simplyblock.io_controlplanes.yaml | 4 +- ...storage.simplyblock.io_replicationops.yaml | 6 +- ...ge.simplyblock.io_replicationpolicies.yaml | 2 +- ...storage.simplyblock.io_storagebackups.yaml | 6 +- ...rage.simplyblock.io_storageclusterops.yaml | 2 +- ...storage.simplyblock.io_storagenodeops.yaml | 2 +- .../storage.simplyblock.io_storagenodes.yaml | 12 ++-- ...torage.simplyblock.io_storagenodesets.yaml | 26 +++---- .../storage.simplyblock.io_storagepools.yaml | 4 +- operator/dist/install.yaml | 72 +++++++++---------- ...storage.simplyblock.io_backuppolicies.yaml | 6 +- ...storage.simplyblock.io_backuprestores.yaml | 2 +- .../storage.simplyblock.io_controlplanes.yaml | 4 +- ...storage.simplyblock.io_replicationops.yaml | 6 +- ...ge.simplyblock.io_replicationpolicies.yaml | 2 +- ...storage.simplyblock.io_storagebackups.yaml | 6 +- ...rage.simplyblock.io_storageclusterops.yaml | 2 +- ...storage.simplyblock.io_storagenodeops.yaml | 2 +- .../storage.simplyblock.io_storagenodes.yaml | 12 ++-- ...torage.simplyblock.io_storagenodesets.yaml | 26 +++---- .../storage.simplyblock.io_storagepools.yaml | 4 +- 35 files changed, 145 insertions(+), 145 deletions(-) diff --git a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_backuppolicies.yaml b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_backuppolicies.yaml index 377b797ea..d2d743334 100644 --- a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_backuppolicies.yaml +++ b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_backuppolicies.yaml @@ -40,7 +40,7 @@ spec: BackupPolicy defines retention and scheduling parameters for simplyblock\nbackups. To apply a policy to a PVC, annotate the PVC with:\n\n\tsimplyblock.io/backup-policy: \n\nThe deprecated simplybk/backup-policy annotation - is still honoured for\nbackwards compatibility; when both are set, simplyblock.io/backup-policy\ntakes + is still honored for\nbackward compatibility; when both are set, simplyblock.io/backup-policy\ntakes precedence.\n\nThe BackupPolicy must be in the same namespace as the annotated PVC.\nThe controller attaches and detaches the policy in the simplyblock backend\nwhenever the annotation is added or removed." @@ -73,7 +73,7 @@ spec: rule: self == oldSelf maxAge: description: |- - MaxAge is the maximum age of backups to retain (e.g. "7d", "12h", "30m"). + MaxAge is the maximum age of backups to retain (e.g., `7d`, `12h`, `30m`). Backups older than this are merged. Accepts m, h, d, w suffixes. pattern: ^[1-9]\d*[mhdw]$ type: string @@ -91,7 +91,7 @@ spec: schedule: description: |- Schedule defines the tiered backup schedule as a space-separated list of - interval,keep_count pairs (e.g. "15m,4 60m,11 24h,7"). + interval,keep_count pairs (e.g., "15m,4 60m,11 24h,7"). Intervals must be strictly increasing. Supported units: m, h, d, w. pattern: ^(\d+[mhdw],\d+)( +\d+[mhdw],\d+)*$ type: string diff --git a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_backuprestores.yaml b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_backuprestores.yaml index fd1750971..d1b427c5a 100644 --- a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_backuprestores.yaml +++ b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_backuprestores.yaml @@ -349,7 +349,7 @@ spec: description: PVCNamespace is the namespace of the created PVC. type: string restoredLvolID: - description: RestoredLvolID is the UUID of the newly-created logical + description: RestoredLvolID is the UUID of the newly created logical volume. type: string sourceClusterUUID: diff --git a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_controlplanes.yaml b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_controlplanes.yaml index b2356c2a0..cff5125d0 100644 --- a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_controlplanes.yaml +++ b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_controlplanes.yaml @@ -60,7 +60,7 @@ spec: image: description: |- Image is the container image used for all simplyblock control-plane and - storage-node workloads (e.g. quay.io/simplyblock-io/simplyblock:26.2.2). + storage-node workloads (e.g., quay.io/simplyblock-io/simplyblock:26.2.2). StorageNodeSet CRs that omit spec.clusterImage inherit this value. Must reference one of the trusted registries (quay.io/simplyblock-io, docker.io/simplyblock, public.ecr.aws/simply-block); digest pinning (@sha256:...) is recommended. pattern: ^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$ @@ -79,7 +79,7 @@ spec: message: description: |- Message contains a human-readable explanation of the current phase, - for example the FDB error returned by the health endpoint. + for example, the FDB error returned by the health endpoint. type: string phase: description: |- diff --git a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_replicationops.yaml b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_replicationops.yaml index 7f7ea56be..ac1ae9947 100644 --- a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_replicationops.yaml +++ b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_replicationops.yaml @@ -67,8 +67,8 @@ spec: action: description: |- Action is the operation to perform. Immutable. - failover: unplanned — promote target clone, source may be down. - failback: restore source as primary after a prior failover. + failover: unplanned — promote target clone, source may be down. + failback: restore source as primary after a prior failover. migration: planned cutover — calls replication_commit per volume; both clusters stay up. State progression: replicating → cutover_pending → cutover_done. enum: @@ -165,7 +165,7 @@ spec: subphase: description: |- Subphase describes what the operation is currently doing within the phase - (e.g. "TriggeringFailover", "UpdatingSlotStatuses", "ReleasingLock"). + (e.g., `TriggeringFailover`, `UpdatingSlotStatuses`, `ReleasingLock`). type: string type: object type: object diff --git a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_replicationpolicies.yaml b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_replicationpolicies.yaml index 41412abc9..7994c0dc2 100644 --- a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_replicationpolicies.yaml +++ b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_replicationpolicies.yaml @@ -69,7 +69,7 @@ spec: interval: default: 5m description: Interval is how often a replication snapshot is taken - (e.g. "5m", "1h"). + (e.g., `5m`, `1h`). type: string mode: default: failover diff --git a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagebackups.yaml b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagebackups.yaml index a1f8731eb..5c95ad517 100644 --- a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagebackups.yaml +++ b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagebackups.yaml @@ -77,7 +77,7 @@ spec: - name type: object snapshotName: - description: SnapshotName optionally overrides the internally-created + description: SnapshotName optionally overrides the internally created snapshot name. type: string sourceClusterUUID: @@ -119,7 +119,7 @@ spec: type: string fsType: description: |- - FSType is the filesystem type of the source PersistentVolume (e.g. "ext4", + FSType is the filesystem type of the source PersistentVolume (e.g., `ext4`, "xfs"), captured at backup time so a restore can preserve it regardless of which StorageClass the restored PVC ends up using. type: string @@ -165,7 +165,7 @@ spec: format: int64 type: integer snapshotID: - description: SnapshotID is the internally-created snapshot UUID used + description: SnapshotID is the internally created snapshot UUID used for the backup request. type: string snapshotName: diff --git a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storageclusterops.yaml b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storageclusterops.yaml index eb1d376bf..d67f8ffee 100644 --- a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storageclusterops.yaml +++ b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storageclusterops.yaml @@ -83,7 +83,7 @@ spec: rule: self == oldSelf nodeRollingRestart: description: |- - NodeRollingRestart configures behaviour specific to the node-rolling-restart action. + NodeRollingRestart configures behavior specific to the node-rolling-restart action. Ignored for all other actions. properties: refreshSNodeAPI: diff --git a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagenodeops.yaml b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagenodeops.yaml index b5f0d3b2e..75403e5b8 100644 --- a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagenodeops.yaml +++ b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagenodeops.yaml @@ -86,7 +86,7 @@ spec: SystemVolumeFilterRegex is a Go regular expression matched against backend volume names. Matching volumes are treated as system volumes: excluded from drain migration and deleted inline during the Verifying phase. - Defaults to "^sb-fio-baseline-.*". + Defaults to `^sb-fio-baseline-.*`. type: string type: object force: diff --git a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagenodes.yaml b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagenodes.yaml index b6a7f5f88..c1e8b69b1 100644 --- a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagenodes.yaml +++ b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagenodes.yaml @@ -86,7 +86,7 @@ spec: deviceNames: description: |- DeviceNames explicitly defines the NVMe namespace names to use on this node - (e.g. ["nvme0n1","nvme1n1"]). + (e.g., ["nvme0n1","nvme1n1"]). items: type: string type: array @@ -101,7 +101,7 @@ spec: expand: description: |- Expand marks this node as a cluster-expansion add. When true the backend - node-add endpoint receives expand=true, triggering rebalancing behaviour + node-add endpoint receives expand=true, triggering rebalancing behavior appropriate for in-place cluster growth. Overrides StorageNodeSet.spec.expand. type: boolean failureDomain: @@ -153,7 +153,7 @@ spec: type: boolean spdkImage: description: SpdkImage overrides the SPDK image for this node - (e.g. for phased rollouts). + (e.g., for phased rollouts). type: string spdkProxyImage: description: SpdkProxyImage overrides the SPDK proxy image for @@ -162,7 +162,7 @@ spec: spdkSystemMemory: description: |- SpdkSystemMemory overrides the SPDK huge-page memory allocation for this node - (e.g. "4G", "512M"). + (e.g., `4G`, `512M`). pattern: ^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$ type: string ubuntuHost: @@ -172,7 +172,7 @@ spec: type: object socketId: description: SocketID is the NUMA socket identifier from spec.socketsToUse - (e.g. "0", "1"). Immutable. + (e.g., `0`, `1`). Immutable. type: string x-kubernetes-validations: - message: field is immutable @@ -332,7 +332,7 @@ spec: type: integer type: object status: - description: Status is the backend-reported node status (e.g. online, + description: Status is the backend-reported node status (e.g., online, suspended, offline). type: string uptime: diff --git a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagenodesets.yaml b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagenodesets.yaml index f3f0e8e2e..3310c43bd 100644 --- a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagenodesets.yaml +++ b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagenodesets.yaml @@ -80,7 +80,7 @@ spec: containerResources: description: |- ContainerResources sets CPU and memory requests/limits for the main storage-node container. - When omitted no limits are enforced, which preserves the previous behaviour. + When omitted no limits are enforced, which preserves the previous behavior. properties: claims: description: |- @@ -145,7 +145,7 @@ spec: type: array deviceNames: description: DeviceNames explicitly defines a comma separated list - of nvme namespace names like nvme0n1,nvme1n1... + of NVMe namespace names like nvme0n1,nvme1n1... items: type: string type: array @@ -171,7 +171,7 @@ spec: Expand indicates that storage nodes added from this StorageNodeSet are being added to expand an already-active cluster. When true the backend node-add endpoint receives expand=true, which triggers the appropriate rebalancing - behaviour for in-place cluster growth. + behavior for in-place cluster growth. type: boolean forceFormat4K: description: ForceFormat4K forces 4K blocksize formatting of the NVMe @@ -294,7 +294,7 @@ spec: deviceNames: description: |- DeviceNames explicitly defines the NVMe namespace names to use on this node - (e.g. ["nvme0n1","nvme1n1"]). + (e.g., ["nvme0n1","nvme1n1"]). items: type: string type: array @@ -309,7 +309,7 @@ spec: expand: description: |- Expand marks this node as a cluster-expansion add. When true the backend - node-add endpoint receives expand=true, triggering rebalancing behaviour + node-add endpoint receives expand=true, triggering rebalancing behavior appropriate for in-place cluster growth. Overrides StorageNodeSet.spec.expand. type: boolean failureDomain: @@ -362,7 +362,7 @@ spec: type: boolean spdkImage: description: SpdkImage overrides the SPDK image for this node - (e.g. for phased rollouts). + (e.g., for phased rollouts). type: string spdkProxyImage: description: SpdkProxyImage overrides the SPDK proxy image for @@ -371,7 +371,7 @@ spec: spdkSystemMemory: description: |- SpdkSystemMemory overrides the SPDK huge-page memory allocation for this node - (e.g. "4G", "512M"). + (e.g., `4G`, `512M`). pattern: ^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$ type: string ubuntuHost: @@ -458,7 +458,7 @@ spec: type: string spdkSystemMemory: description: |- - SpdkSystemMemory is the amount of memory reserved for SPDK system use (e.g. "4G", "512M"). + SpdkSystemMemory is the amount of memory reserved for SPDK system use (e.g., `4G`, `512M`). When omitted the backend default is used. pattern: ^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$ type: string @@ -531,7 +531,7 @@ spec: properties: creatingNodes: description: CreatingNodes is the count of StorageNode CRs with status - "in_creation". + `in_creation`. type: integer drainCoordination: description: DrainCoordination tracks the upgrade-drain state per @@ -672,11 +672,11 @@ spec: type: array offlineNodes: description: OfflineNodes is the count of StorageNode CRs with status - "offline". + `offline`. type: integer onlineNodes: description: OnlineNodes is the count of StorageNode CRs with status - "online". + `online`. type: integer pendingNodeAdds: additionalProperties: @@ -691,7 +691,7 @@ spec: type: object removedNodes: description: RemovedNodes is the count of StorageNode CRs with status - "removed". + `removed`. type: integer schedulingFailedWorkers: additionalProperties: @@ -703,7 +703,7 @@ spec: type: object suspendedNodes: description: SuspendedNodes is the count of StorageNode CRs with status - "suspended". + `suspended`. type: integer totalNodes: description: TotalNodes is the total number of owned StorageNode CRs. diff --git a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagepools.yaml b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagepools.yaml index 1fac84998..b106c42db 100644 --- a/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagepools.yaml +++ b/helm-charts/charts/simplyblock-operator/crds/storage.simplyblock.io_storagepools.yaml @@ -141,7 +141,7 @@ spec: type: boolean fabric: default: tcp - description: Fabric is the transport fabric (e.g. tcp). + description: Fabric is the transport fabric (e.g., `tcp`). type: string filesystem: default: xfs @@ -182,7 +182,7 @@ spec: StorageClass that omits tune2fs_reserved_blocks. A default of "0" here would not be a no-op: it actively runs `tune2fs -m 0` on every volume, since the node plugin only skips the call when the parameter is empty (see stageVolume in the CSI driver), not when it's - "0". + `0`. type: string type: object x-kubernetes-validations: diff --git a/operator/api/v1alpha1/storagepool_types.go b/operator/api/v1alpha1/storagepool_types.go index b6f9237d8..6cfd2f0a1 100644 --- a/operator/api/v1alpha1/storagepool_types.go +++ b/operator/api/v1alpha1/storagepool_types.go @@ -87,7 +87,7 @@ type StorageClassParameters struct { // +kubebuilder:default=false Encryption *bool `json:"encryption,omitempty"` // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Fabric" - // Fabric is the transport fabric (e.g., TCP). + // Fabric is the transport fabric (e.g., `tcp`). // +kubebuilder:default=tcp Fabric string `json:"fabric,omitempty"` // MaxNamespacePerSubsys limits namespaces per NVMf subsystem. diff --git a/operator/config/crd/bases/storage.simplyblock.io_backuppolicies.yaml b/operator/config/crd/bases/storage.simplyblock.io_backuppolicies.yaml index 377b797ea..d2d743334 100644 --- a/operator/config/crd/bases/storage.simplyblock.io_backuppolicies.yaml +++ b/operator/config/crd/bases/storage.simplyblock.io_backuppolicies.yaml @@ -40,7 +40,7 @@ spec: BackupPolicy defines retention and scheduling parameters for simplyblock\nbackups. To apply a policy to a PVC, annotate the PVC with:\n\n\tsimplyblock.io/backup-policy: \n\nThe deprecated simplybk/backup-policy annotation - is still honoured for\nbackwards compatibility; when both are set, simplyblock.io/backup-policy\ntakes + is still honored for\nbackward compatibility; when both are set, simplyblock.io/backup-policy\ntakes precedence.\n\nThe BackupPolicy must be in the same namespace as the annotated PVC.\nThe controller attaches and detaches the policy in the simplyblock backend\nwhenever the annotation is added or removed." @@ -73,7 +73,7 @@ spec: rule: self == oldSelf maxAge: description: |- - MaxAge is the maximum age of backups to retain (e.g. "7d", "12h", "30m"). + MaxAge is the maximum age of backups to retain (e.g., `7d`, `12h`, `30m`). Backups older than this are merged. Accepts m, h, d, w suffixes. pattern: ^[1-9]\d*[mhdw]$ type: string @@ -91,7 +91,7 @@ spec: schedule: description: |- Schedule defines the tiered backup schedule as a space-separated list of - interval,keep_count pairs (e.g. "15m,4 60m,11 24h,7"). + interval,keep_count pairs (e.g., "15m,4 60m,11 24h,7"). Intervals must be strictly increasing. Supported units: m, h, d, w. pattern: ^(\d+[mhdw],\d+)( +\d+[mhdw],\d+)*$ type: string diff --git a/operator/config/crd/bases/storage.simplyblock.io_backuprestores.yaml b/operator/config/crd/bases/storage.simplyblock.io_backuprestores.yaml index fd1750971..d1b427c5a 100644 --- a/operator/config/crd/bases/storage.simplyblock.io_backuprestores.yaml +++ b/operator/config/crd/bases/storage.simplyblock.io_backuprestores.yaml @@ -349,7 +349,7 @@ spec: description: PVCNamespace is the namespace of the created PVC. type: string restoredLvolID: - description: RestoredLvolID is the UUID of the newly-created logical + description: RestoredLvolID is the UUID of the newly created logical volume. type: string sourceClusterUUID: diff --git a/operator/config/crd/bases/storage.simplyblock.io_controlplanes.yaml b/operator/config/crd/bases/storage.simplyblock.io_controlplanes.yaml index b2356c2a0..cff5125d0 100644 --- a/operator/config/crd/bases/storage.simplyblock.io_controlplanes.yaml +++ b/operator/config/crd/bases/storage.simplyblock.io_controlplanes.yaml @@ -60,7 +60,7 @@ spec: image: description: |- Image is the container image used for all simplyblock control-plane and - storage-node workloads (e.g. quay.io/simplyblock-io/simplyblock:26.2.2). + storage-node workloads (e.g., quay.io/simplyblock-io/simplyblock:26.2.2). StorageNodeSet CRs that omit spec.clusterImage inherit this value. Must reference one of the trusted registries (quay.io/simplyblock-io, docker.io/simplyblock, public.ecr.aws/simply-block); digest pinning (@sha256:...) is recommended. pattern: ^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$ @@ -79,7 +79,7 @@ spec: message: description: |- Message contains a human-readable explanation of the current phase, - for example the FDB error returned by the health endpoint. + for example, the FDB error returned by the health endpoint. type: string phase: description: |- diff --git a/operator/config/crd/bases/storage.simplyblock.io_replicationops.yaml b/operator/config/crd/bases/storage.simplyblock.io_replicationops.yaml index 7f7ea56be..ac1ae9947 100644 --- a/operator/config/crd/bases/storage.simplyblock.io_replicationops.yaml +++ b/operator/config/crd/bases/storage.simplyblock.io_replicationops.yaml @@ -67,8 +67,8 @@ spec: action: description: |- Action is the operation to perform. Immutable. - failover: unplanned — promote target clone, source may be down. - failback: restore source as primary after a prior failover. + failover: unplanned — promote target clone, source may be down. + failback: restore source as primary after a prior failover. migration: planned cutover — calls replication_commit per volume; both clusters stay up. State progression: replicating → cutover_pending → cutover_done. enum: @@ -165,7 +165,7 @@ spec: subphase: description: |- Subphase describes what the operation is currently doing within the phase - (e.g. "TriggeringFailover", "UpdatingSlotStatuses", "ReleasingLock"). + (e.g., `TriggeringFailover`, `UpdatingSlotStatuses`, `ReleasingLock`). type: string type: object type: object diff --git a/operator/config/crd/bases/storage.simplyblock.io_replicationpolicies.yaml b/operator/config/crd/bases/storage.simplyblock.io_replicationpolicies.yaml index 41412abc9..7994c0dc2 100644 --- a/operator/config/crd/bases/storage.simplyblock.io_replicationpolicies.yaml +++ b/operator/config/crd/bases/storage.simplyblock.io_replicationpolicies.yaml @@ -69,7 +69,7 @@ spec: interval: default: 5m description: Interval is how often a replication snapshot is taken - (e.g. "5m", "1h"). + (e.g., `5m`, `1h`). type: string mode: default: failover diff --git a/operator/config/crd/bases/storage.simplyblock.io_storagebackups.yaml b/operator/config/crd/bases/storage.simplyblock.io_storagebackups.yaml index a1f8731eb..5c95ad517 100644 --- a/operator/config/crd/bases/storage.simplyblock.io_storagebackups.yaml +++ b/operator/config/crd/bases/storage.simplyblock.io_storagebackups.yaml @@ -77,7 +77,7 @@ spec: - name type: object snapshotName: - description: SnapshotName optionally overrides the internally-created + description: SnapshotName optionally overrides the internally created snapshot name. type: string sourceClusterUUID: @@ -119,7 +119,7 @@ spec: type: string fsType: description: |- - FSType is the filesystem type of the source PersistentVolume (e.g. "ext4", + FSType is the filesystem type of the source PersistentVolume (e.g., `ext4`, "xfs"), captured at backup time so a restore can preserve it regardless of which StorageClass the restored PVC ends up using. type: string @@ -165,7 +165,7 @@ spec: format: int64 type: integer snapshotID: - description: SnapshotID is the internally-created snapshot UUID used + description: SnapshotID is the internally created snapshot UUID used for the backup request. type: string snapshotName: diff --git a/operator/config/crd/bases/storage.simplyblock.io_storageclusterops.yaml b/operator/config/crd/bases/storage.simplyblock.io_storageclusterops.yaml index eb1d376bf..d67f8ffee 100644 --- a/operator/config/crd/bases/storage.simplyblock.io_storageclusterops.yaml +++ b/operator/config/crd/bases/storage.simplyblock.io_storageclusterops.yaml @@ -83,7 +83,7 @@ spec: rule: self == oldSelf nodeRollingRestart: description: |- - NodeRollingRestart configures behaviour specific to the node-rolling-restart action. + NodeRollingRestart configures behavior specific to the node-rolling-restart action. Ignored for all other actions. properties: refreshSNodeAPI: diff --git a/operator/config/crd/bases/storage.simplyblock.io_storagenodeops.yaml b/operator/config/crd/bases/storage.simplyblock.io_storagenodeops.yaml index b5f0d3b2e..75403e5b8 100644 --- a/operator/config/crd/bases/storage.simplyblock.io_storagenodeops.yaml +++ b/operator/config/crd/bases/storage.simplyblock.io_storagenodeops.yaml @@ -86,7 +86,7 @@ spec: SystemVolumeFilterRegex is a Go regular expression matched against backend volume names. Matching volumes are treated as system volumes: excluded from drain migration and deleted inline during the Verifying phase. - Defaults to "^sb-fio-baseline-.*". + Defaults to `^sb-fio-baseline-.*`. type: string type: object force: diff --git a/operator/config/crd/bases/storage.simplyblock.io_storagenodes.yaml b/operator/config/crd/bases/storage.simplyblock.io_storagenodes.yaml index b6a7f5f88..c1e8b69b1 100644 --- a/operator/config/crd/bases/storage.simplyblock.io_storagenodes.yaml +++ b/operator/config/crd/bases/storage.simplyblock.io_storagenodes.yaml @@ -86,7 +86,7 @@ spec: deviceNames: description: |- DeviceNames explicitly defines the NVMe namespace names to use on this node - (e.g. ["nvme0n1","nvme1n1"]). + (e.g., ["nvme0n1","nvme1n1"]). items: type: string type: array @@ -101,7 +101,7 @@ spec: expand: description: |- Expand marks this node as a cluster-expansion add. When true the backend - node-add endpoint receives expand=true, triggering rebalancing behaviour + node-add endpoint receives expand=true, triggering rebalancing behavior appropriate for in-place cluster growth. Overrides StorageNodeSet.spec.expand. type: boolean failureDomain: @@ -153,7 +153,7 @@ spec: type: boolean spdkImage: description: SpdkImage overrides the SPDK image for this node - (e.g. for phased rollouts). + (e.g., for phased rollouts). type: string spdkProxyImage: description: SpdkProxyImage overrides the SPDK proxy image for @@ -162,7 +162,7 @@ spec: spdkSystemMemory: description: |- SpdkSystemMemory overrides the SPDK huge-page memory allocation for this node - (e.g. "4G", "512M"). + (e.g., `4G`, `512M`). pattern: ^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$ type: string ubuntuHost: @@ -172,7 +172,7 @@ spec: type: object socketId: description: SocketID is the NUMA socket identifier from spec.socketsToUse - (e.g. "0", "1"). Immutable. + (e.g., `0`, `1`). Immutable. type: string x-kubernetes-validations: - message: field is immutable @@ -332,7 +332,7 @@ spec: type: integer type: object status: - description: Status is the backend-reported node status (e.g. online, + description: Status is the backend-reported node status (e.g., online, suspended, offline). type: string uptime: diff --git a/operator/config/crd/bases/storage.simplyblock.io_storagenodesets.yaml b/operator/config/crd/bases/storage.simplyblock.io_storagenodesets.yaml index f3f0e8e2e..3310c43bd 100644 --- a/operator/config/crd/bases/storage.simplyblock.io_storagenodesets.yaml +++ b/operator/config/crd/bases/storage.simplyblock.io_storagenodesets.yaml @@ -80,7 +80,7 @@ spec: containerResources: description: |- ContainerResources sets CPU and memory requests/limits for the main storage-node container. - When omitted no limits are enforced, which preserves the previous behaviour. + When omitted no limits are enforced, which preserves the previous behavior. properties: claims: description: |- @@ -145,7 +145,7 @@ spec: type: array deviceNames: description: DeviceNames explicitly defines a comma separated list - of nvme namespace names like nvme0n1,nvme1n1... + of NVMe namespace names like nvme0n1,nvme1n1... items: type: string type: array @@ -171,7 +171,7 @@ spec: Expand indicates that storage nodes added from this StorageNodeSet are being added to expand an already-active cluster. When true the backend node-add endpoint receives expand=true, which triggers the appropriate rebalancing - behaviour for in-place cluster growth. + behavior for in-place cluster growth. type: boolean forceFormat4K: description: ForceFormat4K forces 4K blocksize formatting of the NVMe @@ -294,7 +294,7 @@ spec: deviceNames: description: |- DeviceNames explicitly defines the NVMe namespace names to use on this node - (e.g. ["nvme0n1","nvme1n1"]). + (e.g., ["nvme0n1","nvme1n1"]). items: type: string type: array @@ -309,7 +309,7 @@ spec: expand: description: |- Expand marks this node as a cluster-expansion add. When true the backend - node-add endpoint receives expand=true, triggering rebalancing behaviour + node-add endpoint receives expand=true, triggering rebalancing behavior appropriate for in-place cluster growth. Overrides StorageNodeSet.spec.expand. type: boolean failureDomain: @@ -362,7 +362,7 @@ spec: type: boolean spdkImage: description: SpdkImage overrides the SPDK image for this node - (e.g. for phased rollouts). + (e.g., for phased rollouts). type: string spdkProxyImage: description: SpdkProxyImage overrides the SPDK proxy image for @@ -371,7 +371,7 @@ spec: spdkSystemMemory: description: |- SpdkSystemMemory overrides the SPDK huge-page memory allocation for this node - (e.g. "4G", "512M"). + (e.g., `4G`, `512M`). pattern: ^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$ type: string ubuntuHost: @@ -458,7 +458,7 @@ spec: type: string spdkSystemMemory: description: |- - SpdkSystemMemory is the amount of memory reserved for SPDK system use (e.g. "4G", "512M"). + SpdkSystemMemory is the amount of memory reserved for SPDK system use (e.g., `4G`, `512M`). When omitted the backend default is used. pattern: ^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$ type: string @@ -531,7 +531,7 @@ spec: properties: creatingNodes: description: CreatingNodes is the count of StorageNode CRs with status - "in_creation". + `in_creation`. type: integer drainCoordination: description: DrainCoordination tracks the upgrade-drain state per @@ -672,11 +672,11 @@ spec: type: array offlineNodes: description: OfflineNodes is the count of StorageNode CRs with status - "offline". + `offline`. type: integer onlineNodes: description: OnlineNodes is the count of StorageNode CRs with status - "online". + `online`. type: integer pendingNodeAdds: additionalProperties: @@ -691,7 +691,7 @@ spec: type: object removedNodes: description: RemovedNodes is the count of StorageNode CRs with status - "removed". + `removed`. type: integer schedulingFailedWorkers: additionalProperties: @@ -703,7 +703,7 @@ spec: type: object suspendedNodes: description: SuspendedNodes is the count of StorageNode CRs with status - "suspended". + `suspended`. type: integer totalNodes: description: TotalNodes is the total number of owned StorageNode CRs. diff --git a/operator/config/crd/bases/storage.simplyblock.io_storagepools.yaml b/operator/config/crd/bases/storage.simplyblock.io_storagepools.yaml index 1fac84998..b106c42db 100644 --- a/operator/config/crd/bases/storage.simplyblock.io_storagepools.yaml +++ b/operator/config/crd/bases/storage.simplyblock.io_storagepools.yaml @@ -141,7 +141,7 @@ spec: type: boolean fabric: default: tcp - description: Fabric is the transport fabric (e.g. tcp). + description: Fabric is the transport fabric (e.g., `tcp`). type: string filesystem: default: xfs @@ -182,7 +182,7 @@ spec: StorageClass that omits tune2fs_reserved_blocks. A default of "0" here would not be a no-op: it actively runs `tune2fs -m 0` on every volume, since the node plugin only skips the call when the parameter is empty (see stageVolume in the CSI driver), not when it's - "0". + `0`. type: string type: object x-kubernetes-validations: diff --git a/operator/dist/install.yaml b/operator/dist/install.yaml index 32f1b8e41..b9e6328dd 100644 --- a/operator/dist/install.yaml +++ b/operator/dist/install.yaml @@ -172,7 +172,7 @@ spec: BackupPolicy defines retention and scheduling parameters for simplyblock\nbackups. To apply a policy to a PVC, annotate the PVC with:\n\n\tsimplyblock.io/backup-policy: \n\nThe deprecated simplybk/backup-policy annotation - is still honoured for\nbackwards compatibility; when both are set, simplyblock.io/backup-policy\ntakes + is still honored for\nbackward compatibility; when both are set, simplyblock.io/backup-policy\ntakes precedence.\n\nThe BackupPolicy must be in the same namespace as the annotated PVC.\nThe controller attaches and detaches the policy in the simplyblock backend\nwhenever the annotation is added or removed." @@ -205,7 +205,7 @@ spec: rule: self == oldSelf maxAge: description: |- - MaxAge is the maximum age of backups to retain (e.g. "7d", "12h", "30m"). + MaxAge is the maximum age of backups to retain (e.g., `7d`, `12h`, `30m`). Backups older than this are merged. Accepts m, h, d, w suffixes. pattern: ^[1-9]\d*[mhdw]$ type: string @@ -223,7 +223,7 @@ spec: schedule: description: |- Schedule defines the tiered backup schedule as a space-separated list of - interval,keep_count pairs (e.g. "15m,4 60m,11 24h,7"). + interval,keep_count pairs (e.g., "15m,4 60m,11 24h,7"). Intervals must be strictly increasing. Supported units: m, h, d, w. pattern: ^(\d+[mhdw],\d+)( +\d+[mhdw],\d+)*$ type: string @@ -641,7 +641,7 @@ spec: description: PVCNamespace is the namespace of the created PVC. type: string restoredLvolID: - description: RestoredLvolID is the UUID of the newly-created logical + description: RestoredLvolID is the UUID of the newly created logical volume. type: string sourceClusterUUID: @@ -1120,7 +1120,7 @@ spec: image: description: |- Image is the container image used for all simplyblock control-plane and - storage-node workloads (e.g. quay.io/simplyblock-io/simplyblock:26.2.2). + storage-node workloads (e.g., quay.io/simplyblock-io/simplyblock:26.2.2). StorageNodeSet CRs that omit spec.clusterImage inherit this value. Must reference one of the trusted registries (quay.io/simplyblock-io, docker.io/simplyblock, public.ecr.aws/simply-block); digest pinning (@sha256:...) is recommended. pattern: ^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$ @@ -1139,7 +1139,7 @@ spec: message: description: |- Message contains a human-readable explanation of the current phase, - for example the FDB error returned by the health endpoint. + for example, the FDB error returned by the health endpoint. type: string phase: description: |- @@ -1501,8 +1501,8 @@ spec: action: description: |- Action is the operation to perform. Immutable. - failover: unplanned — promote target clone, source may be down. - failback: restore source as primary after a prior failover. + failover: unplanned — promote target clone, source may be down. + failback: restore source as primary after a prior failover. migration: planned cutover — calls replication_commit per volume; both clusters stay up. State progression: replicating → cutover_pending → cutover_done. enum: @@ -1599,7 +1599,7 @@ spec: subphase: description: |- Subphase describes what the operation is currently doing within the phase - (e.g. "TriggeringFailover", "UpdatingSlotStatuses", "ReleasingLock"). + (e.g., `TriggeringFailover`, `UpdatingSlotStatuses`, `ReleasingLock`). type: string type: object type: object @@ -1840,7 +1840,7 @@ spec: interval: default: 5m description: Interval is how often a replication snapshot is taken - (e.g. "5m", "1h"). + (e.g., `5m`, `1h`). type: string mode: default: failover @@ -2671,7 +2671,7 @@ spec: - name type: object snapshotName: - description: SnapshotName optionally overrides the internally-created + description: SnapshotName optionally overrides the internally created snapshot name. type: string sourceClusterUUID: @@ -2713,7 +2713,7 @@ spec: type: string fsType: description: |- - FSType is the filesystem type of the source PersistentVolume (e.g. "ext4", + FSType is the filesystem type of the source PersistentVolume (e.g., `ext4`, "xfs"), captured at backup time so a restore can preserve it regardless of which StorageClass the restored PVC ends up using. type: string @@ -2759,7 +2759,7 @@ spec: format: int64 type: integer snapshotID: - description: SnapshotID is the internally-created snapshot UUID used + description: SnapshotID is the internally created snapshot UUID used for the backup request. type: string snapshotName: @@ -2865,7 +2865,7 @@ spec: rule: self == oldSelf nodeRollingRestart: description: |- - NodeRollingRestart configures behaviour specific to the node-rolling-restart action. + NodeRollingRestart configures behavior specific to the node-rolling-restart action. Ignored for all other actions. properties: refreshSNodeAPI: @@ -3829,7 +3829,7 @@ spec: SystemVolumeFilterRegex is a Go regular expression matched against backend volume names. Matching volumes are treated as system volumes: excluded from drain migration and deleted inline during the Verifying phase. - Defaults to "^sb-fio-baseline-.*". + Defaults to `^sb-fio-baseline-.*`. type: string type: object force: @@ -4024,7 +4024,7 @@ spec: deviceNames: description: |- DeviceNames explicitly defines the NVMe namespace names to use on this node - (e.g. ["nvme0n1","nvme1n1"]). + (e.g., ["nvme0n1","nvme1n1"]). items: type: string type: array @@ -4039,7 +4039,7 @@ spec: expand: description: |- Expand marks this node as a cluster-expansion add. When true the backend - node-add endpoint receives expand=true, triggering rebalancing behaviour + node-add endpoint receives expand=true, triggering rebalancing behavior appropriate for in-place cluster growth. Overrides StorageNodeSet.spec.expand. type: boolean failureDomain: @@ -4091,7 +4091,7 @@ spec: type: boolean spdkImage: description: SpdkImage overrides the SPDK image for this node - (e.g. for phased rollouts). + (e.g., for phased rollouts). type: string spdkProxyImage: description: SpdkProxyImage overrides the SPDK proxy image for @@ -4100,7 +4100,7 @@ spec: spdkSystemMemory: description: |- SpdkSystemMemory overrides the SPDK huge-page memory allocation for this node - (e.g. "4G", "512M"). + (e.g., `4G`, `512M`). pattern: ^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$ type: string ubuntuHost: @@ -4110,7 +4110,7 @@ spec: type: object socketId: description: SocketID is the NUMA socket identifier from spec.socketsToUse - (e.g. "0", "1"). Immutable. + (e.g., `0`, `1`). Immutable. type: string x-kubernetes-validations: - message: field is immutable @@ -4270,7 +4270,7 @@ spec: type: integer type: object status: - description: Status is the backend-reported node status (e.g. online, + description: Status is the backend-reported node status (e.g., online, suspended, offline). type: string uptime: @@ -4368,7 +4368,7 @@ spec: containerResources: description: |- ContainerResources sets CPU and memory requests/limits for the main storage-node container. - When omitted no limits are enforced, which preserves the previous behaviour. + When omitted no limits are enforced, which preserves the previous behavior. properties: claims: description: |- @@ -4433,7 +4433,7 @@ spec: type: array deviceNames: description: DeviceNames explicitly defines a comma separated list - of nvme namespace names like nvme0n1,nvme1n1... + of NVMe namespace names like nvme0n1,nvme1n1... items: type: string type: array @@ -4459,7 +4459,7 @@ spec: Expand indicates that storage nodes added from this StorageNodeSet are being added to expand an already-active cluster. When true the backend node-add endpoint receives expand=true, which triggers the appropriate rebalancing - behaviour for in-place cluster growth. + behavior for in-place cluster growth. type: boolean forceFormat4K: description: ForceFormat4K forces 4K blocksize formatting of the NVMe @@ -4582,7 +4582,7 @@ spec: deviceNames: description: |- DeviceNames explicitly defines the NVMe namespace names to use on this node - (e.g. ["nvme0n1","nvme1n1"]). + (e.g., ["nvme0n1","nvme1n1"]). items: type: string type: array @@ -4597,7 +4597,7 @@ spec: expand: description: |- Expand marks this node as a cluster-expansion add. When true the backend - node-add endpoint receives expand=true, triggering rebalancing behaviour + node-add endpoint receives expand=true, triggering rebalancing behavior appropriate for in-place cluster growth. Overrides StorageNodeSet.spec.expand. type: boolean failureDomain: @@ -4650,7 +4650,7 @@ spec: type: boolean spdkImage: description: SpdkImage overrides the SPDK image for this node - (e.g. for phased rollouts). + (e.g., for phased rollouts). type: string spdkProxyImage: description: SpdkProxyImage overrides the SPDK proxy image for @@ -4659,7 +4659,7 @@ spec: spdkSystemMemory: description: |- SpdkSystemMemory overrides the SPDK huge-page memory allocation for this node - (e.g. "4G", "512M"). + (e.g., `4G`, `512M`). pattern: ^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$ type: string ubuntuHost: @@ -4746,7 +4746,7 @@ spec: type: string spdkSystemMemory: description: |- - SpdkSystemMemory is the amount of memory reserved for SPDK system use (e.g. "4G", "512M"). + SpdkSystemMemory is the amount of memory reserved for SPDK system use (e.g., `4G`, `512M`). When omitted the backend default is used. pattern: ^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$ type: string @@ -4819,7 +4819,7 @@ spec: properties: creatingNodes: description: CreatingNodes is the count of StorageNode CRs with status - "in_creation". + `in_creation`. type: integer drainCoordination: description: DrainCoordination tracks the upgrade-drain state per @@ -4960,11 +4960,11 @@ spec: type: array offlineNodes: description: OfflineNodes is the count of StorageNode CRs with status - "offline". + `offline`. type: integer onlineNodes: description: OnlineNodes is the count of StorageNode CRs with status - "online". + `online`. type: integer pendingNodeAdds: additionalProperties: @@ -4979,7 +4979,7 @@ spec: type: object removedNodes: description: RemovedNodes is the count of StorageNode CRs with status - "removed". + `removed`. type: integer schedulingFailedWorkers: additionalProperties: @@ -4991,7 +4991,7 @@ spec: type: object suspendedNodes: description: SuspendedNodes is the count of StorageNode CRs with status - "suspended". + `suspended`. type: integer totalNodes: description: TotalNodes is the total number of owned StorageNode CRs. @@ -5155,7 +5155,7 @@ spec: type: boolean fabric: default: tcp - description: Fabric is the transport fabric (e.g. tcp). + description: Fabric is the transport fabric (e.g., `tcp`). type: string filesystem: default: xfs @@ -5196,7 +5196,7 @@ spec: StorageClass that omits tune2fs_reserved_blocks. A default of "0" here would not be a no-op: it actively runs `tune2fs -m 0` on every volume, since the node plugin only skips the call when the parameter is empty (see stageVolume in the CSI driver), not when it's - "0". + `0`. type: string type: object x-kubernetes-validations: diff --git a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_backuppolicies.yaml b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_backuppolicies.yaml index 377b797ea..d2d743334 100644 --- a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_backuppolicies.yaml +++ b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_backuppolicies.yaml @@ -40,7 +40,7 @@ spec: BackupPolicy defines retention and scheduling parameters for simplyblock\nbackups. To apply a policy to a PVC, annotate the PVC with:\n\n\tsimplyblock.io/backup-policy: \n\nThe deprecated simplybk/backup-policy annotation - is still honoured for\nbackwards compatibility; when both are set, simplyblock.io/backup-policy\ntakes + is still honored for\nbackward compatibility; when both are set, simplyblock.io/backup-policy\ntakes precedence.\n\nThe BackupPolicy must be in the same namespace as the annotated PVC.\nThe controller attaches and detaches the policy in the simplyblock backend\nwhenever the annotation is added or removed." @@ -73,7 +73,7 @@ spec: rule: self == oldSelf maxAge: description: |- - MaxAge is the maximum age of backups to retain (e.g. "7d", "12h", "30m"). + MaxAge is the maximum age of backups to retain (e.g., `7d`, `12h`, `30m`). Backups older than this are merged. Accepts m, h, d, w suffixes. pattern: ^[1-9]\d*[mhdw]$ type: string @@ -91,7 +91,7 @@ spec: schedule: description: |- Schedule defines the tiered backup schedule as a space-separated list of - interval,keep_count pairs (e.g. "15m,4 60m,11 24h,7"). + interval,keep_count pairs (e.g., "15m,4 60m,11 24h,7"). Intervals must be strictly increasing. Supported units: m, h, d, w. pattern: ^(\d+[mhdw],\d+)( +\d+[mhdw],\d+)*$ type: string diff --git a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_backuprestores.yaml b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_backuprestores.yaml index fd1750971..d1b427c5a 100644 --- a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_backuprestores.yaml +++ b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_backuprestores.yaml @@ -349,7 +349,7 @@ spec: description: PVCNamespace is the namespace of the created PVC. type: string restoredLvolID: - description: RestoredLvolID is the UUID of the newly-created logical + description: RestoredLvolID is the UUID of the newly created logical volume. type: string sourceClusterUUID: diff --git a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_controlplanes.yaml b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_controlplanes.yaml index b2356c2a0..cff5125d0 100644 --- a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_controlplanes.yaml +++ b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_controlplanes.yaml @@ -60,7 +60,7 @@ spec: image: description: |- Image is the container image used for all simplyblock control-plane and - storage-node workloads (e.g. quay.io/simplyblock-io/simplyblock:26.2.2). + storage-node workloads (e.g., quay.io/simplyblock-io/simplyblock:26.2.2). StorageNodeSet CRs that omit spec.clusterImage inherit this value. Must reference one of the trusted registries (quay.io/simplyblock-io, docker.io/simplyblock, public.ecr.aws/simply-block); digest pinning (@sha256:...) is recommended. pattern: ^($|(quay\.io/simplyblock-io|docker\.io/simplyblock|public\.ecr\.aws/simply-block)/[a-z0-9][a-z0-9._-]*:[a-zA-Z0-9][a-zA-Z0-9._-]*(@sha256:[a-f0-9]{64})?)$ @@ -79,7 +79,7 @@ spec: message: description: |- Message contains a human-readable explanation of the current phase, - for example the FDB error returned by the health endpoint. + for example, the FDB error returned by the health endpoint. type: string phase: description: |- diff --git a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_replicationops.yaml b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_replicationops.yaml index 7f7ea56be..ac1ae9947 100644 --- a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_replicationops.yaml +++ b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_replicationops.yaml @@ -67,8 +67,8 @@ spec: action: description: |- Action is the operation to perform. Immutable. - failover: unplanned — promote target clone, source may be down. - failback: restore source as primary after a prior failover. + failover: unplanned — promote target clone, source may be down. + failback: restore source as primary after a prior failover. migration: planned cutover — calls replication_commit per volume; both clusters stay up. State progression: replicating → cutover_pending → cutover_done. enum: @@ -165,7 +165,7 @@ spec: subphase: description: |- Subphase describes what the operation is currently doing within the phase - (e.g. "TriggeringFailover", "UpdatingSlotStatuses", "ReleasingLock"). + (e.g., `TriggeringFailover`, `UpdatingSlotStatuses`, `ReleasingLock`). type: string type: object type: object diff --git a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_replicationpolicies.yaml b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_replicationpolicies.yaml index 41412abc9..7994c0dc2 100644 --- a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_replicationpolicies.yaml +++ b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_replicationpolicies.yaml @@ -69,7 +69,7 @@ spec: interval: default: 5m description: Interval is how often a replication snapshot is taken - (e.g. "5m", "1h"). + (e.g., `5m`, `1h`). type: string mode: default: failover diff --git a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagebackups.yaml b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagebackups.yaml index a1f8731eb..5c95ad517 100644 --- a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagebackups.yaml +++ b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagebackups.yaml @@ -77,7 +77,7 @@ spec: - name type: object snapshotName: - description: SnapshotName optionally overrides the internally-created + description: SnapshotName optionally overrides the internally created snapshot name. type: string sourceClusterUUID: @@ -119,7 +119,7 @@ spec: type: string fsType: description: |- - FSType is the filesystem type of the source PersistentVolume (e.g. "ext4", + FSType is the filesystem type of the source PersistentVolume (e.g., `ext4`, "xfs"), captured at backup time so a restore can preserve it regardless of which StorageClass the restored PVC ends up using. type: string @@ -165,7 +165,7 @@ spec: format: int64 type: integer snapshotID: - description: SnapshotID is the internally-created snapshot UUID used + description: SnapshotID is the internally created snapshot UUID used for the backup request. type: string snapshotName: diff --git a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storageclusterops.yaml b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storageclusterops.yaml index eb1d376bf..d67f8ffee 100644 --- a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storageclusterops.yaml +++ b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storageclusterops.yaml @@ -83,7 +83,7 @@ spec: rule: self == oldSelf nodeRollingRestart: description: |- - NodeRollingRestart configures behaviour specific to the node-rolling-restart action. + NodeRollingRestart configures behavior specific to the node-rolling-restart action. Ignored for all other actions. properties: refreshSNodeAPI: diff --git a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagenodeops.yaml b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagenodeops.yaml index b5f0d3b2e..75403e5b8 100644 --- a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagenodeops.yaml +++ b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagenodeops.yaml @@ -86,7 +86,7 @@ spec: SystemVolumeFilterRegex is a Go regular expression matched against backend volume names. Matching volumes are treated as system volumes: excluded from drain migration and deleted inline during the Verifying phase. - Defaults to "^sb-fio-baseline-.*". + Defaults to `^sb-fio-baseline-.*`. type: string type: object force: diff --git a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagenodes.yaml b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagenodes.yaml index b6a7f5f88..c1e8b69b1 100644 --- a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagenodes.yaml +++ b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagenodes.yaml @@ -86,7 +86,7 @@ spec: deviceNames: description: |- DeviceNames explicitly defines the NVMe namespace names to use on this node - (e.g. ["nvme0n1","nvme1n1"]). + (e.g., ["nvme0n1","nvme1n1"]). items: type: string type: array @@ -101,7 +101,7 @@ spec: expand: description: |- Expand marks this node as a cluster-expansion add. When true the backend - node-add endpoint receives expand=true, triggering rebalancing behaviour + node-add endpoint receives expand=true, triggering rebalancing behavior appropriate for in-place cluster growth. Overrides StorageNodeSet.spec.expand. type: boolean failureDomain: @@ -153,7 +153,7 @@ spec: type: boolean spdkImage: description: SpdkImage overrides the SPDK image for this node - (e.g. for phased rollouts). + (e.g., for phased rollouts). type: string spdkProxyImage: description: SpdkProxyImage overrides the SPDK proxy image for @@ -162,7 +162,7 @@ spec: spdkSystemMemory: description: |- SpdkSystemMemory overrides the SPDK huge-page memory allocation for this node - (e.g. "4G", "512M"). + (e.g., `4G`, `512M`). pattern: ^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$ type: string ubuntuHost: @@ -172,7 +172,7 @@ spec: type: object socketId: description: SocketID is the NUMA socket identifier from spec.socketsToUse - (e.g. "0", "1"). Immutable. + (e.g., `0`, `1`). Immutable. type: string x-kubernetes-validations: - message: field is immutable @@ -332,7 +332,7 @@ spec: type: integer type: object status: - description: Status is the backend-reported node status (e.g. online, + description: Status is the backend-reported node status (e.g., online, suspended, offline). type: string uptime: diff --git a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagenodesets.yaml b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagenodesets.yaml index f3f0e8e2e..3310c43bd 100644 --- a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagenodesets.yaml +++ b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagenodesets.yaml @@ -80,7 +80,7 @@ spec: containerResources: description: |- ContainerResources sets CPU and memory requests/limits for the main storage-node container. - When omitted no limits are enforced, which preserves the previous behaviour. + When omitted no limits are enforced, which preserves the previous behavior. properties: claims: description: |- @@ -145,7 +145,7 @@ spec: type: array deviceNames: description: DeviceNames explicitly defines a comma separated list - of nvme namespace names like nvme0n1,nvme1n1... + of NVMe namespace names like nvme0n1,nvme1n1... items: type: string type: array @@ -171,7 +171,7 @@ spec: Expand indicates that storage nodes added from this StorageNodeSet are being added to expand an already-active cluster. When true the backend node-add endpoint receives expand=true, which triggers the appropriate rebalancing - behaviour for in-place cluster growth. + behavior for in-place cluster growth. type: boolean forceFormat4K: description: ForceFormat4K forces 4K blocksize formatting of the NVMe @@ -294,7 +294,7 @@ spec: deviceNames: description: |- DeviceNames explicitly defines the NVMe namespace names to use on this node - (e.g. ["nvme0n1","nvme1n1"]). + (e.g., ["nvme0n1","nvme1n1"]). items: type: string type: array @@ -309,7 +309,7 @@ spec: expand: description: |- Expand marks this node as a cluster-expansion add. When true the backend - node-add endpoint receives expand=true, triggering rebalancing behaviour + node-add endpoint receives expand=true, triggering rebalancing behavior appropriate for in-place cluster growth. Overrides StorageNodeSet.spec.expand. type: boolean failureDomain: @@ -362,7 +362,7 @@ spec: type: boolean spdkImage: description: SpdkImage overrides the SPDK image for this node - (e.g. for phased rollouts). + (e.g., for phased rollouts). type: string spdkProxyImage: description: SpdkProxyImage overrides the SPDK proxy image for @@ -371,7 +371,7 @@ spec: spdkSystemMemory: description: |- SpdkSystemMemory overrides the SPDK huge-page memory allocation for this node - (e.g. "4G", "512M"). + (e.g., `4G`, `512M`). pattern: ^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$ type: string ubuntuHost: @@ -458,7 +458,7 @@ spec: type: string spdkSystemMemory: description: |- - SpdkSystemMemory is the amount of memory reserved for SPDK system use (e.g. "4G", "512M"). + SpdkSystemMemory is the amount of memory reserved for SPDK system use (e.g., `4G`, `512M`). When omitted the backend default is used. pattern: ^[0-9]+(G|GI|GB|GiB|M|MI|MB|MiB|g|gi|gb|gib|m|mi|mb|mib)?$ type: string @@ -531,7 +531,7 @@ spec: properties: creatingNodes: description: CreatingNodes is the count of StorageNode CRs with status - "in_creation". + `in_creation`. type: integer drainCoordination: description: DrainCoordination tracks the upgrade-drain state per @@ -672,11 +672,11 @@ spec: type: array offlineNodes: description: OfflineNodes is the count of StorageNode CRs with status - "offline". + `offline`. type: integer onlineNodes: description: OnlineNodes is the count of StorageNode CRs with status - "online". + `online`. type: integer pendingNodeAdds: additionalProperties: @@ -691,7 +691,7 @@ spec: type: object removedNodes: description: RemovedNodes is the count of StorageNode CRs with status - "removed". + `removed`. type: integer schedulingFailedWorkers: additionalProperties: @@ -703,7 +703,7 @@ spec: type: object suspendedNodes: description: SuspendedNodes is the count of StorageNode CRs with status - "suspended". + `suspended`. type: integer totalNodes: description: TotalNodes is the total number of owned StorageNode CRs. diff --git a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagepools.yaml b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagepools.yaml index 1fac84998..b106c42db 100644 --- a/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagepools.yaml +++ b/operator/internal/upgrade/crds/manifests/storage.simplyblock.io_storagepools.yaml @@ -141,7 +141,7 @@ spec: type: boolean fabric: default: tcp - description: Fabric is the transport fabric (e.g. tcp). + description: Fabric is the transport fabric (e.g., `tcp`). type: string filesystem: default: xfs @@ -182,7 +182,7 @@ spec: StorageClass that omits tune2fs_reserved_blocks. A default of "0" here would not be a no-op: it actively runs `tune2fs -m 0` on every volume, since the node plugin only skips the call when the parameter is empty (see stageVolume in the CSI driver), not when it's - "0". + `0`. type: string type: object x-kubernetes-validations: From c2310eccaef0145028d260921c8c9c88bd4ca181 Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Fri, 11 Sep 2026 09:09:30 +0200 Subject: [PATCH 17/19] docs(go): undo the comment fixes that landed on code Copilot found five things on the pull request and four of them are mine: places where a house style fixer rewrote a word that was not prose, and my review of the substitution list did not catch it. I checked the risky-looking terms and waved the ordinary-looking ones through, which is how `slack` and `forwards` got past. Restored: - `nvme connect` and `nvme list` are commands. The terminology rule spells the protocol NVMe, but these two name a binary and a subcommand, and the point of writing them exactly is that somebody can grep a log for them. - `slack` is spare capacity in that test's fixture, not the product. - `reads and forwards` is a verb. The American English rule reads the -wards ending as an adverb, which is right for backwards and afterwards and wrong here. - `uuid` and `uuids` name parameters. "UUID matches uuid" had become "UUID matches UUID", which says nothing. - Nine occurrences of NQN that my earlier revert downgraded. Reverting NQN back to nqn was right where the word names a parameter and wrong in paths.go, release.go, and rebalancing.go, where it names the concept or the field the comment documents. The blanket replacement did not distinguish them; these are restored to what origin/main had. A systematic check replaces the eyeballing that missed them: for every lowercase-to-cased substitution, does the lowercase form also appear as a declared identifier in the same file? It reports none now. The tool module's Go directive drops from 1.26.5, which was whatever the local toolchain happened to be, to the 1.26.2 the four component modules share. The house style gate now fails on these files, with fourteen errors that are all correct code: six nqn, three uuid, and one each of slack, ip, nics, forwards, and a quoted error body. A fifteenth, MigrationStatusCancelled, predates this branch and is an exported name, so renaming it is an API change. Until the checkers learn that a doc comment names its parameters as they are spelled, that [nvme] is a godoc link, and that a comment line can be commented-out code, that is the floor for Go and no amount of editing the source lowers it. --- hack/golangci-onelinefunc/go.mod | 2 +- operator/cmd/simplyblock-rebalancer/main.go | 2 +- operator/cmd/simplyblock-rebalancer/validate_migration.go | 2 +- operator/internal/controller/nodedrain_controller.go | 2 +- .../controller/storagenodeops_controller_unit_test.go | 2 +- operator/internal/utils/objects.go | 2 +- operator/internal/volumemigration/paths.go | 4 ++-- operator/internal/volumemigration/release.go | 6 +++--- operator/internal/webapi/rebalancing.go | 8 ++++---- .../webhook/simplyblock_volume_placement_injector.go | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/hack/golangci-onelinefunc/go.mod b/hack/golangci-onelinefunc/go.mod index 41c3c50dc..cad6613cd 100644 --- a/hack/golangci-onelinefunc/go.mod +++ b/hack/golangci-onelinefunc/go.mod @@ -1,6 +1,6 @@ module github.com/simplyblock/golangci-onelinefunc -go 1.26.5 +go 1.26.2 require ( github.com/golangci/plugin-module-register v0.1.1 diff --git a/operator/cmd/simplyblock-rebalancer/main.go b/operator/cmd/simplyblock-rebalancer/main.go index 97c95d084..7b25015d8 100644 --- a/operator/cmd/simplyblock-rebalancer/main.go +++ b/operator/cmd/simplyblock-rebalancer/main.go @@ -77,7 +77,7 @@ type fioOutput struct { } `json:"jobs"` } -// ── NVMe list JSON structures ────────────────────────────────────────────────── +// ── `nvme list` JSON structures ────────────────────────────────────────────────── type nvmeListOutput struct { Devices []struct { diff --git a/operator/cmd/simplyblock-rebalancer/validate_migration.go b/operator/cmd/simplyblock-rebalancer/validate_migration.go index 63083a918..36981255e 100644 --- a/operator/cmd/simplyblock-rebalancer/validate_migration.go +++ b/operator/cmd/simplyblock-rebalancer/validate_migration.go @@ -126,7 +126,7 @@ func (v validationRun) run( log.Printf("path %s to %s already present before connecting", addr, nqn) } - // The freshly connected target path can lag behind: NVMe connect may return before + // The freshly connected target path can lag behind: `nvme connect` may return before // its controller is live and the ANA log page settles. Retry the connect+verify // cycle a few times before giving up so a transient lag is not mistaken for a // missing path. Already connected paths are a no-op in ensurePaths, so re-running diff --git a/operator/internal/controller/nodedrain_controller.go b/operator/internal/controller/nodedrain_controller.go index 062b52ee4..6b5da0593 100644 --- a/operator/internal/controller/nodedrain_controller.go +++ b/operator/internal/controller/nodedrain_controller.go @@ -1441,7 +1441,7 @@ func findAllNodeUUIDs(snCR *simplyblockv1alpha1.StorageNodeSet, hostname string) return uuids } -// nextUUIDInList returns the element immediately after current in UUIDs, +// nextUUIDInList returns the element immediately after current in uuids, // or an empty string if current is the last element or not found. func nextUUIDInList(uuids []string, current string) string { for i, u := range uuids { diff --git a/operator/internal/controller/storagenodeops_controller_unit_test.go b/operator/internal/controller/storagenodeops_controller_unit_test.go index 76055f9cb..97fe74ef1 100644 --- a/operator/internal/controller/storagenodeops_controller_unit_test.go +++ b/operator/internal/controller/storagenodeops_controller_unit_test.go @@ -132,7 +132,7 @@ func TestAcquireLock_RequeuesWhenAnotherOpsActive(t *testing.T) { // FD1=2/FD2=2/FD3=3. Removing a node from the already-smallest domain (FD1) // drops it to 1 while FD3 stays at 3 -- a spread the backend's own // check_fd_admission_for_remove correctly refuses (populations {1,2,3}). -// Removing instead from the domain with Slack (FD3) leaves 2/2/2, which is +// Removing instead from the domain with slack (FD3) leaves 2/2/2, which is // fine. This is the gate that must fire in drainValidate BEFORE Suspending, // so an infeasible removal never suspends the node in the first place. diff --git a/operator/internal/utils/objects.go b/operator/internal/utils/objects.go index e8587e558..499f5991f 100644 --- a/operator/internal/utils/objects.go +++ b/operator/internal/utils/objects.go @@ -137,7 +137,7 @@ func ResolveClusterIdentifier(ctx context.Context, k8sClient client.Client, name } // ResolveClusterCRByUUID finds the StorageCluster CR in namespace whose backend -// UUID matches UUID. Used to go from a cross-cluster reference (which only +// UUID matches uuid. Used to go from a cross-cluster reference (which only // carries the backend UUID) back to the CR, to read config the backend doesn't // expose, such as a cluster's backup credentials secret. func ResolveClusterCRByUUID( diff --git a/operator/internal/volumemigration/paths.go b/operator/internal/volumemigration/paths.go index 91ad16a4d..6ff0ea123 100644 --- a/operator/internal/volumemigration/paths.go +++ b/operator/internal/volumemigration/paths.go @@ -147,7 +147,7 @@ func PresentAddresses(ctx context.Context, sysRoot, nqn string) (map[string]bool // // Inspect is what names the defects a connect cannot see — a live controller that // serves no namespace at all, or serves the subsystem's other namespaces but not -// this one, or an nqn answered by two kernel subsystem instances at once. Those are +// this one, or an NQN answered by two kernel subsystem instances at once. Those are // the states in which every expected path looks established while the volume has // nothing to take over at cutover, and diagnosing them from what the kernel already // publishes is what atlas centralizes. @@ -155,7 +155,7 @@ func PresentAddresses(ctx context.Context, sysRoot, nqn string) (map[string]bool // It is asked once per exported namespace rather than once for the subsystem, and // that is the whole reason it can say anything here: its controller-level check // needs to know which namespace is meant, and stands down when a selector matches -// several — which a bare nqn does on exactly the multi-namespace subsystems this +// several — which a bare NQN does on exactly the multi-namespace subsystems this // package migrates. // // No target list is passed. Targets are how Inspect tells an attached endpoint the diff --git a/operator/internal/volumemigration/release.go b/operator/internal/volumemigration/release.go index 12ba1ab70..e0fcd5786 100644 --- a/operator/internal/volumemigration/release.go +++ b/operator/internal/volumemigration/release.go @@ -207,7 +207,7 @@ func releaseMigrationPaths( return nil, err } if s.NQN == "" { - // Nothing attached for this nqn: the paths are already gone. + // Nothing attached for this NQN: the paths are already gone. return nil, nil } return detach(ctx, d, migrationPathVictims(s, conns), @@ -305,9 +305,9 @@ func reapDeadControllers( // the other thing worth reusing here — and when the controller serves no namespace at // all. See reapableKind for why that last test is this caller's and not atlas's. // -// Inspect is asked once per exported namespace as well as once for the bare nqn, because +// Inspect is asked once per exported namespace as well as once for the bare NQN, because // its controller-level check needs to know which namespace is meant and stands down when -// a selector matches several — which a bare nqn does on exactly the multi-namespace +// a selector matches several — which a bare NQN does on exactly the multi-namespace // subsystems this package migrates. That is the same reason diagnose asks that way. func reapableDefects(ctx context.Context, sysRoot string, s nvme.Subsystem) []nvmeof.Defect { subs := snapshot{s} diff --git a/operator/internal/webapi/rebalancing.go b/operator/internal/webapi/rebalancing.go index 0b4e0c703..ecd691a49 100644 --- a/operator/internal/webapi/rebalancing.go +++ b/operator/internal/webapi/rebalancing.go @@ -34,7 +34,7 @@ type CapacityStat struct { type VolumeInfo struct { UUID string `json:"id"` Name string `json:"name"` - // nqn is the volume's NVMe subsystem nqn. Namespaced volumes share it with + // NQN is the volume's NVMe subsystem NQN. Namespaced volumes share it with // their siblings, and it is the identity a batch migration is addressed by // (see MigrationRef). NQN string `json:"nqn"` @@ -59,7 +59,7 @@ type ContinueMigrationParams struct { DeadlineSeconds int `json:"deadline_seconds,omitempty"` } -// Migrations are addressed by cluster and NVMe subsystem nqn: the control plane +// Migrations are addressed by cluster and NVMe subsystem NQN: the control plane // migrates a whole subsystem at once, covering both a single-namespace subsystem // and a namespaced one, where several volumes share it. // @@ -125,7 +125,7 @@ type MigrationDTO struct { // normalize fills in what a single-namespace migration's response leaves out. // Such a migration still moves exactly one volume, so reporting 0 members would // make "how many volumes did this move" wrong for every non-namespaced volume; -// and it is addressed under the subsystem the caller asked for, so that nqn is +// and it is addressed under the subsystem the caller asked for, so that NQN is // the subsystem being migrated whether or not the response repeats it. func (m *MigrationDTO) normalize(nqn string) { if m.MemberCount <= 0 { @@ -243,7 +243,7 @@ func (c *Client) GetPoolVolumes( // single-namespace subsystem that is one volume; for a namespaced one it is the // volume and its siblings. // -// The control plane has no volume-by-nqn lookup and its batch-migration DTO reports +// The control plane has no volume-by-NQN lookup and its batch-migration DTO reports // only a member *count*, so membership is derived here by scanning the cluster's // pools. Pools are scanned rather than assuming the subsystem's members live in the // pool of any one member: a subsystem is scoped to a storage node, not to a pool. diff --git a/operator/internal/webhook/simplyblock_volume_placement_injector.go b/operator/internal/webhook/simplyblock_volume_placement_injector.go index 206243e27..dc07db222 100644 --- a/operator/internal/webhook/simplyblock_volume_placement_injector.go +++ b/operator/internal/webhook/simplyblock_volume_placement_injector.go @@ -45,7 +45,7 @@ type primaryNodeSelector interface { // SimplyblockVolumePlacementInjector is a mutating admission webhook that computes the // least-loaded eligible storage node for a new PVC's primary volume — using the same // latency-deviation signal the auto-rebalancer (Issue #130) uses — and stamps it onto the -// PVC as the simplyblock.io/host-id annotation, which spdk-csi already reads and forward +// PVC as the simplyblock.io/host-id annotation, which spdk-csi already reads and forwards // as host_id on CreateVolume. failurePolicy=ignore, and every skip/error path below allows // the PVC unmodified, so this can never block volume provisioning: sbcli's own // weighted-random pick (_get_next_3_nodes) runs as the fallback exactly as it does today. From 7fb8c9ed3d84704b4f4331224ac99687fb4a0304 Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Fri, 11 Sep 2026 09:16:19 +0200 Subject: [PATCH 18/19] build: enable the onelinefunc linter in all three components The plugin was added two commits ago and nothing ran it. It runs now, in operator, csi-driver, and atlas-lib alike. golangci-lint cannot load a plugin at run time: the linter has to be compiled in. `.custom-gcl.yml` describes that build against the v2.11.4 that scripts/tools.manifest already pins, and scripts/tools.mk grows a $(CUSTOM_GCL) target that produces .bin/custom-gcl from the plugin's sources. Every lint target now runs that binary instead of the downloaded one, which does not know the linter and refuses a config that enables it. atlas-lib had no .golangci.yml at all, and so ran golangci-lint's standard set. The new file says `default: standard` rather than `default: none`, because the point is to add one linter, not to quietly stop running the ones the module has been checked with all along. repo_lint.yaml stops using golangci-lint-action, which installs the released binary. It builds custom-gcl and runs it directly rather than through `make -C lint`, because atlas-lib's lint target also runs code generation that this job has never provisioned and the action never triggered. Verified from an empty .bin: `make custom-gcl` builds it, `custom-gcl linters` lists onelinefunc as enabled in each of the three modules, and a full run in each reports 0 issues. A temporary one-line function in operator/internal/discovery is reported, so the rule fires rather than merely loading. Two things this cost, both worth knowing. The build clones and compiles golangci-lint, so the first lint of the day takes a minute or two; the target is gated on the plugin's sources and .custom-gcl.yml, so it does not rebuild otherwise. And the version in .custom-gcl.yml has to move with the one in scripts/tools.manifest, because the plugin compiles against golangci-lint's own module and a mismatch fails the build. --- .custom-gcl.yml | 18 ++++++++++++++++ .github/workflows/repo_lint.yaml | 16 +++++++++++---- Makefile | 5 +++++ atlas-lib/.golangci.yml | 35 ++++++++++++++++++++++++++++++++ atlas-lib/Makefile | 4 ++-- csi-driver/.golangci.yml | 6 ++++++ csi-driver/Makefile | 4 ++-- operator/.golangci.yml | 6 ++++++ operator/Makefile | 12 +++++------ scripts/tools.mk | 15 ++++++++++++++ 10 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 .custom-gcl.yml create mode 100644 atlas-lib/.golangci.yml diff --git a/.custom-gcl.yml b/.custom-gcl.yml new file mode 100644 index 000000000..e59be964e --- /dev/null +++ b/.custom-gcl.yml @@ -0,0 +1,18 @@ +# Builds a golangci-lint with this repository's own linter compiled in. +# +# The version must match the one scripts/tools.manifest pins, because the +# plugin is compiled against golangci-lint's own module: a mismatch fails the +# build rather than producing a binary that misbehaves. +# +# `golangci-lint custom` writes the result to destination/name. scripts/tools.mk +# exposes it as $(GOLANGCI_LINT_CUSTOM) and the component Makefiles run that +# instead of the downloaded binary, because the downloaded one does not know the +# onelinefunc linter and refuses a config that enables it. +--- +version: v2.11.4 +name: custom-gcl +destination: .bin + +plugins: + - module: github.com/simplyblock/golangci-onelinefunc + path: ./hack/golangci-onelinefunc diff --git a/.github/workflows/repo_lint.yaml b/.github/workflows/repo_lint.yaml index 0e2e1490a..d41a88529 100644 --- a/.github/workflows/repo_lint.yaml +++ b/.github/workflows/repo_lint.yaml @@ -37,8 +37,16 @@ jobs: with: go-version-file: ${{ matrix.component }}/go.mod + # golangci-lint-action installs the released binary, which does not carry + # this repository's onelinefunc linter and rejects a config enabling it. + # The binary is built from .custom-gcl.yml instead, against the same + # version scripts/tools.manifest pins. + - name: Build golangci-lint with this repository's linters + run: make custom-gcl + + # Run it directly rather than through `make -C lint`: the + # atlas-lib target also runs code generation, which this job does not + # provision and the action never did either. - name: Run linter - uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8 - with: - version: v2.11.4 - working-directory: ${{ matrix.component }} + working-directory: ${{ matrix.component }} + run: '"${GITHUB_WORKSPACE}/.bin/custom-gcl" run ./...' diff --git a/Makefile b/Makefile index 6cb90ced0..a07359e56 100644 --- a/Makefile +++ b/Makefile @@ -62,6 +62,11 @@ test: atlas-test csi-test operator-test ## Test every component. lint: atlas-lint csi-lint operator-lint ## Lint every component. +# Every component's lint target needs this binary, and building it three times +# over would clone and compile golangci-lint three times. It is built once here. +custom-gcl: ## Build golangci-lint with this repository's linters compiled in. + $(MAKE) -C $(OPERATOR_DIR) custom-gcl + fmt: atlas-fmt csi-fmt operator-fmt ## Format every component. vet: atlas-vet csi-vet operator-vet ## Vet every component. diff --git a/atlas-lib/.golangci.yml b/atlas-lib/.golangci.yml new file mode 100644 index 000000000..15e17da4c --- /dev/null +++ b/atlas-lib/.golangci.yml @@ -0,0 +1,35 @@ +# golangci-lint configuration for atlas-lib. +# +# The module had no configuration and so ran golangci-lint's standard set. That +# set is kept here rather than replaced: `default: standard` is what was already +# in effect, and the only addition is this repository's own onelinefunc linter, +# which needs a config file to be enabled in. +# +# onelinefunc comes from a plugin, so the stock golangci-lint does not know it +# and rejects this file. Run the binary .custom-gcl.yml builds, which is what +# `make lint` and repo_lint.yaml both use. +--- +version: "2" + +run: + allow-parallel-runners: true + +linters: + default: standard + enable: + - onelinefunc + settings: + custom: + onelinefunc: + type: module + description: A function declaration's body does not share a line with its signature. + original-url: hack/golangci-onelinefunc + exclusions: + generated: lax + +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax diff --git a/atlas-lib/Makefile b/atlas-lib/Makefile index 8b8607444..f09dfa327 100644 --- a/atlas-lib/Makefile +++ b/atlas-lib/Makefile @@ -57,8 +57,8 @@ build: $(GENERATED) ## Build all packages. test: vet ## Run go vet and the unit tests (race + coverage). go test -race -coverprofile=coverage.out ./... -lint: golangci-lint $(GENERATED) ## Run golangci-lint (pinned version, matches CI). - "$(GOLANGCI_LINT)" run ./... +lint: custom-gcl $(GENERATED) ## Run golangci-lint (pinned version, matches CI). + "$(CUSTOM_GCL)" run ./... lint-proto: | buf ## Lint the protocol definitions. @for dir in $(PROTO_DIRS); do \ diff --git a/csi-driver/.golangci.yml b/csi-driver/.golangci.yml index b7452d53a..64831a188 100644 --- a/csi-driver/.golangci.yml +++ b/csi-driver/.golangci.yml @@ -21,7 +21,13 @@ linters: - unconvert - unparam - unused + - onelinefunc settings: + custom: + onelinefunc: + type: module + description: A function declaration's body does not share a line with its signature. + original-url: hack/golangci-onelinefunc revive: rules: - name: comment-spacings diff --git a/csi-driver/Makefile b/csi-driver/Makefile index e26cbf576..ba1aaea5d 100644 --- a/csi-driver/Makefile +++ b/csi-driver/Makefile @@ -50,9 +50,9 @@ lint: golangci include ../scripts/tools.mk .PHONY: golangci -golangci: golangci-lint +golangci: custom-gcl @echo === running golangci-lint - @"$(GOLANGCI_LINT)" run ./... + @"$(CUSTOM_GCL)" run ./... .PHONY: fmt fmt: diff --git a/operator/.golangci.yml b/operator/.golangci.yml index e5b21b0f1..0da86ef63 100644 --- a/operator/.golangci.yml +++ b/operator/.golangci.yml @@ -21,7 +21,13 @@ linters: - unconvert - unparam - unused + - onelinefunc settings: + custom: + onelinefunc: + type: module + description: A function declaration's body does not share a line with its signature. + original-url: hack/golangci-onelinefunc revive: rules: - name: comment-spacings diff --git a/operator/Makefile b/operator/Makefile index 0ec04d3f0..1f6dae897 100644 --- a/operator/Makefile +++ b/operator/Makefile @@ -149,16 +149,16 @@ cleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests @$(KIND) delete cluster --name $(KIND_CLUSTER) .PHONY: lint -lint: golangci-lint ## Run golangci-lint linter - "$(GOLANGCI_LINT)" run +lint: custom-gcl ## Run golangci-lint linter + "$(CUSTOM_GCL)" run .PHONY: lint-fix -lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes - "$(GOLANGCI_LINT)" run --fix +lint-fix: custom-gcl ## Run golangci-lint linter and perform fixes + "$(CUSTOM_GCL)" run --fix .PHONY: lint-config -lint-config: golangci-lint ## Verify golangci-lint linter configuration - "$(GOLANGCI_LINT)" config verify +lint-config: custom-gcl ## Verify golangci-lint linter configuration + "$(CUSTOM_GCL)" config verify ##@ Build diff --git a/scripts/tools.mk b/scripts/tools.mk index 5e6e96843..ffdff5304 100644 --- a/scripts/tools.mk +++ b/scripts/tools.mk @@ -24,6 +24,11 @@ TOOLS_SH := $(REPO_ROOT)/scripts/tools.sh # ── Tool binaries (stable symlinks maintained by scripts/tools.sh) ─────────── GOLANGCI_LINT ?= $(BIN_DIR)/golangci-lint +# The same linter with this repository's own rule compiled in, built by +# `golangci-lint custom` from .custom-gcl.yml. The downloaded binary does not +# know the onelinefunc linter and rejects a config that enables it, so this is +# what `make lint` runs. +CUSTOM_GCL ?= $(BIN_DIR)/custom-gcl KUSTOMIZE ?= $(BIN_DIR)/kustomize CONTROLLER_GEN ?= $(BIN_DIR)/controller-gen ENVTEST ?= $(BIN_DIR)/setup-envtest @@ -40,6 +45,16 @@ BUF ?= $(BIN_DIR)/buf golangci-lint: ## Install golangci-lint (manifest-pinned) into .bin. @"$(TOOLS_SH)" install golangci-lint +# Rebuilt whenever the plugin's sources or .custom-gcl.yml change, and not +# otherwise: the build clones golangci-lint and compiles it, which takes a +# minute or two. +$(CUSTOM_GCL): $(REPO_ROOT)/.custom-gcl.yml $(wildcard $(REPO_ROOT)/hack/golangci-onelinefunc/*.go) $(REPO_ROOT)/hack/golangci-onelinefunc/go.mod | golangci-lint + @echo ">> building custom-gcl (golangci-lint + hack/golangci-onelinefunc)" + @cd "$(REPO_ROOT)" && "$(GOLANGCI_LINT)" custom + +.PHONY: custom-gcl +custom-gcl: $(CUSTOM_GCL) ## Build golangci-lint with this repository's linters compiled in. + .PHONY: kustomize kustomize: ## Install kustomize (manifest-pinned) into .bin. @"$(TOOLS_SH)" install kustomize From d36aed2673049a8f66846f459a01bb7e42cdc9b5 Mon Sep 17 00:00:00 2001 From: "Christoph Engelbert (noctarius)" Date: Fri, 11 Sep 2026 09:17:45 +0200 Subject: [PATCH 19/19] docs(ci): correct the stale checkov note in .mega-linter.yml When checkov's scoping moved from `--directory` arguments to skip-path in .checkov.yml, the two keys went and the paragraph explaining them stayed. It claimed the scope is set by argument, which it is not, and ended on "the ones added below" with nothing below it. It had also run into the next comment, having eaten the blank line between them. It now says where the scope actually lives, and keeps the three measurements as what they are useful for: a record of the routes that do not work, so nobody spends the afternoon rediscovering that FORWARD_EXCLUDED_DIRECTORIES is a no-op or that repeating `--directory` reports every finding once per directory. The finding count in the note below it was from the first clean run, before the exclusions and the fixes. It is roughly a thousand now, not 1,880. --- .mega-linter.yml | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/.mega-linter.yml b/.mega-linter.yml index 0d0abcd36..499aa0777 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -106,21 +106,25 @@ SPELL_CSPELL_FILE_EXTENSIONS: - .md - .txt -# checkov runs in project mode, so MegaLinter hands it `--directory .` and never -# builds a file list. FILTER_REGEX_EXCLUDE above is applied while building that -# list, which means it never reaches checkov: 286 of its 649 findings were in -# paths excluded everywhere else. Project mode is still the right mode, because -# it is what lets checkov render the Helm charts before scanning them and run the -# CKV2_* checks that reason across resources, so the scope is set by argument. +# checkov is scoped in .checkov.yml, not here. It runs in project mode, so +# MegaLinter hands it `--directory .` and never builds a file list, and +# FILTER_REGEX_EXCLUDE above is applied while building that list: it never +# reaches checkov, and 286 of its 649 findings were in paths excluded everywhere +# else. Project mode is still the right mode, because it is what lets checkov +# render the Helm charts before scanning them and run the CKV2_* checks that +# reason across resources, so .checkov.yml narrows it with skip-path instead. # -# Three things were measured rather than assumed. A `directory:` key in -# .checkov.yml is ignored, because the command-line `--directory` overrides it. -# REPOSITORY_CHECKOV_FORWARD_EXCLUDED_DIRECTORIES defaults to true and is a no-op -# here: with a directory named in EXCLUDED_DIRECTORIES, checkov still scanned it -# and the command carried no --skip-path. Removing `--directory` does not remove -# the ones added below, so the pair is what scopes the run. -# Report without blocking, for now. The first clean run left roughly 1,880 -# findings across the tree, so a gate that failed on them would fail every pull +# Three ways of doing it here were tried and are recorded so they are not tried +# again. A `directory:` key in .checkov.yml is ignored, because the +# command-line `--directory` overrides it. +# REPOSITORY_CHECKOV_FORWARD_EXCLUDED_DIRECTORIES defaults to true and is a +# no-op: with a directory named in EXCLUDED_DIRECTORIES, checkov scanned it +# anyway and the command carried no --skip-path. And scoping by repeating +# `--directory` works but reports every finding once per directory, which +# turned 592 distinct findings into 1364. + +# Report without blocking, for now. The tree carries roughly a thousand +# findings, so a gate that failed on them would fail every pull # request from the day it landed and teach everyone to ignore it. It reports # into the checks tab and the uploaded artifact instead. Remove this line once # the backlog is worked down. That is the point at which this becomes a gate.