From 0c14c7191abbbaf63a42f362496389792244c20a Mon Sep 17 00:00:00 2001 From: Dan Clegg Date: Thu, 9 Oct 2025 14:15:15 -0600 Subject: [PATCH] Package updates, Added tests, removed deprecated code --- .github/workflows/ci.yml | 99 +++++++++++++++ .github/workflows/release.yml | 60 +++++++++ .gitignore | 5 + .golangci.yml | 82 ++++++++++++ CONTRIBUTING.md | 226 ++++++++++++++++++++++++++++++++++ Makefile | 87 +++++++++++++ README.md | 133 +++++++++++++++++++- action/action.go | 10 +- action/doc.go | 93 ++++++++++++++ collection/doc.go | 64 ++++++++++ core/component.go | 6 +- core/condition.go | 26 ++-- core/condition_test.go | 28 ++--- core/context.go | 2 +- core/doc.go | 82 ++++++++++++ core/reconciler.go | 47 ++++--- core/reconciler_test.go | 28 ++--- go.mod | 8 +- go.sum | 108 +++++++++++++++- metadata/doc.go | 117 ++++++++++++++++++ renovate.json | 6 + sonar-project.properties | 4 + 22 files changed, 1252 insertions(+), 69 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .golangci.yml create mode 100644 CONTRIBUTING.md create mode 100644 Makefile create mode 100644 action/doc.go create mode 100644 collection/doc.go create mode 100644 core/doc.go create mode 100644 metadata/doc.go create mode 100644 renovate.json create mode 100644 sonar-project.properties diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ec718c0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,99 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + cache: true + + - name: Install dependencies + run: make tools + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v7 + with: + version: v2.5.0 # Supports Go 1.25+ + args: --timeout=5m + + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + cache: true + + - name: Run tests + run: make test + + - name: Generate coverage report + run: make test-coverage + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + files: ./coverage.out + flags: unittests + name: codecov-umbrella + fail_ci_if_error: false + + verify: + name: Verify + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + cache: true + + - name: Install dependencies + run: make tools + + - name: Verify dependencies and formatting + run: make verify + + # Commenting out example build since it requires generated code + # The example is for reference/documentation purposes + # build: + # name: Build Example + # runs-on: ubuntu-latest + # steps: + # - name: Checkout code + # uses: actions/checkout@v4 + + # - name: Set up Go + # uses: actions/setup-go@v5 + # with: + # go-version-file: 'go.mod' + # cache: true + + all-checks: + name: All Checks Passed + needs: [lint, test, verify] + runs-on: ubuntu-latest + steps: + - name: All checks passed + run: echo "All CI checks passed successfully!" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..52dcbc5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,60 @@ +name: Release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + release: + name: Create Release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + cache: true + + - name: Run tests + run: make test + + - name: Generate changelog + id: changelog + run: | + # Get the tag name + TAG=${GITHUB_REF#refs/tags/} + + # Get previous tag + PREV_TAG=$(git describe --tags --abbrev=0 $TAG^ 2>/dev/null || echo "") + + # Generate changelog + if [ -z "$PREV_TAG" ]; then + CHANGELOG=$(git log --pretty=format:"- %s (%h)" $TAG) + else + CHANGELOG=$(git log --pretty=format:"- %s (%h)" $PREV_TAG..$TAG) + fi + + # Save changelog to file for multi-line handling + echo "$CHANGELOG" > changelog.txt + + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + name: Release ${{ steps.changelog.outputs.tag }} + body_path: changelog.txt + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index ee770a6..14e6544 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,13 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +coverage.html # Dependency directories (remove the comment below to include it) # vendor/ .idea/ +.trunk/ +.vscode/ +*.swp +*~ diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..41136c5 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,82 @@ +version: "2" +run: + tests: false +linters: + default: all + disable: + - containedctx + - copyloopvar + - cyclop + - err113 + - exhaustive + - exhaustruct + - forbidigo + - forcetypeassert + - gochecknoglobals + - gochecknoinits + - gocognit + - godot + - godox + - gosec + - govet + - ireturn + - mnd + - nestif + - nlreturn + - noinlineerr + - nonamedreturns + - nosprintfhostport + - perfsprint + - prealloc + - tagliatelle + - varnamelen + - wrapcheck + - wsl + - wsl_v5 + settings: + depguard: + rules: + main: + files: + - '!**/*_a _file.go' + deny: + - pkg: github.com/sirupsen/logrus + desc: not allowed + - pkg: github.com/pkg/errors + desc: Should be replaced by standard lib errors package + errorlint: + comparison: false + funlen: + lines: 200 + statements: 100 + gosec: + excludes: + - G306 + lll: + line-length: 120 + nakedret: + max-func-lines: 35 + tagalign: + align: false + sort: true + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - zz_generated.* + - third_party$ + - builtin$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - zz_generated.* + - third_party$ + - builtin$ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c169a9f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,226 @@ +# Contributing to controller-util + +Thank you for your interest in contributing to controller-util! This document provides guidelines and instructions for contributing. + +## Code of Conduct + +- Be respectful and inclusive +- Focus on constructive feedback +- Help create a welcoming environment for all contributors + +## Getting Started + +### Prerequisites + +- Go 1.25 or later +- Git +- Basic understanding of Kubernetes controllers and controller-runtime + +### Development Setup + +1. **Fork and clone the repository** + +```bash +git clone https://github.com/dominodatalab/controller-util.git +cd controller-util +``` + +1. **Install development tools** + +```bash +make tools +``` + +1. **Run tests to verify setup** + +```bash +make test +``` + +## Development Workflow + +### Making Changes + +1. **Create a feature branch** + +```bash +git checkout -b feature/your-feature-name +``` + +1. **Make your changes** + - Write clear, concise code + - Follow Go best practices and idioms + - Add tests for new functionality + - Update documentation as needed + +1. **Run checks locally** + +```bash +make fmt # Format code +make lint # Run linters +make test # Run tests +make ci # Run all CI checks +``` + +1. **Commit your changes** + +```bash +git add . +git commit -m "feat: add new feature description" +``` + +We follow [Conventional Commits](https://www.conventionalcommits.org/): + +- `feat:` - New feature +- `fix:` - Bug fix +- `docs:` - Documentation changes +- `test:` - Test additions or changes +- `refactor:` - Code refactoring +- `chore:` - Maintenance tasks + +1. **Push and create a Pull Request** + +```bash +git push origin feature/your-feature-name +``` + +## Pull Request Guidelines + +### Before Submitting + +- [ ] All tests pass (`make test`) +- [ ] Code is formatted (`make fmt`) +- [ ] Linters pass (`make lint`) +- [ ] Documentation is updated +- [ ] Commit messages follow conventional commits +- [ ] Changes are focused and atomic + +### PR Description + +Include: + +- **What**: Brief description of the change +- **Why**: Motivation and context +- **How**: Implementation approach (if non-obvious) +- **Testing**: How you tested the changes +- **Related Issues**: Link to any related issues + +### Review Process + +1. Automated CI checks must pass +2. At least one maintainer approval required +3. Address review feedback promptly +4. Keep PR updated with main branch + +## Coding Standards + +### Go Style + +- Follow [Effective Go](https://golang.org/doc/effective_go.html) +- Use `gofmt` and `goimports` for formatting (automated via `make fmt`) +- Run `golangci-lint` and fix issues (automated via `make lint`) + +### Code Organization + +```plain +controller-util/ +├── action/ # Resource action utilities +├── collection/ # Collection helpers +├── core/ # Core reconciler and component interfaces +├── metadata/ # Kubernetes metadata helpers +``` + +### Testing + +- Write table-driven tests where appropriate +- Use meaningful test names that describe the scenario +- Mock external dependencies +- Aim for high code coverage on new features + +```go +func TestComponentName(t *testing.T) { + tests := []struct { + name string + input interface{} + want interface{} + wantErr bool + }{ + { + name: "valid case", + input: validInput, + want: expectedOutput, + }, + // ... more test cases + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // test implementation + }) + } +} +``` + +### Documentation + +- Add package-level godoc comments for all packages +- Document exported types, functions, and methods +- Keep README.md updated with new features + +## Project Structure + +### Adding New Components + +When adding new utilities or components: + +1. Choose appropriate package (`action/`, `core/`, etc.) +2. Add comprehensive tests +3. Document with godoc comments +4. Update README.md if user-facing + +## Reporting Issues + +### Bug Reports + +Include: + +- Clear description of the issue +- Steps to reproduce +- Expected vs actual behavior +- Environment details (Go version, K8s version) +- Relevant logs or error messages + +### Feature Requests + +Include: + +- Use case description +- Proposed API or interface +- Alternative approaches considered +- Willingness to implement + +## Getting Help + +- Review existing [documentation](./README.md) +- Check [existing issues](https://github.com/dominodatalab/controller-util/issues) +- Ask questions in pull requests or issues + +## Release Process + +(For maintainers) + +1. Update version in relevant files +2. Update CHANGELOG.md +3. Create git tag: `git tag -a v0.x.0 -m "Release v0.x.0"` +4. Push tag: `git push origin v0.x.0` +5. GitHub Actions will handle the release + +## License + +By contributing, you agree that your contributions will be licensed under the same license as the project (see [LICENSE](./LICENSE)). + +## Questions? + +Feel free to open an issue for any questions or clarifications needed. + +Thank you for contributing! 🎉 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f39d4a5 --- /dev/null +++ b/Makefile @@ -0,0 +1,87 @@ +SHELL:=/bin/bash + +.PHONY: help test test-verbose test-coverage lint fmt vet tidy verify clean ci all tools + +# Go parameters +GOCMD=go +GOTEST=$(GOCMD) test +GOVET=$(GOCMD) vet +GOFMT=$(GOCMD) fmt +GOMOD=$(GOCMD) mod + +# Directories +PACKAGES=$(shell $(GOCMD) list ./...) + +# Default target +.DEFAULT_GOAL := help + +##@ Development + +test: ## Run all tests + @echo "Running tests..." + $(GOTEST) -v -timeout=5m -race ./... + +test-verbose: ## Run tests with verbose output + @echo "Running tests (verbose)..." + $(GOTEST) -v -race ./... + +test-coverage: ## Run tests with coverage report + @echo "Running tests with coverage..." + $(GOTEST) -race -coverprofile=coverage.out -covermode=atomic ./... + $(GOCMD) tool cover -html=coverage.out -o coverage.html + @echo "Coverage report generated: coverage.html" + +lint: tools ## Run linter suite + golangci-lint version + golangci-lint run + +fmt: tools ## Format Go code + @echo "Formatting code..." + goimports -w . + $(GOFMT) ./... + +vet: ## Run go vet + @echo "Running go vet..." + $(GOVET) ./... + +tidy: ## Tidy and verify dependencies + @echo "Tidying dependencies..." + $(GOMOD) tidy -v + $(GOMOD) verify + +verify: tools ## Ensure generated files and dependencies are up-to-date + goimports -w . + $(GOFMT) ./... + $(GOMOD) tidy -v + @if [ -n "$(git status --porcelain go.mod go.sum)" ]; then \ + echo "Error: go.mod or go.sum has uncommitted changes after 'go mod tidy'"; \ + git diff go.mod go.sum; \ + exit 1; \ + fi + +clean: ## Clean build artifacts and test cache + @echo "Cleaning..." + @$(GOCMD) clean -testcache + @rm -f coverage.out coverage.html + +ci: verify lint test ## Run all CI checks (verify, lint, test) + @echo "All CI checks passed!" + +all: lint test ## Run linting and tests + +##@ Misc + +tools: ## Install go tooling + @echo "Installing golangci-lint..." + @command -v golangci-lint >/dev/null 2>&1 || { \ + echo "golangci-lint not found, installing..."; \ + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest; \ + } + @command -v goimports >/dev/null 2>&1 || { \ + echo "goimports not found, installing..."; \ + go install golang.org/x/tools/cmd/goimports@latest; \ + } + +.DEFAULT_GOAL:=help +help: ## Display this help + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) diff --git a/README.md b/README.md index ce33431..59e62db 100644 --- a/README.md +++ b/README.md @@ -1 +1,132 @@ -# controller-util \ No newline at end of file +# controller-util + +A Go library providing common utilities and patterns for building Kubernetes controllers using [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime). + +## Overview + +`controller-util` simplifies Kubernetes controller development by providing: + +- **Component-based reconciliation**: Build controllers from composable, reusable components +- **Finalizer management**: Automatic finalizer handling with component-level cleanup +- **Condition helpers**: Simplified status condition management +- **Action utilities**: Common operations for owned resources (create, update, delete) +- **Metadata providers**: Standard Kubernetes label generation +- **Patch utilities**: Strategic merge patching for resource updates + +## Installation + +```bash +go get github.com/dominodatalab/controller-util +``` + +## Usage + +### Basic Controller Setup + +```go +import ( + "github.com/dominodatalab/controller-util/core" + ctrl "sigs.k8s.io/controller-runtime" +) + +// Create a reconciler with manager +reconciler := core.NewReconciler(mgr). + For(&v1.YourCRD{}). + Named("your-controller"). + Component("component-name", yourComponent). + Build() +``` + +### Component Interface + +Components implement the `core.Component` interface: + +```go +type Component interface { + Reconcile(*Context) (ctrl.Result, error) +} +``` + +Components can also implement optional interfaces: +- `OwnedComponent`: For resources owned by the controller +- `FinalizerComponent`: For cleanup logic during deletion +- `InitializerComponent`: For setup during controller initialization + +### Condition Management + +```go +func (c *YourComponent) Reconcile(ctx *core.Context) (ctrl.Result, error) { + // Set conditions on your CRD status + ctx.Conditions.SetTrue("Ready", "AllComponentsReady", "All components reconciled successfully") + ctx.Conditions.SetFalse("Available", "PodNotReady", "Pod is not ready yet") + + return ctrl.Result{}, nil +} +``` + +### Action Utilities + +```go +import "github.com/dominodatalab/controller-util/action" + +// Create or update an owned resource +err := action.CreateOrUpdateOwnedResource(ctx, owner, deployment) + +// Delete resources if they exist +err := action.DeleteIfExists(ctx, service, configMap) + +// Delete all PVCs matching label selector +err := action.DeleteStorage(ctx, []client.ListOption{ + client.InNamespace(namespace), + client.MatchingLabels(labels), +}) +``` + +### Metadata Helpers + +```go +import "github.com/dominodatalab/controller-util/metadata" + +provider := metadata.NewProvider("myapp", + metadata.WithCreator("myapp-controller"), + metadata.WithManager("myapp-operator"), +) + +// Generate standard labels +labels := provider.StandardLabels(obj, metadata.AppComponent("api"), extraLabels) + +// Generate match labels for selectors +matchLabels := provider.MatchLabels(obj, metadata.AppComponent("api")) +``` + +## Development + +### Prerequisites + +- Go 1.25+ +- golangci-lint (installed via `make tools`) + +### Common Tasks + +```bash +make help # Show all available targets +make test # Run tests +make lint # Run linters +make fmt # Format code +make ci # Run all CI checks +make tools # Install development tools +``` + +## Dependencies + +- Kubernetes v0.34.x +- controller-runtime v0.22.x +- Go 1.25+ + +## License + +See [LICENSE](LICENSE) file for details. + +## Related Projects + +- [hephaestus](https://github.com/dominodatalab/hephaestus) - Uses controller-util for building ImageBuild controllers diff --git a/action/action.go b/action/action.go index 4b81896..1d3d1ff 100644 --- a/action/action.go +++ b/action/action.go @@ -75,7 +75,10 @@ func DeleteStorage(ctx *core.Context, opts []client.ListOption) error { pvcList := &corev1.PersistentVolumeClaimList{} listOpts := (&client.ListOptions{}).ApplyOptions(opts) - ctx.Log.Info("Querying for persistent volume claims", "namespace", listOpts.Namespace, "labels", listOpts.LabelSelector.String()) + ctx.Log.Info( + "Querying for persistent volume claims", + "namespace", listOpts.Namespace, "labels", + listOpts.LabelSelector.String()) if err := ctx.Client.List(ctx, pvcList, opts...); err != nil { ctx.Log.Error(err, "Cannot list persistent volume claims") return err @@ -95,7 +98,10 @@ func DeleteStorage(ctx *core.Context, opts []client.ListOption) error { return nil } -func createOwnedResource(ctx *core.Context, owner metav1.Object, controlled client.Object) (found client.Object, gvk schema.GroupVersionKind, err error) { +func createOwnedResource( + ctx *core.Context, + owner metav1.Object, + controlled client.Object) (found client.Object, gvk schema.GroupVersionKind, err error) { if err = ctrl.SetControllerReference(owner, controlled, ctx.Scheme); err != nil { return } diff --git a/action/doc.go b/action/doc.go new file mode 100644 index 0000000..893e069 --- /dev/null +++ b/action/doc.go @@ -0,0 +1,93 @@ +// Package action provides high-level utilities for common resource management operations +// in Kubernetes controllers. +// +// This package simplifies common controller patterns for creating, updating, and deleting +// Kubernetes resources while properly handling ownership and error conditions. +// +// # Creating Owned Resources +// +// Use CreateOwnedResource to create a resource with proper controller ownership: +// +// deployment := buildDeployment(myapp) +// err := action.CreateOwnedResource(ctx, myapp, deployment) +// +// This automatically: +// - Sets the controller reference +// - Creates the resource if it doesn't exist +// - Returns nil if the resource already exists +// +// # Creating or Updating Resources +// +// Use CreateOrUpdateOwnedResource for idempotent resource management: +// +// service := buildService(myapp) +// err := action.CreateOrUpdateOwnedResource(ctx, myapp, service) +// +// This automatically: +// - Creates the resource if it doesn't exist +// - Calculates a strategic merge patch if it exists +// - Updates only when changes are detected +// - Preserves generated fields (e.g., Service.Spec.ClusterIP) +// +// # Deleting Resources +// +// Use DeleteIfExists to safely delete resources: +// +// err := action.DeleteIfExists(ctx, deployment, service, configMap) +// +// This: +// - Deletes each resource if it exists +// - Returns nil if resources don't exist (idempotent) +// - Returns errors for actual deletion failures +// +// # Deleting Storage +// +// Use DeleteStorage to clean up PersistentVolumeClaims: +// +// opts := []client.ListOption{ +// client.InNamespace(namespace), +// client.MatchingLabels(labels), +// } +// err := action.DeleteStorage(ctx, opts) +// +// This is useful during finalizer cleanup to remove persistent storage. +// +// # Special Handling +// +// CreateOrUpdateOwnedResource handles special cases for certain resource types: +// - Service: Preserves Spec.ClusterIP (immutable generated field) +// - Job: Preserves Spec.Selector (immutable generated field) +// +// # Example Component +// +// type DeploymentComponent struct{} +// +// func (c *DeploymentComponent) Reconcile(ctx *core.Context) (ctrl.Result, error) { +// myapp := ctx.Object.(*v1.MyApp) +// deployment := c.buildDeployment(myapp) +// +// // Creates or updates the deployment +// if err := action.CreateOrUpdateOwnedResource(ctx, myapp, deployment); err != nil { +// return ctrl.Result{}, err +// } +// +// return ctrl.Result{}, nil +// } +// +// func (c *DeploymentComponent) Finalize(ctx *core.Context) (ctrl.Result, bool, error) { +// myapp := ctx.Object.(*v1.MyApp) +// deployment := &appsv1.Deployment{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: "myapp-deployment", +// Namespace: myapp.Namespace, +// }, +// } +// +// // Clean up deployment during deletion +// if err := action.DeleteIfExists(ctx, deployment); err != nil { +// return ctrl.Result{}, false, err +// } +// +// return ctrl.Result{}, true, nil +// } +package action diff --git a/collection/doc.go b/collection/doc.go new file mode 100644 index 0000000..8a5d2d6 --- /dev/null +++ b/collection/doc.go @@ -0,0 +1,64 @@ +// Package collection provides utility functions for working with Go collections, +// particularly focused on map operations commonly needed in Kubernetes controllers. +// +// This package contains helper functions that simplify common collection manipulation +// tasks when building controller components. +// +// # Map Utilities +// +// MergeStringMaps merges string maps with source values taking precedence: +// +// base := map[string]string{ +// "app": "myapp", +// "env": "dev", +// } +// additional := map[string]string{ +// "env": "prod", // This will override base["env"] +// "tier": "frontend", // This will be added +// } +// +// result := collection.MergeStringMaps(additional, base) +// // result = { +// // "app": "myapp", +// // "env": "prod", // Overridden +// // "tier": "frontend", // Added +// // } +// +// # Common Use Cases +// +// Combining standard labels with custom labels: +// +// standardLabels := map[string]string{ +// "app.kubernetes.io/name": "myapp", +// "app.kubernetes.io/instance": "prod", +// } +// customLabels := map[string]string{ +// "environment": "production", +// "team": "platform", +// } +// +// allLabels := collection.MergeStringMaps(customLabels, standardLabels) +// +// Merging user-provided labels with defaults: +// +// func buildPodLabels(userLabels map[string]string) map[string]string { +// defaults := map[string]string{ +// "app": "myapp", +// "version": "v1", +// } +// return collection.MergeStringMaps(userLabels, defaults) +// } +// +// # Note on Mutability +// +// MergeStringMaps modifies the destination map in place and returns it for convenience. +// If you need to preserve the original destination map, create a copy first: +// +// original := map[string]string{"key": "value"} +// copy := make(map[string]string, len(original)) +// for k, v := range original { +// copy[k] = v +// } +// result := collection.MergeStringMaps(newValues, copy) +// // original is unchanged, result contains merged values +package collection diff --git a/core/component.go b/core/component.go index 76b5832..f95b0a4 100644 --- a/core/component.go +++ b/core/component.go @@ -6,7 +6,7 @@ import ( ) type Component interface { - Reconcile(*Context) (ctrl.Result, error) + Reconcile(ctx *Context) (ctrl.Result, error) } type OwnedComponent interface { @@ -15,9 +15,9 @@ type OwnedComponent interface { } type InitializerComponent interface { - Initialize(*Context, *ctrl.Builder) error + Initialize(ctx *Context, builder *ctrl.Builder) error } type FinalizerComponent interface { - Finalize(*Context) (ctrl.Result, bool, error) + Finalize(ctx *Context) (ctrl.Result, bool, error) } diff --git a/core/condition.go b/core/condition.go index 1e38239..220c059 100644 --- a/core/condition.go +++ b/core/condition.go @@ -12,19 +12,19 @@ type ConditionObject interface { GetConditions() *[]metav1.Condition } -type conditionHelper struct { +type ConditionHelper struct { obj client.Object pending map[string]metav1.Condition } -func NewConditionHelper(obj client.Object) *conditionHelper { - return &conditionHelper{ +func NewConditionHelper(obj client.Object) *ConditionHelper { + return &ConditionHelper{ obj: obj, pending: map[string]metav1.Condition{}, } } -func (h *conditionHelper) Flush() error { +func (h *ConditionHelper) Flush() error { // NOTE: what do we do if obj does not adhere to interface, assuming they have not conditions? condObj, ok := h.obj.(ConditionObject) if !ok { @@ -39,14 +39,16 @@ func (h *conditionHelper) Flush() error { return nil } -func (h *conditionHelper) SetCondition(cond metav1.Condition) { +func (h *ConditionHelper) SetCondition(cond metav1.Condition) { if cond.ObservedGeneration == 0 { cond.ObservedGeneration = h.obj.GetGeneration() } h.pending[cond.Type] = cond } -func (h *conditionHelper) Set(conditionType string, status metav1.ConditionStatus, reason, message string) { +func (h *ConditionHelper) Set( + conditionType string, status metav1.ConditionStatus, reason, message string, +) { h.SetCondition(metav1.Condition{ Type: conditionType, Status: status, @@ -55,23 +57,25 @@ func (h *conditionHelper) Set(conditionType string, status metav1.ConditionStatu }) } -func (h *conditionHelper) Setf(conditionType string, status metav1.ConditionStatus, reason, message string, args ...interface{}) { +func (h *ConditionHelper) Setf( + conditionType string, status metav1.ConditionStatus, reason, message string, args ...interface{}, +) { h.Set(conditionType, status, reason, fmt.Sprintf(message, args...)) } -func (h *conditionHelper) SetFalse(conditionType string, reason, message string) { +func (h *ConditionHelper) SetFalse(conditionType string, reason, message string) { h.Set(conditionType, metav1.ConditionFalse, reason, message) } -func (h *conditionHelper) SetTrue(conditionType string, reason, message string) { +func (h *ConditionHelper) SetTrue(conditionType string, reason, message string) { h.Set(conditionType, metav1.ConditionTrue, reason, message) } -func (h *conditionHelper) SetUnknown(conditionType string, reason, message string) { +func (h *ConditionHelper) SetUnknown(conditionType string, reason, message string) { h.Set(conditionType, metav1.ConditionUnknown, reason, message) } -func (h *conditionHelper) SetfUnknown(conditionType string, reason, message string, args ...interface{}) { +func (h *ConditionHelper) SetfUnknown(conditionType string, reason, message string, args ...interface{}) { h.Setf(conditionType, metav1.ConditionUnknown, reason, message, args...) } diff --git a/core/condition_test.go b/core/condition_test.go index aaea55b..710e700 100644 --- a/core/condition_test.go +++ b/core/condition_test.go @@ -35,7 +35,7 @@ func NewMockObject(name string, generation int64) *MockConditionObject { } } -func TestConditionHelper_New(t *testing.T) { +func TestConditionHelperNew(t *testing.T) { obj := NewMockObject("test-object", 42) helper := NewConditionHelper(obj) @@ -44,7 +44,7 @@ func TestConditionHelper_New(t *testing.T) { assert.Empty(t, helper.pending) } -func TestConditionHelper_SetCondition(t *testing.T) { +func TestConditionHelperSetCondition(t *testing.T) { obj := NewMockObject("test-object", 42) helper := NewConditionHelper(obj) @@ -65,7 +65,7 @@ func TestConditionHelper_SetCondition(t *testing.T) { assert.Equal(t, int64(42), helper.pending["TestCondition"].ObservedGeneration) } -func TestConditionHelper_Set(t *testing.T) { +func TestConditionHelperSet(t *testing.T) { obj := NewMockObject("test-object", 42) helper := NewConditionHelper(obj) @@ -82,7 +82,7 @@ func TestConditionHelper_Set(t *testing.T) { assert.Equal(t, int64(42), pendingCond.ObservedGeneration) } -func TestConditionHelper_Setf(t *testing.T) { +func TestConditionHelperSetf(t *testing.T) { obj := NewMockObject("test-object", 42) helper := NewConditionHelper(obj) @@ -91,7 +91,7 @@ func TestConditionHelper_Setf(t *testing.T) { assert.Equal(t, "Count: 5", helper.pending["TestCondition"].Message) } -func TestConditionHelper_SetTrue(t *testing.T) { +func TestConditionHelperSetTrue(t *testing.T) { obj := NewMockObject("test-object", 1) helper := NewConditionHelper(obj) @@ -100,7 +100,7 @@ func TestConditionHelper_SetTrue(t *testing.T) { assert.Equal(t, metav1.ConditionTrue, helper.pending["TestCondition"].Status) } -func TestConditionHelper_SetFalse(t *testing.T) { +func TestConditionHelperSetFalse(t *testing.T) { obj := NewMockObject("test-object", 1) helper := NewConditionHelper(obj) @@ -109,7 +109,7 @@ func TestConditionHelper_SetFalse(t *testing.T) { assert.Equal(t, metav1.ConditionFalse, helper.pending["TestCondition"].Status) } -func TestConditionHelper_SetUnknown(t *testing.T) { +func TestConditionHelperSetUnknown(t *testing.T) { obj := NewMockObject("test-object", 1) helper := NewConditionHelper(obj) @@ -118,7 +118,7 @@ func TestConditionHelper_SetUnknown(t *testing.T) { assert.Equal(t, metav1.ConditionUnknown, helper.pending["TestCondition"].Status) } -func TestConditionHelper_SetfUnknown(t *testing.T) { +func TestConditionHelperSetfUnknown(t *testing.T) { obj := NewMockObject("test-object", 1) helper := NewConditionHelper(obj) @@ -128,7 +128,7 @@ func TestConditionHelper_SetfUnknown(t *testing.T) { assert.Equal(t, "Status for resource is unknown", helper.pending["TestCondition"].Message) } -func TestConditionHelper_Flush(t *testing.T) { +func TestConditionHelperFlush(t *testing.T) { obj := NewMockObject("test-object", 1) helper := NewConditionHelper(obj) @@ -160,7 +160,7 @@ func TestConditionHelper_Flush(t *testing.T) { assert.Empty(t, helper.pending) } -func TestConditionHelper_FlushNonConditionObject(t *testing.T) { +func TestConditionHelperFlushNonConditionObject(t *testing.T) { obj := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", @@ -458,26 +458,26 @@ func TestConditionReasonAndMessage(t *testing.T) { func TestConditionHelperSetFieldsExplicitly(t *testing.T) { testCases := []struct { name string - helperFunc func(*conditionHelper, string, string, string) + helperFunc func(*ConditionHelper, string, string, string) expectedStatus metav1.ConditionStatus }{ { name: "SetTrue", - helperFunc: func(h *conditionHelper, condType, reason, message string) { + helperFunc: func(h *ConditionHelper, condType, reason, message string) { h.SetTrue(condType, reason, message) }, expectedStatus: metav1.ConditionTrue, }, { name: "SetFalse", - helperFunc: func(h *conditionHelper, condType, reason, message string) { + helperFunc: func(h *ConditionHelper, condType, reason, message string) { h.SetFalse(condType, reason, message) }, expectedStatus: metav1.ConditionFalse, }, { name: "SetUnknown", - helperFunc: func(h *conditionHelper, condType, reason, message string) { + helperFunc: func(h *ConditionHelper, condType, reason, message string) { h.SetUnknown(condType, reason, message) }, expectedStatus: metav1.ConditionUnknown, diff --git a/core/context.go b/core/context.go index 8da4e8a..7d4b951 100644 --- a/core/context.go +++ b/core/context.go @@ -23,5 +23,5 @@ type Context struct { Client client.Client Scheme *runtime.Scheme Recorder record.EventRecorder - Conditions *conditionHelper + Conditions *ConditionHelper } diff --git a/core/doc.go b/core/doc.go new file mode 100644 index 0000000..32edcdf --- /dev/null +++ b/core/doc.go @@ -0,0 +1,82 @@ +// Package core provides the fundamental building blocks for creating Kubernetes controllers +// using a component-based architecture. +// +// The core package includes: +// - Reconciler: A flexible, component-based reconciler that manages the reconciliation lifecycle +// - Component interfaces: Define reusable controller components with optional lifecycle hooks +// - Context: Rich context object passed to components containing clients, loggers, and helpers +// - Condition helpers: Utilities for managing Kubernetes status conditions +// - Patch utilities: Strategic merge patching support for resource updates +// +// # Basic Usage +// +// Create a reconciler with components: +// +// reconciler := core.NewReconciler(mgr). +// For(&v1.MyCRD{}). +// Named("my-controller"). +// Component("deployment", &DeploymentComponent{}). +// Component("service", &ServiceComponent{}). +// Build() +// +// # Component Architecture +// +// Components implement the Component interface and handle specific resource types: +// +// type MyComponent struct{} +// +// func (c *MyComponent) Reconcile(ctx *core.Context) (ctrl.Result, error) { +// // Reconciliation logic +// return ctrl.Result{}, nil +// } +// +// Components can implement optional interfaces: +// - OwnedComponent: Declare resource ownership for automatic watch setup +// - FinalizerComponent: Define cleanup logic during resource deletion +// - InitializerComponent: Perform setup during controller initialization +// +// # Context Object +// +// The Context provides components with everything needed for reconciliation: +// - Client: Kubernetes client for API operations +// - Log: Structured logger specific to the component +// - Object: The resource being reconciled +// - Conditions: Helper for managing status conditions +// - Patch: Utilities for calculating and applying patches +// - Recorder: Event recorder for Kubernetes events +// - Scheme: Runtime scheme for object conversion +// +// # Condition Management +// +// Use the condition helper to set status conditions: +// +// ctx.Conditions.SetTrue("Ready", "ComponentReady", "Component is ready") +// ctx.Conditions.SetFalse("Available", "PodNotReady", "Pod not ready") +// +// Conditions are automatically flushed to the resource status at the end of reconciliation. +// +// # Finalizers +// +// Components implementing FinalizerComponent automatically get finalizers: +// +// func (c *MyComponent) Finalize(ctx *core.Context) (ctrl.Result, bool, error) { +// // Cleanup logic +// return ctrl.Result{}, true, nil // true indicates finalization is complete +// } +// +// The reconciler automatically: +// - Registers finalizers when resources are created +// - Calls Finalize when resources are being deleted +// - Removes finalizers when cleanup is complete +// +// # Patching +// +// The Patch utility enables efficient resource updates using strategic merge: +// +// patchResult, err := ctx.Patch.Maker.Calculate(current, desired, opts...) +// if !patchResult.IsEmpty() { +// // Apply changes +// } +// +// This ensures only actual changes trigger updates, reducing API server load. +package core diff --git a/core/reconciler.go b/core/reconciler.go index cdff79c..922f0e6 100644 --- a/core/reconciler.go +++ b/core/reconciler.go @@ -180,7 +180,11 @@ func (r *Reconciler) ReduceReconcileLogging() *Reconciler { return r } -func (r *Reconciler) Watches(apiType client.Object, eventHandler handler.EventHandler, opts ...builder.WatchesOption) *Reconciler { +func (r *Reconciler) Watches( + apiType client.Object, + eventHandler handler.EventHandler, + opts ...builder.WatchesOption, +) *Reconciler { r.controllerBuilder = r.controllerBuilder.Watches(apiType, eventHandler, opts...) return r } @@ -312,8 +316,7 @@ func (r *Reconciler) Reconcile(rootCtx context.Context, req ctrl.Request) (ctrl. cleanObj := obj.DeepCopyObject().(client.Object) // skip reconcile when annotated - skip, ok := obj.GetAnnotations()[SkipReconcileAnnotation] - if ok && skip == "true" { + if skip, ok := obj.GetAnnotations()[SkipReconcileAnnotation]; ok && skip == "true" { log.Info("Skipping reconcile due to annotation") return ctrl.Result{}, nil } @@ -332,13 +335,14 @@ func (r *Reconciler) Reconcile(rootCtx context.Context, req ctrl.Request) (ctrl. Data: r.contextData, } - // reconcile components var finalRes ctrl.Result var errs []error - for _, rc := range r.components { - res := ctrl.Result{} - var err error + for _, rc := range r.components { + var ( + res ctrl.Result + err error + ) ctx.Log = compLog.WithName(rc.name) if ctx.Object.GetDeletionTimestamp().IsZero() { @@ -368,12 +372,18 @@ func (r *Reconciler) Reconcile(rootCtx context.Context, req ctrl.Request) (ctrl. } ctx.Conditions.Flush() - if res.Requeue { - finalRes.Requeue = true + + // Convert deprecated Requeue to RequeueAfter using retry period + if res.RequeueAfter == 0 { + res.RequeueAfter = r.defaultRetryPeriod } - if res.RequeueAfter != 0 && (finalRes.RequeueAfter == 0 || finalRes.RequeueAfter > res.RequeueAfter) { - finalRes.RequeueAfter = res.RequeueAfter + // Aggregate RequeueAfter (use shortest non-zero duration) + if res.RequeueAfter > 0 { + if finalRes.RequeueAfter == 0 || finalRes.RequeueAfter > res.RequeueAfter { + finalRes.RequeueAfter = res.RequeueAfter + } } + if err != nil { log.Error(err, "Component reconciliation failed", "component", rc.name) errs = append(errs, err) @@ -397,15 +407,22 @@ func (r *Reconciler) Reconcile(rootCtx context.Context, req ctrl.Request) (ctrl. patchOpts := &client.PatchOptions{FieldManager: r.name} - // ignore NotFound errors when patching object/status since the object may already be deleted - if err := r.client.Patch(ctx, currentMeta, client.MergeFrom(cleanMeta), patchOpts); err != nil && !apierrors.IsNotFound(err) { + // Use rootCtx instead of ctx.Context to maintain proper context inheritance + if err := r.client.Patch( + rootCtx, + currentMeta, + client.MergeFrom(cleanMeta), + patchOpts); err != nil && !apierrors.IsNotFound(err) { return ctrl.Result{}, fmt.Errorf("error patching metadata: %w", err) } - if err := r.client.Status().Patch(ctx, ctx.Object, client.MergeFrom(cleanObj)); err != nil && !apierrors.IsNotFound(err) { + + if err := r.client.Status().Patch( + rootCtx, + ctx.Object, + client.MergeFrom(cleanObj)); err != nil && !apierrors.IsNotFound(err) { return ctrl.Result{}, fmt.Errorf("error patching status: %w", err) } - // condense all error messages into one log.Info("Reconciliation complete") return finalRes, utilerrors.NewAggregate(errs) } diff --git a/core/reconciler_test.go b/core/reconciler_test.go index 977b0fd..b4d6efa 100644 --- a/core/reconciler_test.go +++ b/core/reconciler_test.go @@ -159,7 +159,7 @@ func createTestDeployment(name, namespace string) *appsv1.Deployment { } } -func TestReconciler_Basic(t *testing.T) { +func TestReconcilerBasic(t *testing.T) { cl, s := setupTest(t) deploy := createTestDeployment("test-deployment", "default") @@ -168,7 +168,7 @@ func TestReconciler_Basic(t *testing.T) { mgr := NewMockManager(cl, s, testr.New(t)) mockComp := new(MockComponent) - expectedResult := ctrl.Result{Requeue: true} + expectedResult := ctrl.Result{RequeueAfter: time.Second} mockComp.On("Reconcile", mock.AnythingOfType("*core.Context")).Return(expectedResult, nil) r := NewReconciler(mgr). @@ -199,7 +199,7 @@ func TestReconciler_Basic(t *testing.T) { mockComp.AssertExpectations(t) } -func TestReconciler_MultipleComponents(t *testing.T) { +func TestReconcilerMultipleComponents(t *testing.T) { cl, s := setupTest(t) deploy := createTestDeployment("test-deployment", "default") @@ -210,7 +210,7 @@ func TestReconciler_MultipleComponents(t *testing.T) { mockComp1 := new(MockComponent) mockComp2 := new(MockComponent) - result1 := ctrl.Result{Requeue: true} + result1 := ctrl.Result{RequeueAfter: time.Second} result2 := ctrl.Result{RequeueAfter: 5 * time.Minute} mockComp1.On("Reconcile", mock.AnythingOfType("*core.Context")).Return(result1, nil) @@ -241,13 +241,13 @@ func TestReconciler_MultipleComponents(t *testing.T) { res, err := r.Reconcile(context.Background(), req) assert.NoError(t, err) - assert.True(t, res.Requeue) - assert.Equal(t, 5*time.Minute, res.RequeueAfter) + assert.True(t, res.RequeueAfter > 0) + assert.Equal(t, time.Second, res.RequeueAfter) // Shortest duration wins mockComp1.AssertExpectations(t) mockComp2.AssertExpectations(t) } -func TestReconciler_NotFound(t *testing.T) { +func TestReconcilerNotFound(t *testing.T) { cl, s := setupTest(t) mgr := NewMockManager(cl, s, testr.New(t)) @@ -292,7 +292,7 @@ func TestReconciler_NotFound(t *testing.T) { mockComp.AssertExpectations(t) } -func TestReconciler_SkipReconcile(t *testing.T) { +func TestReconcilerSkipReconcile(t *testing.T) { cl, s := setupTest(t) deploy := createTestDeployment("test-deployment", "default") @@ -330,10 +330,10 @@ func TestReconciler_SkipReconcile(t *testing.T) { assert.NoError(t, err) assert.Equal(t, ctrl.Result{}, res) - mockComp.AssertNotCalled(t, "Reconcile") + mockComp.AssertNotCalled(t, "ReconcileG") } -func TestReconciler_OwnedComponent(t *testing.T) { +func TestReconcilerOwnedComponent(t *testing.T) { cl, s := setupTest(t) deploy := createTestDeployment("test-deployment", "default") @@ -391,7 +391,7 @@ func TestReconciler_OwnedComponent(t *testing.T) { mockOwnedComp.AssertExpectations(t) } -func TestReconciler_FinalizerComponent(t *testing.T) { +func TestReconcilerFinalizerComponent(t *testing.T) { cl, s := setupTest(t) deploy := createTestDeployment("test-deployment", "default") @@ -442,7 +442,7 @@ func TestReconciler_FinalizerComponent(t *testing.T) { } -func TestReconciler_ComponentError(t *testing.T) { +func TestReconcilerComponentError(t *testing.T) { cl, s := setupTest(t) deploy := createTestDeployment("test-deployment", "default") @@ -483,7 +483,7 @@ func TestReconciler_ComponentError(t *testing.T) { mockComp.AssertExpectations(t) } -func TestReconciler_MultipleComponentErrors(t *testing.T) { +func TestReconcilerMultipleComponentErrors(t *testing.T) { cl, s := setupTest(t) deploy := createTestDeployment("test-deployment", "default") @@ -532,7 +532,7 @@ func TestReconciler_MultipleComponentErrors(t *testing.T) { mockComp2.AssertExpectations(t) } -func TestReconciler_InitializerComponent(t *testing.T) { +func TestReconcilerInitializerComponent(t *testing.T) { t.Skip("Skipping initializer test since we're not using Build() in the tests") } diff --git a/go.mod b/go.mod index f5cfd31..3deab1a 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/dominodatalab/controller-util -go 1.24.0 +go 1.25 -toolchain go1.24.1 +toolchain go1.25.2 require ( github.com/banzaicloud/k8s-objectmatcher v1.8.0 @@ -11,7 +11,7 @@ require ( k8s.io/api v0.34.1 k8s.io/apimachinery v0.34.1 k8s.io/client-go v0.34.1 - sigs.k8s.io/controller-runtime v0.22.1 + sigs.k8s.io/controller-runtime v0.22.2 ) require ( @@ -20,7 +20,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect - github.com/evanphx/json-patch v5.7.0+incompatible // indirect + github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect diff --git a/go.sum b/go.sum index 0539bbd..e0a4f40 100644 --- a/go.sum +++ b/go.sum @@ -24,11 +24,13 @@ github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkg github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= -github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= +github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -37,19 +39,29 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= +github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.25.1 h1:6uwVsx+/OuvFVPqfQmOOPsqTcm5/GkBhNwLqIR916n8= @@ -78,6 +90,32 @@ github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3 github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8= github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk= github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg= +github.com/go-openapi/swag v0.25.1 h1:6uwVsx+/OuvFVPqfQmOOPsqTcm5/GkBhNwLqIR916n8= +github.com/go-openapi/swag v0.25.1/go.mod h1:bzONdGlT0fkStgGPd3bhZf1MnuPkf2YAys6h+jZipOo= +github.com/go-openapi/swag/cmdutils v0.25.1 h1:nDke3nAFDArAa631aitksFGj2omusks88GF1VwdYqPY= +github.com/go-openapi/swag/cmdutils v0.25.1/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0= +github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs= +github.com/go-openapi/swag/fileutils v0.25.1 h1:rSRXapjQequt7kqalKXdcpIegIShhTPXx7yw0kek2uU= +github.com/go-openapi/swag/fileutils v0.25.1/go.mod h1:+NXtt5xNZZqmpIpjqcujqojGFek9/w55b3ecmOdtg8M= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8= +github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg= +github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw= +github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc= +github.com/go-openapi/swag/mangling v0.25.1 h1:XzILnLzhZPZNtmxKaz/2xIGPQsBsvmCjrJOWGNz/ync= +github.com/go-openapi/swag/mangling v0.25.1/go.mod h1:CdiMQ6pnfAgyQGSOIYnZkXvqhnnwOn997uXZMAd/7mQ= +github.com/go-openapi/swag/netutils v0.25.1 h1:2wFLYahe40tDUHfKT1GRC4rfa5T1B4GWZ+msEFA4Fl4= +github.com/go-openapi/swag/netutils v0.25.1/go.mod h1:CAkkvqnUJX8NV96tNhEQvKz8SQo2KF0f7LleiJwIeRE= +github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw= +github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg= +github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA= +github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8= +github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk= +github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -100,12 +138,16 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -127,6 +169,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -136,6 +180,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -145,6 +191,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= @@ -166,6 +214,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= @@ -175,10 +225,20 @@ github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7D github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI= +github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= @@ -186,6 +246,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -202,6 +264,10 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -224,9 +290,13 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -234,6 +304,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -246,12 +318,20 @@ golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -264,12 +344,16 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -289,12 +373,16 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -310,11 +398,19 @@ k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= @@ -325,8 +421,8 @@ k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZ k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.22.1 h1:Ah1T7I+0A7ize291nJZdS1CabF/lB4E++WizgV24Eqg= -sigs.k8s.io/controller-runtime v0.22.1/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY= +sigs.k8s.io/controller-runtime v0.22.2 h1:cK2l8BGWsSWkXz09tcS4rJh95iOLney5eawcK5A33r4= +sigs.k8s.io/controller-runtime v0.22.2/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= @@ -334,7 +430,11 @@ sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxO sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/metadata/doc.go b/metadata/doc.go new file mode 100644 index 0000000..457e081 --- /dev/null +++ b/metadata/doc.go @@ -0,0 +1,117 @@ +// Package metadata provides utilities for generating standard Kubernetes labels and metadata +// following the recommended Kubernetes label conventions. +// +// This package implements the standard Kubernetes application labels as defined in: +// https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ +// +// # Standard Label Keys +// +// The package defines constants for all standard application labels: +// - app.kubernetes.io/name: The name of the application +// - app.kubernetes.io/instance: A unique name identifying the instance +// - app.kubernetes.io/version: The current version of the application +// - app.kubernetes.io/component: The component within the architecture +// - app.kubernetes.io/part-of: The name of a higher level application +// - app.kubernetes.io/managed-by: The tool being used to manage the application +// - app.kubernetes.io/created-by: The controller/user who created the resource +// +// # Provider Usage +// +// Create a metadata provider for your application: +// +// provider := metadata.NewProvider("myapp", +// metadata.WithCreator("myapp-controller"), +// metadata.WithManager("myapp-operator"), +// metadata.WithVersion(func(obj client.Object) string { +// return obj.(*v1.MyApp).Spec.Version +// }), +// ) +// +// # Generating Labels +// +// Use StandardLabels for comprehensive label sets: +// +// labels := provider.StandardLabels(myapp, metadata.AppComponent("api"), extraLabels) +// // Returns: { +// // "app.kubernetes.io/name": "myapp", +// // "app.kubernetes.io/instance": "myapp-prod", +// // "app.kubernetes.io/component": "api", +// // "app.kubernetes.io/created-by": "myapp-controller", +// // "app.kubernetes.io/managed-by": "myapp-operator", +// // ... plus any extra labels +// // } +// +// Use MatchLabels for label selectors: +// +// matchLabels := provider.MatchLabels(myapp, metadata.AppComponent("api")) +// // Returns minimal set for matching: +// // { +// // "app.kubernetes.io/name": "myapp", +// // "app.kubernetes.io/instance": "myapp-prod", +// // "app.kubernetes.io/component": "api", +// // } +// +// # Instance Names +// +// Generate consistent names for resources: +// +// name := provider.InstanceName(myapp, metadata.AppComponent("api")) +// // Returns: "myapp-prod-myapp-api" +// +// name := provider.InstanceName(myapp, metadata.AppComponentNone) +// // Returns: "myapp-prod-myapp" +// +// # Dynamic Labels +// +// Add dynamic labels based on the resource: +// +// provider := metadata.NewProvider("myapp", +// metadata.WithDynamicLabels(func(obj client.Object) map[string]string { +// myapp := obj.(*v1.MyApp) +// return map[string]string{ +// "environment": myapp.Spec.Environment, +// "tier": myapp.Spec.Tier, +// } +// }), +// ) +// +// # Example Component +// +// type ServiceComponent struct { +// provider *metadata.Provider +// } +// +// func NewServiceComponent() *ServiceComponent { +// return &ServiceComponent{ +// provider: metadata.NewProvider("myapp", +// metadata.WithCreator("myapp-controller"), +// ), +// } +// } +// +// func (c *ServiceComponent) buildService(myapp *v1.MyApp) *corev1.Service { +// labels := c.provider.StandardLabels(myapp, metadata.AppComponentNone, nil) +// matchLabels := c.provider.MatchLabels(myapp, metadata.AppComponentNone) +// +// return &corev1.Service{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: c.provider.InstanceName(myapp, metadata.AppComponentNone), +// Namespace: myapp.Namespace, +// Labels: labels, +// }, +// Spec: corev1.ServiceSpec{ +// Selector: matchLabels, +// // ... +// }, +// } +// } +// +// # Benefits +// +// Using this package ensures: +// - Consistent labeling across all resources +// - Compliance with Kubernetes best practices +// - Easy resource querying and filtering +// - Clear ownership and management tracking +// - Support for multi-tenancy and multi-component applications +package metadata diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..66b0d9a --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "github>cerebrotech/renovate-config:go-minimal" + ] +} \ No newline at end of file diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..0a07daa --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,4 @@ +sonar.projectName=controller-util +sonar.projectKey=controller-util +sonar.test.inclusions=**/*_test.go +sonar.exclusions=**/*_test.go \ No newline at end of file