├── .editorconfig ├── .gitattributes ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── dependabot-multimodule.yml │ └── release.yml ├── .gitignore ├── .golangci.yaml ├── Earthfile ├── LICENSE ├── README.md ├── aggregator ├── aggregator │ ├── controller.go │ ├── controller_test.go │ └── next_event_pool.go ├── constants │ └── constants.go ├── go.mod ├── go.sum ├── main.go ├── observer │ ├── logging.go │ ├── metrics.go │ └── observer.go ├── synctime │ ├── interpreter.go │ └── synctime.go └── updatetrigger │ └── updatetrigger.go ├── allinone ├── go.mod ├── go.sum └── main.go ├── apis ├── constants.go ├── go.mod ├── go.sum ├── register.go └── v1alpha1 │ ├── doc.go │ ├── register.go │ ├── types.go │ └── zz_generated.deepcopy.go ├── chart ├── Chart.yaml ├── STYLE.md ├── crds │ └── podseidon.kubewharf.io_podprotectors.yaml ├── templates │ ├── _aggregator.yaml │ ├── _args.yaml │ ├── _deployment.yaml │ ├── _generator.yaml │ ├── _kubeconfig.yaml │ ├── _labels.yaml │ ├── _rbac.yaml │ ├── _webhook.yaml │ ├── aggregator.yaml │ ├── generator.yaml │ └── webhook.yaml └── values.yaml ├── client ├── clientset │ └── versioned │ │ ├── clientset.go │ │ ├── fake │ │ ├── clientset_generated.go │ │ ├── doc.go │ │ └── register.go │ │ ├── scheme │ │ ├── doc.go │ │ └── register.go │ │ └── typed │ │ └── apis │ │ └── v1alpha1 │ │ ├── apis_client.go │ │ ├── doc.go │ │ ├── fake │ │ ├── doc.go │ │ ├── fake_apis_client.go │ │ └── fake_podprotector.go │ │ ├── generated_expansion.go │ │ └── podprotector.go ├── go.mod ├── go.sum ├── informers │ └── externalversions │ │ ├── apis │ │ ├── interface.go │ │ └── v1alpha1 │ │ │ ├── interface.go │ │ │ └── podprotector.go │ │ ├── factory.go │ │ ├── generic.go │ │ └── internalinterfaces │ │ └── factory_interfaces.go └── listers │ └── apis │ └── v1alpha1 │ ├── expansion_generated.go │ └── podprotector.go ├── docs ├── coverage-analysis.md ├── deployment.md ├── design.md └── development.md ├── generator ├── constants │ └── constants.go ├── generator │ ├── controller.go │ └── controller_test.go ├── go.mod ├── go.sum ├── main.go ├── monitor │ ├── monitor.go │ └── monitor_test.go ├── observer │ ├── logging.go │ ├── metrics.go │ └── observer.go └── resource │ ├── deployment │ └── deployment.go │ └── interface.go ├── go.work ├── tests ├── .golangci.jq ├── aggregator │ └── aggregator.go ├── assets │ ├── audit-kubeconfig-core.yaml │ ├── audit-kubeconfig-worker-1.yaml │ ├── audit-kubeconfig-worker-2.yaml │ ├── audit-policy.yaml │ ├── cfssl-ca-csr.json │ ├── cfssl-config.json │ ├── cfssl-webhook-csr.json │ ├── e2e-node.yaml │ ├── kwok-cluster-core.yaml │ ├── kwok-cluster-worker.yaml │ ├── kwok-node.yaml │ ├── run.sh │ └── values.json ├── generator │ └── generator.go ├── go.mod ├── go.sum ├── suite_test.go ├── util │ ├── expect.go │ └── setup.go └── webhook │ └── webhook.go ├── tools ├── boilerplate.txt ├── go.mod ├── go.sum ├── go.work ├── go.work.sum └── imports.go ├── typos.toml ├── util ├── cmd │ ├── cmd.go │ └── cmd_test.go ├── component │ ├── declare.go │ ├── interface.go │ ├── mock.go │ └── mux.go ├── defaultconfig │ └── options.go ├── errors │ ├── errors.go │ ├── tag.go │ └── tag_test.go ├── flag │ ├── enum.go │ ├── int.go │ ├── labelselector.go │ ├── map.go │ ├── slice.go │ └── subset.go ├── go.mod ├── go.sum ├── haschange │ ├── haschange.go │ └── haschange_test.go ├── healthz │ ├── observer │ │ ├── logging.go │ │ ├── metrics.go │ │ └── observer.go │ └── server.go ├── http │ └── http.go ├── iter │ └── iter.go ├── kube │ ├── client.go │ ├── event.go │ ├── informers.go │ ├── leaderelection.go │ ├── metrics │ │ ├── component.go │ │ └── lifted.go │ ├── observer │ │ ├── leaderelection_logging.go │ │ └── leaderelection_metrics.go │ └── util.go ├── labelindex │ ├── canonical │ │ ├── selector.go │ │ └── set.go │ ├── interface.go │ ├── locked.go │ ├── matchable │ │ └── matchable.go │ ├── mm │ │ └── multimap.go │ ├── namespaced.go │ ├── selectors.go │ ├── selectors_bench_test.go │ ├── selectors_test.go │ ├── sets.go │ ├── sets_bench_test.go │ ├── sets_test.go │ └── settrie │ │ ├── multi.go │ │ ├── trie.go │ │ └── trie_test.go ├── o11y │ ├── component.go │ ├── klog │ │ └── klog.go │ ├── metrics │ │ ├── api.go │ │ ├── component.go │ │ ├── http.go │ │ ├── multifield.go │ │ └── unique │ │ │ └── unique.go │ └── observer.go ├── optional │ ├── optional.go │ └── optional_test.go ├── podprotector │ ├── informer.go │ ├── multi_informer.go │ ├── observer │ │ ├── logging.go │ │ ├── metrics.go │ │ └── observer.go │ ├── podprotector.go │ └── single_source_provider.go ├── pprof │ └── pprof.go ├── retrybatch │ ├── fusedrc │ │ └── fusedrc.go │ ├── messages.go │ ├── observer │ │ ├── metrics.go │ │ └── observer.go │ ├── pool.go │ ├── pool_test.go │ └── syncbarrier │ │ └── barrier.go ├── shutdown │ └── shutdown.go ├── sort │ └── sort.go ├── util │ ├── atomic.go │ ├── channel.go │ ├── maps.go │ ├── math.go │ ├── math_test.go │ ├── slices.go │ ├── sync.go │ └── types.go └── worker │ ├── observer │ ├── logging.go │ ├── metrics.go │ └── observer.go │ ├── prereq.go │ └── worker.go └── webhook ├── go.mod ├── go.sum ├── handler ├── handler.go ├── pool_adapter.go └── requires_pod_name.go ├── main.go ├── observer ├── logging.go ├── metrics.go └── observer.go └── server └── server.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.go] 4 | indent_style = tab 5 | indent_size = 4 6 | 7 | [*.md] 8 | indent_style = space 9 | indent_size = 2 10 | 11 | [Earthfile] 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.sh] 16 | indent_style = space 17 | indent_size = 4 18 | 19 | [*.jq] 20 | indent_style = space 21 | indent_size = 4 22 | 23 | [*.json] 24 | indent_style = space 25 | indent_size = 4 26 | 27 | [*.yaml] 28 | indent_style = space 29 | indent_size = 2 30 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | zz_generated*.go linguist-generated=true 3 | apis/{clientset,listers,informers} linguist-generated=true 4 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # ByteDance Contributor Code of Conduct 2 | 3 | Our Pledge 4 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 5 | 6 | Our Standards 7 | Examples of behavior that contributes to creating a positive environment include: 8 | - Using welcoming and inclusive language 9 | - Being respectful of differing viewpoints and experiences 10 | - Gracefully accepting constructive criticism 11 | - Focusing on what is best for the community 12 | - Showing empathy towards other community members 13 | 14 | Examples of unacceptable behavior by participants include: 15 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 16 | - Trolling, insulting/derogatory comments, and personal or political attacks 17 | - Public or private harassment 18 | - Publishing others’ private information, such as a physical or electronic address, without explicit permission 19 | - Other conduct which could reasonably be considered inappropriate in a professional setting 20 | 21 | Our Responsibilities 22 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 23 | 24 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 25 | 26 | Scope 27 | This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 28 | 29 | Enforcement 30 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at kubewharf.conduct@bytedance.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 31 | 32 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership. 33 | 34 | Attribution 35 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 36 | 37 | For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq 38 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | 3 | Thank you for contributing to Podseidon. 4 | 5 | ## Code of Conduct 6 | 7 | Please check our [Code of Conduct](CODE_OF_CONDUCT.md). 8 | 9 | ## Reporting security issues 10 | 11 | If you find a security issue in Podseidon, 12 | please report to directly. 13 | 14 | ## Reporting general issues 15 | 16 | If you have any feedback for the project, feel free to open an issue. 17 | Please be reminded to remove the sensitive data from your logs before posting. 18 | 19 | To make communication more efficient, 20 | we suggest searching for duplicates before filing a new issue. 21 | 22 | ## Code/documentation contribution 23 | 24 | All pull requests are welcome. 25 | Please check the [development guide](../docs/development.md) 26 | for information on the environment setup, style guide and CI requirement. 27 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: /tools 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: gomod 8 | directories: 9 | - /apis 10 | - /client 11 | - /util 12 | - /generator 13 | - /aggregator 14 | - /webhook 15 | - /allinone 16 | - /tests 17 | schedule: 18 | interval: weekly 19 | ignore: 20 | # k8s.io/apimachinery and k8s.io/api updates always come with k8s.io/client-go updates 21 | - dependency-name: k8s.io/apimachinery 22 | - dependency-name: k8s.io/api 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | test: 10 | name: Run unit tests 11 | runs-on: [ubuntu-24.04] 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: earthly/actions-setup@v1 15 | with: 16 | version: 'v0.8.0' 17 | - name: Run unit tests 18 | run: ssh-agent earthly +test 19 | - name: Upload coverage reports to Codecov 20 | uses: codecov/codecov-action@v5 21 | with: 22 | token: ${{ secrets.CODECOV_TOKEN }} 23 | verbose: true 24 | files: output/coverage.out 25 | 26 | e2e: 27 | name: Run e2e tests 28 | runs-on: [ubuntu-24.04] 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: earthly/actions-setup@v1 32 | with: 33 | version: 'v0.8.0' 34 | - name: Run e2e tests 35 | run: ssh-agent earthly -P +e2e 36 | - name: Upload logs 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: output 40 | path: output 41 | 42 | lint: 43 | name: golangci-lint 44 | runs-on: [ubuntu-24.04] 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: earthly/actions-setup@v1 48 | with: 49 | version: 'v0.8.0' 50 | - name: Run linters 51 | run: ssh-agent earthly +lint 52 | 53 | typos: 54 | name: Check typos 55 | runs-on: [ubuntu-24.04] 56 | steps: 57 | - uses: actions/checkout@v4 58 | - uses: crate-ci/typos@v1 59 | with: 60 | config: typos.toml 61 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: ["v*"] 6 | 7 | jobs: 8 | build: 9 | name: Build docker images 10 | runs-on: [ubuntu-24.04] 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: earthly/actions-setup@v1 14 | with: 15 | version: 'v0.8.0' 16 | - name: Docker login for ghcr 17 | uses: docker/login-action@v2 18 | with: 19 | registry: ghcr.io 20 | username: ${{github.actor}} 21 | password: ${{secrets.GITHUB_TOKEN}} 22 | - name: Build Docker images 23 | run: ssh-agent earthly --push +build --output_tag=${{github.ref_name}} 24 | chart: 25 | name: Build helm chart 26 | runs-on: [ubuntu-24.04] 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: earthly/actions-setup@v1 30 | with: 31 | version: 'v0.8.0' 32 | - name: Docker login for ghcr 33 | uses: docker/login-action@v2 34 | with: 35 | registry: ghcr.io 36 | username: ${{github.actor}} 37 | password: ${{secrets.GITHUB_TOKEN}} 38 | - name: Build helm chart 39 | run: | 40 | ssh-agent earthly --ci --secret docker_config="$(cat ~/.docker/config.json)" \ 41 | +chart-push --chart_version=${{github.ref_name}} --app_version=${{github.ref_name}} 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /output 2 | /go.work.sum 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Podseidon 2 | === 3 | 4 | A pod deletion protection webhook with 5 | high scalability, disaster tolerance and multi-cluster support. 6 | 7 | ## Key features 8 | 9 | - Optional vendor-agnostic multi-cluster support 10 | - No dependency on intermediate workload controllers 11 | - Scalable for large clusters with high throughput 12 | - Designed for extreme disasters such as etcd corruption 13 | 14 | ## Why? 15 | 16 | When a user workload enters the Kubernetes control plane, 17 | it has to go through many steps before the pods are finally processed by the kubelet &mbsp; 18 | deployment controller, replicaset controller, multi-cluster management, CD pipelines, 19 | and probably a lot of other asynchronous controllers e.g. evictions from reschedulers, 20 | along with the common dependencies such as kube-apiserver and etcd. 21 | User services may be disrupted if anything goes wrong in this long chain of events. 22 | 23 | Kubernetes is self-healing and eventually consistent &mbsp; 24 | if something fails, it will *eventually* work again. 25 | But how long is *eventually*? 26 | From the perspective of control plane stability, 27 | we do not want anything to fail, even if it *will* self-heal eventually. 28 | But due to the declarative nature of Kubernetes, 29 | it is very easy for an accidental operation 30 | to propagate its impact before humans can respond to it. 31 | 32 | Podseidon provides an end-to-end safey net to minimize the risk: 33 | if a workload request declares a requirement of *n* available pods, 34 | the control plane can never disrupt the service beyond the minimum *n* pods. 35 | 36 | Of course, it is impossible to prevent service disruptions 37 | caused by the data plane itself (e.g. an entire data center caught fire) 38 | or if Podseidon itself fails (e.g. malicious attacker deleted the entire cluster), 39 | but Podseidon effectively reduces any single point of failure in the control plane, 40 | especially in multi-cluster setups where Podseidon and workload control plane are deployed separately. 41 | Check out [what scenarios](docs/coverage-analysis.md) Podseidon can prevent. 42 | 43 | ## Documentation 44 | 45 | - [Deployment and operating procedure](docs/deployment.md) 46 | - [Disaster coverage analysis](docs/coverage-analysis.md) 47 | - [Technical documentation](docs/design.md) 48 | - [Development guide](docs/development.md) 49 | 50 | ## Architecture 51 | 52 | Since Podseidon supports multi-cluster setup, 53 | in this diagram we express the storage of workload declarations ("core") 54 | and management of pods ("worker") as separate clusters. 55 | However, Podseidon can be deployed for a single cluster, 56 | in which "core" and "worker" would be the same cluster. 57 | 58 | ```mermaid 59 | graph LR 60 | 61 | subgraph "Core" 62 | deploy[(Deployment)] 63 | sts[(StatefulSet)] 64 | generator[podseidon-generator] 65 | ppr[(PodProtector)] 66 | webhook[[podseidon-webhook]] 67 | end 68 | 69 | deploy & sts -->|ListWatch| generator 70 | generator --->|Create/UpdateSpec/Delete| ppr 71 | 72 | subgraph "Worker" 73 | pod[(Pod)] 74 | aggregator[podseidon-aggregator] 75 | worker-apiserver[kube-apiserver] 76 | end 77 | 78 | pod -->|Delete| worker-apiserver 79 | worker-apiserver -->|AdmissionReview| webhook 80 | webhook -->|"read + UpdateStatus(admissionHistory)"| ppr 81 | pod --->|ListWatch| aggregator 82 | aggregator -->|"UpdateStatus(aggregation)"| ppr 83 | ``` 84 | 85 | See [design.md](docs/design.md) for more information. 86 | 87 | ## License 88 | 89 | Podseidon is licensed under [Apache License 2.0](./LICENSE). 90 | -------------------------------------------------------------------------------- /aggregator/constants/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package constants 16 | 17 | import "github.com/kubewharf/podseidon/util/kube" 18 | 19 | const ( 20 | WorkerClusterName kube.ClusterName = "worker" 21 | ) 22 | 23 | const LeaderPhase kube.InformerPhase = "main" 24 | 25 | const ElectorName kube.ElectorName = "aggregator" 26 | 27 | var ElectorArgs = kube.ElectorArgs{ 28 | ClusterName: WorkerClusterName, 29 | ElectorName: ElectorName, 30 | } 31 | 32 | const AnnotUpdateTriggerTime string = "podseidon.kubewharf.io/update-trigger-time" 33 | -------------------------------------------------------------------------------- /aggregator/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kubewharf/podseidon/aggregator 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | github.com/kubewharf/podseidon/apis v0.0.0 9 | github.com/kubewharf/podseidon/util v0.0.0 10 | github.com/stretchr/testify v1.10.0 11 | k8s.io/api v0.33.1 12 | k8s.io/apimachinery v0.33.1 13 | k8s.io/client-go v0.33.1 14 | k8s.io/klog/v2 v2.130.1 15 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 16 | ) 17 | 18 | require ( 19 | github.com/beorn7/perks v1.0.1 // indirect 20 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 21 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 22 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 23 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 24 | github.com/go-logr/logr v1.4.2 // indirect 25 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 26 | github.com/go-openapi/jsonreference v0.21.0 // indirect 27 | github.com/go-openapi/swag v0.23.0 // indirect 28 | github.com/gogo/protobuf v1.3.2 // indirect 29 | github.com/google/gnostic-models v0.6.9 // indirect 30 | github.com/google/go-cmp v0.7.0 // indirect 31 | github.com/google/uuid v1.6.0 // indirect 32 | github.com/josharian/intern v1.0.0 // indirect 33 | github.com/json-iterator/go v1.1.12 // indirect 34 | github.com/kubewharf/podseidon/client v0.0.0 // indirect 35 | github.com/mailru/easyjson v0.7.7 // indirect 36 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 37 | github.com/modern-go/reflect2 v1.0.2 // indirect 38 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 39 | github.com/pkg/errors v0.9.1 // indirect 40 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 41 | github.com/prometheus/client_golang v1.22.0 // indirect 42 | github.com/prometheus/client_model v0.6.1 // indirect 43 | github.com/prometheus/common v0.62.0 // indirect 44 | github.com/prometheus/procfs v0.15.1 // indirect 45 | github.com/spf13/pflag v1.0.6 // indirect 46 | github.com/x448/float16 v0.8.4 // indirect 47 | golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect 48 | golang.org/x/net v0.39.0 // indirect 49 | golang.org/x/oauth2 v0.27.0 // indirect 50 | golang.org/x/sync v0.13.0 // indirect 51 | golang.org/x/sys v0.32.0 // indirect 52 | golang.org/x/term v0.31.0 // indirect 53 | golang.org/x/text v0.24.0 // indirect 54 | golang.org/x/time v0.9.0 // indirect 55 | google.golang.org/protobuf v1.36.5 // indirect 56 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 57 | gopkg.in/inf.v0 v0.9.1 // indirect 58 | gopkg.in/yaml.v3 v3.0.1 // indirect 59 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 60 | sigs.k8s.io/controller-runtime v0.21.0 // indirect 61 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 62 | sigs.k8s.io/randfill v1.0.0 // indirect 63 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 64 | sigs.k8s.io/yaml v1.4.0 // indirect 65 | ) 66 | 67 | replace ( 68 | github.com/kubewharf/podseidon/apis => ../apis 69 | github.com/kubewharf/podseidon/client => ../client 70 | github.com/kubewharf/podseidon/util => ../util 71 | ) 72 | 73 | replace ( 74 | k8s.io/api => github.com/kubewharf/kubernetes/staging/src/k8s.io/api v0.0.0-20250527032544-4b83dd839ef1 75 | k8s.io/apimachinery => github.com/kubewharf/kubernetes/staging/src/k8s.io/apimachinery v0.0.0-20250527032544-4b83dd839ef1 76 | k8s.io/client-go => github.com/kubewharf/kubernetes/staging/src/k8s.io/client-go v0.0.0-20250527032544-4b83dd839ef1 77 | ) 78 | -------------------------------------------------------------------------------- /aggregator/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "k8s.io/utils/clock" 19 | 20 | "github.com/kubewharf/podseidon/util/cmd" 21 | "github.com/kubewharf/podseidon/util/component" 22 | healthzobserver "github.com/kubewharf/podseidon/util/healthz/observer" 23 | kubeobserver "github.com/kubewharf/podseidon/util/kube/observer" 24 | "github.com/kubewharf/podseidon/util/o11y/metrics" 25 | pprutil "github.com/kubewharf/podseidon/util/podprotector" 26 | pprutilobserver "github.com/kubewharf/podseidon/util/podprotector/observer" 27 | "github.com/kubewharf/podseidon/util/pprof" 28 | "github.com/kubewharf/podseidon/util/util" 29 | workerobserver "github.com/kubewharf/podseidon/util/worker/observer" 30 | 31 | "github.com/kubewharf/podseidon/aggregator/aggregator" 32 | aggregatorobserver "github.com/kubewharf/podseidon/aggregator/observer" 33 | "github.com/kubewharf/podseidon/aggregator/synctime" 34 | "github.com/kubewharf/podseidon/aggregator/updatetrigger" 35 | ) 36 | 37 | func main() { 38 | cmd.Run( 39 | component.RequireDep(pprof.New(util.Empty{})), 40 | component.RequireDep(metrics.NewHttp(metrics.HttpArgs{})), 41 | healthzobserver.Provide, 42 | workerobserver.Provide, 43 | kubeobserver.ProvideElector, 44 | aggregatorobserver.Provide, 45 | pprutilobserver.ProvideInformer, 46 | component.RequireDep(aggregator.NewController(aggregator.ControllerArgs{ 47 | Clock: clock.RealClock{}, 48 | })), 49 | pprutil.RequireSingleSourceProvider(pprutil.SingleSourceProviderArgs{ClusterName: "core"}, true), 50 | synctime.DefaultImpls, 51 | component.RequireDep(updatetrigger.New(updatetrigger.Args{})), 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /aggregator/observer/logging.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package observer 16 | 17 | import ( 18 | "context" 19 | 20 | "k8s.io/klog/v2" 21 | 22 | "github.com/kubewharf/podseidon/util/component" 23 | "github.com/kubewharf/podseidon/util/o11y" 24 | o11yklog "github.com/kubewharf/podseidon/util/o11y/klog" 25 | "github.com/kubewharf/podseidon/util/util" 26 | ) 27 | 28 | func NewLoggingObserver() Observer { 29 | return Observer{ 30 | StartReconcile: func(ctx context.Context, arg StartReconcile) (context.Context, context.CancelFunc) { 31 | logger := klog.FromContext(ctx) 32 | logger = logger.WithValues( 33 | "namespace", arg.Namespace, 34 | "name", arg.Name, 35 | ) 36 | logger.V(2).WithCallDepth(1).Info("reconcile start") 37 | return klog.NewContext(ctx, logger), util.NoOp 38 | }, 39 | EndReconcile: func(ctx context.Context, arg EndReconcile) { 40 | logger := klog.FromContext(ctx) 41 | logger = logger.V(4).WithCallDepth(1).WithValues( 42 | "hasChange", arg.HasChange.String(), 43 | "action", arg.Action, 44 | ) 45 | if arg.Err != nil { 46 | logger.Error(arg.Err, "reconcile error") 47 | } else { 48 | logger.Info("reconcile complete") 49 | } 50 | }, 51 | StartEnqueue: func(ctx context.Context, _ StartEnqueue) (context.Context, context.CancelFunc) { 52 | return ctx, util.NoOp 53 | }, 54 | EndEnqueue: func(context.Context, EndEnqueue) {}, 55 | EnqueueError: func(ctx context.Context, arg EnqueueError) { 56 | klog.FromContext(ctx). 57 | WithCallDepth(1). 58 | Error(arg.Err, "handle reflector event", "namespace", arg.Namespace, "name", arg.Name) 59 | }, 60 | Aggregated: func(ctx context.Context, arg Aggregated) { 61 | klog.FromContext(ctx).V(4).WithCallDepth(1).Info( 62 | "Updated PodProtector aggregation status", 63 | "numPods", arg.NumPods, 64 | "readyReplicas", arg.ReadyReplicas, 65 | "scheduledReplicas", arg.ScheduledReplicas, 66 | "runningReplicas", arg.RunningReplicas, 67 | "availableReplicas", arg.AvailableReplicas, 68 | ) 69 | }, 70 | NextEventPoolCurrentSize: nil, 71 | NextEventPoolCurrentLatency: nil, 72 | NextEventPoolSingleDrain: nil, 73 | TriggerPodCreate: func(ctx context.Context, arg TriggerPodCreate) { 74 | if arg.Err != nil { 75 | klog.FromContext(ctx).Error(arg.Err, "error creating update-trigger pod") 76 | } 77 | }, 78 | TriggerPodUpdate: func(ctx context.Context, arg TriggerPodUpdate) { 79 | if arg.Err != nil { 80 | klog.FromContext(ctx).Error(arg.Err, "error updating update-trigger pod") 81 | } 82 | }, 83 | } 84 | } 85 | 86 | func ProvideLogging() component.Declared[Observer] { 87 | return o11y.Provide( 88 | func(requests *component.DepRequests) util.Empty { 89 | o11yklog.RequestKlogArgs(requests) 90 | return util.Empty{} 91 | }, 92 | func(util.Empty) Observer { 93 | return NewLoggingObserver() 94 | }, 95 | ) 96 | } 97 | -------------------------------------------------------------------------------- /aggregator/synctime/synctime.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package synctime 16 | 17 | import ( 18 | "sync/atomic" 19 | "time" 20 | 21 | corev1 "k8s.io/api/core/v1" 22 | "k8s.io/utils/ptr" 23 | 24 | "github.com/kubewharf/podseidon/util/errors" 25 | "github.com/kubewharf/podseidon/util/optional" 26 | "github.com/kubewharf/podseidon/util/util" 27 | ) 28 | 29 | func New(interpreter PodInterpreter) (InitialMarker, Notifier, Reader) { 30 | lastInformerSync := &atomic.Pointer[time.Time]{} 31 | 32 | // Since we only use this on pod informers, 33 | // pods always receive multiple update events (due to deletion status updates), 34 | // so we don't need to worry about not sending events on deleted objects. 35 | 36 | initialMarker := func() { 37 | // Empty list, initialize pointer with current timestamp if it is the initial event. 38 | _ = lastInformerSync.CompareAndSwap(nil, ptr.To(time.Now())) 39 | } 40 | 41 | notifier := func(pod *corev1.Pod) error { 42 | newTime, err := interpreter.Interpret(pod) 43 | if err != nil { 44 | return errors.TagWrapf("InterpretPod", err, "interpreting sync timestamp from pod") 45 | } 46 | 47 | util.AtomicExtrema(lastInformerSync, ptr.To(newTime), func(left, right *time.Time) bool { 48 | if left == nil { 49 | return true 50 | } 51 | 52 | if right == nil { 53 | return false 54 | } 55 | 56 | return right.After(*left) 57 | }) 58 | 59 | return nil 60 | } 61 | 62 | reader := func() optional.Optional[time.Time] { 63 | return optional.FromPtr(lastInformerSync.Load()) 64 | } 65 | 66 | return initialMarker, notifier, reader 67 | } 68 | 69 | type InitialMarker func() 70 | 71 | type Notifier func(*corev1.Pod) error 72 | 73 | type Reader func() optional.Optional[time.Time] 74 | -------------------------------------------------------------------------------- /allinone/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "k8s.io/utils/clock" 19 | 20 | "github.com/kubewharf/podseidon/util/cmd" 21 | "github.com/kubewharf/podseidon/util/component" 22 | healthzobserver "github.com/kubewharf/podseidon/util/healthz/observer" 23 | kubeobserver "github.com/kubewharf/podseidon/util/kube/observer" 24 | "github.com/kubewharf/podseidon/util/o11y/metrics" 25 | pprutil "github.com/kubewharf/podseidon/util/podprotector" 26 | pprutilobserver "github.com/kubewharf/podseidon/util/podprotector/observer" 27 | "github.com/kubewharf/podseidon/util/pprof" 28 | "github.com/kubewharf/podseidon/util/util" 29 | workerobserver "github.com/kubewharf/podseidon/util/worker/observer" 30 | 31 | "github.com/kubewharf/podseidon/aggregator/aggregator" 32 | aggregatorobserver "github.com/kubewharf/podseidon/aggregator/observer" 33 | "github.com/kubewharf/podseidon/aggregator/synctime" 34 | "github.com/kubewharf/podseidon/aggregator/updatetrigger" 35 | "github.com/kubewharf/podseidon/generator/generator" 36 | "github.com/kubewharf/podseidon/generator/monitor" 37 | generatorobserver "github.com/kubewharf/podseidon/generator/observer" 38 | "github.com/kubewharf/podseidon/generator/resource" 39 | "github.com/kubewharf/podseidon/generator/resource/deployment" 40 | "github.com/kubewharf/podseidon/webhook/handler" 41 | webhookobserver "github.com/kubewharf/podseidon/webhook/observer" 42 | webhookserver "github.com/kubewharf/podseidon/webhook/server" 43 | ) 44 | 45 | func main() { 46 | cmd.Run( 47 | component.RequireDep(pprof.New(util.Empty{})), 48 | component.RequireDep(metrics.NewHttp(metrics.HttpArgs{})), 49 | workerobserver.Provide, 50 | kubeobserver.ProvideElector, 51 | pprutilobserver.ProvideInformer, 52 | healthzobserver.Provide, 53 | generatorobserver.Provide, 54 | aggregatorobserver.Provide, 55 | webhookobserver.Provide, 56 | component.RequireDep(aggregator.NewController(aggregator.ControllerArgs{ 57 | Clock: clock.RealClock{}, 58 | })), 59 | synctime.DefaultImpls, 60 | component.RequireDep(updatetrigger.New(updatetrigger.Args{})), 61 | component.RequireDep(generator.NewController( 62 | generator.ControllerArgs{ 63 | Types: []component.Declared[resource.TypeProvider]{ 64 | deployment.New(util.Empty{}), 65 | }, 66 | }, 67 | )), 68 | component.RequireDep(monitor.New(monitor.Args{})), 69 | component.RequireDep(webhookserver.New(webhookserver.Args{})), 70 | pprutil.RequireSingleSourceProvider(pprutil.SingleSourceProviderArgs{ClusterName: "core"}, true), 71 | handler.DefaultRequiresPodNameImpls, 72 | ) 73 | } 74 | -------------------------------------------------------------------------------- /apis/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package podseidon 16 | 17 | const ( 18 | SourceObjectNameLabel = "podseidon.kubewharf.io/source-name" 19 | SourceObjectGroupLabel = "podseidon.kubewharf.io/source-group" 20 | SourceObjectKindLabel = "podseidon.kubewharf.io/source-kind" 21 | SourceObjectResourceLabel = "podseidon.kubewharf.io/source-resource" 22 | ) 23 | 24 | // The finalizer applied on both the workload and the PodProtector object. 25 | // 26 | // The finalizer on a workload ensures graceful deletion of the PodProtector object 27 | // such that PodProtector is only safely deleted when 28 | // generator witnesses an explicit non-zero deletionTimestamp on the workload object. 29 | // Removal of this finalizer from the workload may result in a dangling PodProtector object. 30 | // 31 | // The finalizer on a PodProtector object prevents accidentally voiding protection 32 | // due to GC controller or other manual operations deleting the PodProtector 33 | // when the workload object is not explicitly deleted. 34 | // Removal of this finalizer from a PodProtector object indicates an explicit intention 35 | // to declare that a dangling PodProtector shall no longer be maintained. 36 | const GeneratorFinalizer = "podseidon.kubewharf.io/generator" 37 | 38 | // A convenience hack to remove the entries for cells that are no longer online. 39 | // 40 | // The value of this annotation is a comma-separated list of cell names. 41 | // 42 | // This is mostly useful when a cell is deleted and no aggregator instances are running. 43 | // This removal is handled by the generator leader when it reconciles the object. 44 | const PprAnnotationRemoveCellOnce = "podseidon.kubewharf.io/remove-cell-once" 45 | 46 | // Annotates pods that are never rejected. 47 | // 48 | // Podseidon webhook still handles its deletion requests and reports metrics normally, 49 | // but the admission review response is always positive. 50 | // This has the same effect as enabling `--webhook-dry-run=true`, 51 | // but only affects the pod that contains this annotation. 52 | // 53 | // This annotation takes effect as long as it exists under .metadata.annotations of a pod; 54 | // regardless of the annotation value. 55 | // It is RECOMMENDED that the annotation is a PascalCase string 56 | // documenting why this pod should be exempted from protection. 57 | // Annotation values starting with `{` are reserved for future extension; 58 | // handling such pods in the current version of Podseidon webhook results in unspecified behavior. 59 | const PodAnnotationForceDelete = "podseidon.kubewharf.io/force-delete" 60 | 61 | const ( 62 | // Indicates that the request went through a webhook dry-run. 63 | AuditAnnotationDryRun = "dry-run" 64 | // Indicates the PodProtector object that denied the request. 65 | AuditAnnotationRejectByPpr = "reject-by-podprotector" 66 | ) 67 | -------------------------------------------------------------------------------- /apis/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kubewharf/podseidon/apis 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | require k8s.io/apimachinery v0.33.1 8 | 9 | require ( 10 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 11 | github.com/go-logr/logr v1.4.2 // indirect 12 | github.com/gogo/protobuf v1.3.2 // indirect 13 | github.com/json-iterator/go v1.1.12 // indirect 14 | github.com/kr/pretty v0.3.1 // indirect 15 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 16 | github.com/modern-go/reflect2 v1.0.2 // indirect 17 | github.com/rogpeppe/go-internal v1.14.1 // indirect 18 | github.com/spf13/pflag v1.0.6 // indirect 19 | github.com/x448/float16 v0.8.4 // indirect 20 | golang.org/x/net v0.39.0 // indirect 21 | golang.org/x/text v0.24.0 // indirect 22 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 23 | gopkg.in/inf.v0 v0.9.1 // indirect 24 | k8s.io/klog/v2 v2.130.1 // indirect 25 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 26 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 27 | sigs.k8s.io/randfill v1.0.0 // indirect 28 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 29 | sigs.k8s.io/yaml v1.4.0 // indirect 30 | ) 31 | -------------------------------------------------------------------------------- /apis/register.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package podseidon 16 | 17 | const GroupName = "podseidon.kubewharf.io" 18 | -------------------------------------------------------------------------------- /apis/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +k8s:deepcopy-gen=package 16 | // +groupName=podseidon.kubewharf.io 17 | 18 | package v1alpha1 19 | -------------------------------------------------------------------------------- /apis/v1alpha1/register.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package v1alpha1 16 | 17 | import ( 18 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 19 | "k8s.io/apimachinery/pkg/runtime" 20 | "k8s.io/apimachinery/pkg/runtime/schema" 21 | 22 | podseidon "github.com/kubewharf/podseidon/apis" 23 | ) 24 | 25 | var SchemeGroupVersion = schema.GroupVersion{ 26 | Group: podseidon.GroupName, 27 | Version: "v1alpha1", 28 | } 29 | 30 | func Resource(resource string) schema.GroupResource { 31 | return SchemeGroupVersion.WithResource(resource).GroupResource() 32 | } 33 | 34 | var ( 35 | SchemeBuilder = runtime.SchemeBuilder{} 36 | localSchemeBuilder = &SchemeBuilder 37 | AddToScheme = localSchemeBuilder.AddToScheme 38 | ) 39 | 40 | func init() { 41 | localSchemeBuilder.Register(addKnownTypes) 42 | } 43 | 44 | func addKnownTypes(scheme *runtime.Scheme) error { 45 | scheme.AddKnownTypes(SchemeGroupVersion, &PodProtector{}, &PodProtectorList{}) 46 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: podseidon-chart 3 | description: Integrated setup configuration for podseidon 4 | type: application 5 | version: 0.0.0 6 | appVersion: 0.0.0 7 | -------------------------------------------------------------------------------- /chart/STYLE.md: -------------------------------------------------------------------------------- 1 | # Helm chart code style 2 | 3 | Due to the complicated reusability characteristics of the podseidon helm chart, 4 | the code style used in the templates is different from the typical charts, 5 | and adds some rules to avoid common footguns. 6 | 7 | > The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL 8 | > NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and 9 | > "OPTIONAL" in this document are to be interpreted as described in 10 | > RFC 2119. 11 | 12 | 13 | 1. All templates MUST be defined under `templates/_{xxx}.yaml`. 14 | The component files `template/{component}.yaml` SHOULD only contain direct invocation of the boilerplate entrypoint. 15 | 2. Template naming rules: 16 | 1. All template names MUST be suffixed with the data type provided by the template: 17 | - `.json` for templates that provide a single compact JSON object 18 | - `.json-array` for templates that provide a single JSON array 19 | - `.yaml` for templates that provide a single compact YAML object 20 | - `.yaml-array` for templates that provide a single YAML array 21 | - `.obj` for templates that emit one or multiple K8S objects with leading `---` 22 | - `.string` for templates that contain a string seagment that can be interpolated into another string 23 | 3. Use of `{{}}` padding: 24 | 1. Flow control directives (i.e. everything at the same level as `{{end}}`) 25 | MUST always start with `{{- ` and end with `}}`. 26 | 2. Included JSON objects and strings MUST start with `{{` and end with `}}`. 27 | 3. Non-indented YAML interpolation MUST start with `{{` and end with `}}`. 28 | 4. YAML content MUST NOT be interpolated. 29 | The content should be provided in a separate template at root indent level instead, 30 | and the referrer may include it with `include ... | fromYaml | toJson`. 31 | 5. Templates of `.string` type MUST surround any literal content with `{{- }}`. 32 | 5. All variables, when used as a YAML value, MUST be piped through a `toJson`. 33 | 6. All interpolated strings MUST be piped through `printf | toJson`. 34 | 7. All templates SHOULD expect a dictionary parameter with the keys listed below. 35 | Templates that accept other keys MUST be explicitly documented. 36 | - `.main`: main helm context 37 | - `.component`: current component name 38 | - `.generic`: equivalent to `(get .main.Values .component)` 39 | 8. All arguments passed to `merge` MUST be `deepCopy`ed, 40 | unless the value originates from a `fromYaml`/`fromYamlArray` directly 41 | and is not used anywhere else. 42 | -------------------------------------------------------------------------------- /chart/templates/_generator.yaml: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=gotmpl : */}} 2 | 3 | {{- define "podseidon.generator.rbac-rules.yaml-array"}} 4 | {{- if .main.Values.release.core}} 5 | {{- if .generic.leaderElection.enable}} 6 | - apiGroups: ["coordination.k8s.io"] 7 | resources: ["leases"] 8 | verbs: ["get", "list", "watch", "create", "update", "delete"] 9 | {{- end}} 10 | - apiGroups: [""] 11 | resources: ["events"] 12 | verbs: ["get", "watch", "create", "update", "patch"] 13 | - apiGroups: ["podseidon.kubewharf.io"] 14 | resources: ["podprotectors"] 15 | verbs: ["get", "list", "watch", "create", "update", "delete"] 16 | - apiGroups: ["podseidon.kubewharf.io"] 17 | resources: ["podprotectors/status"] 18 | verbs: ["update"] # used for status cell cleanup 19 | - apiGroups: ["apps"] 20 | resources: ["deployments"] 21 | verbs: ["get", "list", "watch", "update"] 22 | {{- end}} 23 | {{- end}} 24 | 25 | {{- define "podseidon.generator.volumes.yaml"}} 26 | {{dict 27 | "config" .main.Values.generator.coreCluster 28 | "volumeName" (printf "%s-generator-core-cluster" .main.Release.Name) 29 | "argPrefix" "core" 30 | | include "podseidon.kubeconfig.volumes.yaml"}} 31 | {{- end}} 32 | 33 | {{- define "podseidon.generator.args.yaml"}} 34 | {{include "podseidon.args.leader-election.yaml" .}} 35 | {{include "podseidon.args.default-admission-history-config.yaml" .main.Values.common.defaultAdmissionHistoryConfig}} 36 | {{include "podseidon.args.logging.yaml" .}} 37 | {{include "podseidon.args.metrics.yaml" .}} 38 | {{include "podseidon.args.debug.yaml" .}} 39 | {{dict 40 | "component" .component 41 | "config" .main.Values.generator.coreCluster 42 | "volumeName" (printf "%s-generator-core-cluster" .main.Release.Name) 43 | "argPrefix" "core" 44 | | deepCopy | merge (deepCopy .) | include "podseidon.kubeconfig.args.yaml"}} 45 | generator-worker-concurrency: {{toJson .main.Values.generator.workerCount}} 46 | 47 | {{- $selector := get .main.Values.generator.protectedSelector "deployments.apps"}} 48 | {{- if empty $selector | not}} 49 | deployment-plugin-protection-selector: {{toJson $selector}} 50 | {{- end}} 51 | 52 | generator-monitor-enable: {{toJson .main.Values.generator.monitor.enable}} 53 | {{- end}} 54 | 55 | {{- define "podseidon.generator.env.yaml"}} 56 | {{- end}} 57 | 58 | {{- define "podseidon.generator.ports.yaml"}} 59 | - pprof: 6060 60 | - healthz: 8081 61 | - metrics: 9090 62 | {{- end}} 63 | -------------------------------------------------------------------------------- /chart/templates/_labels.yaml: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=gotmpl : */}} 2 | 3 | {{- /* 4 | Generic labels for an object associated with a component. 5 | */}} 6 | {{- define "podseidon.boilerplate.labels.json"}} 7 | {{- include "podseidon.boilerplate.labels.yaml" . | fromYaml | toJson}} 8 | {{- end}} 9 | 10 | {{- /* 11 | Generic labels for an object associated with a component. 12 | */}} 13 | {{- define "podseidon.boilerplate.labels.yaml"}} 14 | app.kubernetes.io/name: {{include "podseidon.boilerplate.name-label.string" . | toJson}} 15 | app.kubernetes.io/instance: {{include "podseidon.boilerplate.instance-label.string" . | toJson}} 16 | app.kubernetes.io/component: {{toJson .component}} 17 | podseidon.kubewharf.io/release-id: {{include "podseidon.boilerplate.release-id-label.string" . | toJson}} 18 | {{- end}} 19 | 20 | {{- /* 21 | Label selector for pods associated with a component. 22 | */}} 23 | {{- define "podseidon.boilerplate.label-selector.yaml"}} 24 | app.kubernetes.io/name: {{include "podseidon.boilerplate.name-label.string" . | toJson}} 25 | app.kubernetes.io/instance: {{include "podseidon.boilerplate.instance-label.string" . | toJson}} 26 | app.kubernetes.io/component: {{toJson .component}} 27 | {{- end}} 28 | 29 | {{- define "podseidon.boilerplate.name-label.string"}} 30 | {{- .main.Values.rbacPrefix | default "podseidon"}} 31 | {{- end}} 32 | 33 | {{- define "podseidon.boilerplate.instance-label.string"}} 34 | {{- .main.Release.Name}} 35 | {{- end}} 36 | 37 | {{- define "podseidon.boilerplate.release-id-label.string"}} 38 | {{- printf "%s.%s" .main.Release.Namespace .main.Release.Name}} 39 | {{- end}} 40 | -------------------------------------------------------------------------------- /chart/templates/_rbac.yaml: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=gotmpl : */}} 2 | 3 | {{- /* 4 | Infer the RBAC name prefix for this release. 5 | */}} 6 | {{- define "podseidon.boilerplate.rbac-prefix.string"}} 7 | {{- if .main.Values.rbacPrefix | ne ""}} 8 | {{- .main.Values.rbacPrefix}} 9 | {{- else}} 10 | {{- .main.Release.Name}} 11 | {{- end}} 12 | {{- end}} 13 | 14 | {{- /* 15 | Generate ServiceAccount name for a component. 16 | */}} 17 | {{- define "podseidon.boilerplate.rbac-name.string"}} 18 | {{- printf "%s-%s" (include "podseidon.boilerplate.rbac-prefix.string" .) .component}} 19 | {{- end}} 20 | 21 | {{- /* 22 | Generate RBAC objects. 23 | Additional parameters: 24 | .rules: array of ClusterRole rules 25 | */}} 26 | {{- define "podseidon.boilerplate.rbac.obj"}} 27 | --- 28 | {{include "podseidon.boilerplate.rbac.service-account.yaml" . | fromYaml | toYaml}} 29 | --- 30 | {{include "podseidon.boilerplate.rbac.cluster-role-binding.yaml" . | fromYaml | toYaml}} 31 | --- 32 | {{include "podseidon.boilerplate.rbac.cluster-role.yaml" . | fromYaml | toYaml}} 33 | {{- end}} 34 | 35 | {{- define "podseidon.boilerplate.rbac.service-account.yaml"}} 36 | apiVersion: v1 37 | kind: ServiceAccount 38 | metadata: 39 | name: {{include "podseidon.boilerplate.rbac-name.string" . | toJson}} 40 | labels: {{include "podseidon.boilerplate.labels.json" .}} 41 | {{- end}} 42 | 43 | {{- define "podseidon.boilerplate.rbac.cluster-role-binding.yaml"}} 44 | apiVersion: rbac.authorization.k8s.io/v1 45 | kind: ClusterRoleBinding 46 | metadata: 47 | name: {{include "podseidon.boilerplate.rbac-name.string" . | toJson}} 48 | labels: {{include "podseidon.boilerplate.labels.json" .}} 49 | roleRef: 50 | apiGroup: rbac.authorization.k8s.io 51 | kind: ClusterRole 52 | name: {{include "podseidon.boilerplate.rbac-name.string" . | toJson}} 53 | subjects: 54 | - kind: ServiceAccount 55 | name: {{include "podseidon.boilerplate.rbac-name.string" . | toJson}} 56 | namespace: {{toJson .main.Release.Namespace}} 57 | {{- end}} 58 | 59 | {{- define "podseidon.boilerplate.rbac.cluster-role.yaml"}} 60 | apiVersion: rbac.authorization.k8s.io/v1 61 | kind: ClusterRole 62 | metadata: 63 | name: {{include "podseidon.boilerplate.rbac-name.string" . | toJson}} 64 | labels: {{include "podseidon.boilerplate.labels.json" .}} 65 | {{dict "rules" .rules | toYaml}} 66 | {{- end}} 67 | -------------------------------------------------------------------------------- /chart/templates/aggregator.yaml: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=gotmpl : */}} 2 | 3 | {{- $ctx := dict 4 | "main" . 5 | "component" "aggregator" 6 | "generic" .Values.aggregator 7 | }} 8 | {{- dict 9 | "deployedCluster" "core" 10 | "binary" "/usr/local/bin/podseidon-aggregator" 11 | "args" (include "podseidon.aggregator.args.yaml" $ctx | fromYaml) 12 | "env" (dict) 13 | "volumes" (include "podseidon.aggregator.volumes.yaml" $ctx | fromYaml) 14 | "ports" (include "podseidon.aggregator.ports.yaml" $ctx | fromYamlArray) 15 | "rbacRules" (include "podseidon.aggregator.rbac-rules.yaml-array" $ctx | fromYamlArray) 16 | "clusters" (list "core") 17 | | deepCopy | merge (deepCopy $ctx) | include "podseidon.boilerplate.entrypoint.obj"}} 18 | -------------------------------------------------------------------------------- /chart/templates/generator.yaml: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=gotmpl : */}} 2 | 3 | {{- $ctx := dict 4 | "main" . 5 | "component" "generator" 6 | "generic" .Values.generator 7 | }} 8 | {{- dict 9 | "deployedCluster" "core" 10 | "binary" "/usr/local/bin/podseidon-generator" 11 | "args" (include "podseidon.generator.args.yaml" $ctx | fromYaml) 12 | "env" (dict) 13 | "volumes" (include "podseidon.generator.volumes.yaml" $ctx | fromYaml) 14 | "ports" (include "podseidon.generator.ports.yaml" $ctx | fromYamlArray) 15 | "rbacRules" (include "podseidon.generator.rbac-rules.yaml-array" $ctx | fromYamlArray) 16 | "clusters" (list "core") 17 | | deepCopy | merge (deepCopy $ctx) | include "podseidon.boilerplate.entrypoint.obj"}} 18 | -------------------------------------------------------------------------------- /chart/templates/webhook.yaml: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=gotmpl : */}} 2 | 3 | {{- $ctx := dict 4 | "main" . 5 | "component" "webhook" 6 | "generic" .Values.webhook 7 | }} 8 | {{- dict 9 | "deployedCluster" "core" 10 | "binary" "/usr/local/bin/podseidon-webhook" 11 | "args" (include "podseidon.webhook.args.yaml" $ctx | fromYaml) 12 | "env" (dict) 13 | "volumes" (include "podseidon.webhook.volumes.yaml" $ctx | fromYaml) 14 | "ports" (include "podseidon.webhook.ports.yaml" $ctx | fromYamlArray) 15 | "rbacRules" (include "podseidon.webhook.rbac-rules.yaml-array" $ctx | fromYamlArray) 16 | "clusters" (list "core") 17 | | deepCopy | merge (deepCopy $ctx) | include "podseidon.boilerplate.entrypoint.obj"}} 18 | 19 | {{- if .Values.release.core}} 20 | --- 21 | {{- include "podseidon.webhook.svc.yaml" $ctx | fromYaml | toYaml}} 22 | 23 | {{- if .Values.webhook.tls.custom}} 24 | --- 25 | {{- include "podseidon.webhook.tls-secret.yaml" $ctx | fromYaml | toYaml}} 26 | {{- end}} 27 | {{- end}} 28 | 29 | {{- if .Values.release.worker}} 30 | --- 31 | {{- include "podseidon.webhook.vwc.yaml" $ctx | fromYaml | toYaml}} 32 | {{- end}} 33 | -------------------------------------------------------------------------------- /client/clientset/versioned/fake/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | // This package has the automatically generated fake clientset. 18 | package fake 19 | -------------------------------------------------------------------------------- /client/clientset/versioned/fake/register.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | package fake 18 | 19 | import ( 20 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | runtime "k8s.io/apimachinery/pkg/runtime" 22 | schema "k8s.io/apimachinery/pkg/runtime/schema" 23 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 24 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 25 | 26 | podseidonv1alpha1 "github.com/kubewharf/podseidon/apis/v1alpha1" 27 | ) 28 | 29 | var ( 30 | scheme = runtime.NewScheme() 31 | codecs = serializer.NewCodecFactory(scheme) 32 | ) 33 | 34 | var localSchemeBuilder = runtime.SchemeBuilder{ 35 | podseidonv1alpha1.AddToScheme, 36 | } 37 | 38 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 39 | // of clientsets, like in: 40 | // 41 | // import ( 42 | // "k8s.io/client-go/kubernetes" 43 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 44 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 45 | // ) 46 | // 47 | // kclientset, _ := kubernetes.NewForConfig(c) 48 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 49 | // 50 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 51 | // correctly. 52 | var AddToScheme = localSchemeBuilder.AddToScheme 53 | 54 | func init() { 55 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) 56 | utilruntime.Must(AddToScheme(scheme)) 57 | } 58 | -------------------------------------------------------------------------------- /client/clientset/versioned/scheme/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | // This package contains the scheme of the automatically generated clientset. 18 | package scheme 19 | -------------------------------------------------------------------------------- /client/clientset/versioned/scheme/register.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | package scheme 18 | 19 | import ( 20 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | runtime "k8s.io/apimachinery/pkg/runtime" 22 | schema "k8s.io/apimachinery/pkg/runtime/schema" 23 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 24 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 25 | 26 | podseidonv1alpha1 "github.com/kubewharf/podseidon/apis/v1alpha1" 27 | ) 28 | 29 | var ( 30 | Scheme = runtime.NewScheme() 31 | Codecs = serializer.NewCodecFactory(Scheme) 32 | ParameterCodec = runtime.NewParameterCodec(Scheme) 33 | localSchemeBuilder = runtime.SchemeBuilder{ 34 | podseidonv1alpha1.AddToScheme, 35 | } 36 | ) 37 | 38 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 39 | // of clientsets, like in: 40 | // 41 | // import ( 42 | // "k8s.io/client-go/kubernetes" 43 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 44 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 45 | // ) 46 | // 47 | // kclientset, _ := kubernetes.NewForConfig(c) 48 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 49 | // 50 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 51 | // correctly. 52 | var AddToScheme = localSchemeBuilder.AddToScheme 53 | 54 | func init() { 55 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) 56 | utilruntime.Must(AddToScheme(Scheme)) 57 | } 58 | -------------------------------------------------------------------------------- /client/clientset/versioned/typed/apis/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | // This package has the automatically generated typed clients. 18 | package v1alpha1 19 | -------------------------------------------------------------------------------- /client/clientset/versioned/typed/apis/v1alpha1/fake/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | // Package fake has the automatically generated clients. 18 | package fake 19 | -------------------------------------------------------------------------------- /client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | package fake 18 | 19 | import ( 20 | rest "k8s.io/client-go/rest" 21 | testing "k8s.io/client-go/testing" 22 | 23 | v1alpha1 "github.com/kubewharf/podseidon/client/clientset/versioned/typed/apis/v1alpha1" 24 | ) 25 | 26 | type FakePodseidonV1alpha1 struct { 27 | *testing.Fake 28 | } 29 | 30 | func (c *FakePodseidonV1alpha1) PodProtectors(namespace string) v1alpha1.PodProtectorInterface { 31 | return newFakePodProtectors(c, namespace) 32 | } 33 | 34 | // RESTClient returns a RESTClient that is used to communicate 35 | // with API server by this client implementation. 36 | func (c *FakePodseidonV1alpha1) RESTClient() rest.Interface { 37 | var ret *rest.RESTClient 38 | return ret 39 | } 40 | -------------------------------------------------------------------------------- /client/clientset/versioned/typed/apis/v1alpha1/fake/fake_podprotector.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | package fake 18 | 19 | import ( 20 | gentype "k8s.io/client-go/gentype" 21 | 22 | v1alpha1 "github.com/kubewharf/podseidon/apis/v1alpha1" 23 | apisv1alpha1 "github.com/kubewharf/podseidon/client/clientset/versioned/typed/apis/v1alpha1" 24 | ) 25 | 26 | // fakePodProtectors implements PodProtectorInterface 27 | type fakePodProtectors struct { 28 | *gentype.FakeClientWithList[*v1alpha1.PodProtector, *v1alpha1.PodProtectorList] 29 | Fake *FakePodseidonV1alpha1 30 | } 31 | 32 | func newFakePodProtectors(fake *FakePodseidonV1alpha1, namespace string) apisv1alpha1.PodProtectorInterface { 33 | return &fakePodProtectors{ 34 | gentype.NewFakeClientWithList[*v1alpha1.PodProtector, *v1alpha1.PodProtectorList]( 35 | fake.Fake, 36 | namespace, 37 | v1alpha1.SchemeGroupVersion.WithResource("podprotectors"), 38 | v1alpha1.SchemeGroupVersion.WithKind("PodProtector"), 39 | func() *v1alpha1.PodProtector { return &v1alpha1.PodProtector{} }, 40 | func() *v1alpha1.PodProtectorList { return &v1alpha1.PodProtectorList{} }, 41 | func(dst, src *v1alpha1.PodProtectorList) { dst.ListMeta = src.ListMeta }, 42 | func(list *v1alpha1.PodProtectorList) []*v1alpha1.PodProtector { 43 | return gentype.ToPointerSlice(list.Items) 44 | }, 45 | func(list *v1alpha1.PodProtectorList, items []*v1alpha1.PodProtector) { 46 | list.Items = gentype.FromPointerSlice(items) 47 | }, 48 | ), 49 | fake, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /client/clientset/versioned/typed/apis/v1alpha1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | package v1alpha1 18 | 19 | type PodProtectorExpansion interface{} 20 | -------------------------------------------------------------------------------- /client/clientset/versioned/typed/apis/v1alpha1/podprotector.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by client-gen. DO NOT EDIT. 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | context "context" 21 | 22 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | types "k8s.io/apimachinery/pkg/types" 24 | watch "k8s.io/apimachinery/pkg/watch" 25 | gentype "k8s.io/client-go/gentype" 26 | 27 | apisv1alpha1 "github.com/kubewharf/podseidon/apis/v1alpha1" 28 | scheme "github.com/kubewharf/podseidon/client/clientset/versioned/scheme" 29 | ) 30 | 31 | // PodProtectorsGetter has a method to return a PodProtectorInterface. 32 | // A group's client should implement this interface. 33 | type PodProtectorsGetter interface { 34 | PodProtectors(namespace string) PodProtectorInterface 35 | } 36 | 37 | // PodProtectorInterface has methods to work with PodProtector resources. 38 | type PodProtectorInterface interface { 39 | Create(ctx context.Context, podProtector *apisv1alpha1.PodProtector, opts v1.CreateOptions) (*apisv1alpha1.PodProtector, error) 40 | Update(ctx context.Context, podProtector *apisv1alpha1.PodProtector, opts v1.UpdateOptions) (*apisv1alpha1.PodProtector, error) 41 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). 42 | UpdateStatus(ctx context.Context, podProtector *apisv1alpha1.PodProtector, opts v1.UpdateOptions) (*apisv1alpha1.PodProtector, error) 43 | Delete(ctx context.Context, name string, opts v1.DeleteOptions) error 44 | DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error 45 | Get(ctx context.Context, name string, opts v1.GetOptions) (*apisv1alpha1.PodProtector, error) 46 | List(ctx context.Context, opts v1.ListOptions) (*apisv1alpha1.PodProtectorList, error) 47 | Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) 48 | Patch( 49 | ctx context.Context, 50 | name string, 51 | pt types.PatchType, 52 | data []byte, 53 | opts v1.PatchOptions, 54 | subresources ...string, 55 | ) (result *apisv1alpha1.PodProtector, err error) 56 | PodProtectorExpansion 57 | } 58 | 59 | // podProtectors implements PodProtectorInterface 60 | type podProtectors struct { 61 | *gentype.ClientWithList[*apisv1alpha1.PodProtector, *apisv1alpha1.PodProtectorList] 62 | } 63 | 64 | // newPodProtectors returns a PodProtectors 65 | func newPodProtectors(c *PodseidonV1alpha1Client, namespace string) *podProtectors { 66 | return &podProtectors{ 67 | gentype.NewClientWithList[*apisv1alpha1.PodProtector, *apisv1alpha1.PodProtectorList]( 68 | "podprotectors", 69 | c.RESTClient(), 70 | scheme.ParameterCodec, 71 | namespace, 72 | func() *apisv1alpha1.PodProtector { return &apisv1alpha1.PodProtector{} }, 73 | func() *apisv1alpha1.PodProtectorList { return &apisv1alpha1.PodProtectorList{} }, 74 | ), 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /client/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kubewharf/podseidon/client 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | github.com/kubewharf/podseidon/apis v0.0.0 9 | k8s.io/apimachinery v0.33.1 10 | k8s.io/client-go v0.33.1 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 15 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 16 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 17 | github.com/go-logr/logr v1.4.2 // indirect 18 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 19 | github.com/go-openapi/jsonreference v0.21.0 // indirect 20 | github.com/go-openapi/swag v0.23.0 // indirect 21 | github.com/gogo/protobuf v1.3.2 // indirect 22 | github.com/google/gnostic-models v0.6.9 // indirect 23 | github.com/google/go-cmp v0.7.0 // indirect 24 | github.com/google/uuid v1.6.0 // indirect 25 | github.com/josharian/intern v1.0.0 // indirect 26 | github.com/json-iterator/go v1.1.12 // indirect 27 | github.com/mailru/easyjson v0.7.7 // indirect 28 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 29 | github.com/modern-go/reflect2 v1.0.2 // indirect 30 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 31 | github.com/onsi/ginkgo/v2 v2.23.4 // indirect 32 | github.com/onsi/gomega v1.37.0 // indirect 33 | github.com/pkg/errors v0.9.1 // indirect 34 | github.com/x448/float16 v0.8.4 // indirect 35 | golang.org/x/net v0.39.0 // indirect 36 | golang.org/x/oauth2 v0.27.0 // indirect 37 | golang.org/x/sys v0.32.0 // indirect 38 | golang.org/x/term v0.31.0 // indirect 39 | golang.org/x/text v0.24.0 // indirect 40 | golang.org/x/time v0.9.0 // indirect 41 | golang.org/x/tools v0.32.0 // indirect 42 | google.golang.org/protobuf v1.36.5 // indirect 43 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 44 | gopkg.in/inf.v0 v0.9.1 // indirect 45 | gopkg.in/yaml.v3 v3.0.1 // indirect 46 | k8s.io/api v0.33.1 // indirect 47 | k8s.io/klog/v2 v2.130.1 // indirect 48 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 49 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 50 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 51 | sigs.k8s.io/randfill v1.0.0 // indirect 52 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 53 | sigs.k8s.io/yaml v1.4.0 // indirect 54 | ) 55 | 56 | replace github.com/kubewharf/podseidon/apis => ../apis 57 | -------------------------------------------------------------------------------- /client/informers/externalversions/apis/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by informer-gen. DO NOT EDIT. 16 | 17 | package apis 18 | 19 | import ( 20 | v1alpha1 "github.com/kubewharf/podseidon/client/informers/externalversions/apis/v1alpha1" 21 | internalinterfaces "github.com/kubewharf/podseidon/client/informers/externalversions/internalinterfaces" 22 | ) 23 | 24 | // Interface provides access to each of this group's versions. 25 | type Interface interface { 26 | // V1alpha1 provides access to shared informers for resources in V1alpha1. 27 | V1alpha1() v1alpha1.Interface 28 | } 29 | 30 | type group struct { 31 | factory internalinterfaces.SharedInformerFactory 32 | namespace string 33 | tweakListOptions internalinterfaces.TweakListOptionsFunc 34 | } 35 | 36 | // New returns a new Interface. 37 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 38 | return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 39 | } 40 | 41 | // V1alpha1 returns a new v1alpha1.Interface. 42 | func (g *group) V1alpha1() v1alpha1.Interface { 43 | return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) 44 | } 45 | -------------------------------------------------------------------------------- /client/informers/externalversions/apis/v1alpha1/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by informer-gen. DO NOT EDIT. 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | internalinterfaces "github.com/kubewharf/podseidon/client/informers/externalversions/internalinterfaces" 21 | ) 22 | 23 | // Interface provides access to all the informers in this group version. 24 | type Interface interface { 25 | // PodProtectors returns a PodProtectorInformer. 26 | PodProtectors() PodProtectorInformer 27 | } 28 | 29 | type version struct { 30 | factory internalinterfaces.SharedInformerFactory 31 | namespace string 32 | tweakListOptions internalinterfaces.TweakListOptionsFunc 33 | } 34 | 35 | // New returns a new Interface. 36 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 37 | return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 38 | } 39 | 40 | // PodProtectors returns a PodProtectorInformer. 41 | func (v *version) PodProtectors() PodProtectorInformer { 42 | return &podProtectorInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 43 | } 44 | -------------------------------------------------------------------------------- /client/informers/externalversions/generic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by informer-gen. DO NOT EDIT. 16 | 17 | package externalversions 18 | 19 | import ( 20 | fmt "fmt" 21 | 22 | schema "k8s.io/apimachinery/pkg/runtime/schema" 23 | cache "k8s.io/client-go/tools/cache" 24 | 25 | v1alpha1 "github.com/kubewharf/podseidon/apis/v1alpha1" 26 | ) 27 | 28 | // GenericInformer is type of SharedIndexInformer which will locate and delegate to other 29 | // sharedInformers based on type 30 | type GenericInformer interface { 31 | Informer() cache.SharedIndexInformer 32 | Lister() cache.GenericLister 33 | } 34 | 35 | type genericInformer struct { 36 | informer cache.SharedIndexInformer 37 | resource schema.GroupResource 38 | } 39 | 40 | // Informer returns the SharedIndexInformer. 41 | func (f *genericInformer) Informer() cache.SharedIndexInformer { 42 | return f.informer 43 | } 44 | 45 | // Lister returns the GenericLister. 46 | func (f *genericInformer) Lister() cache.GenericLister { 47 | return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) 48 | } 49 | 50 | // ForResource gives generic access to a shared informer of the matching type 51 | // TODO extend this to unknown resources with a client pool 52 | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { 53 | switch resource { 54 | // Group=podseidon.kubewharf.io, Version=v1alpha1 55 | case v1alpha1.SchemeGroupVersion.WithResource("podprotectors"): 56 | return &genericInformer{resource: resource.GroupResource(), informer: f.Podseidon().V1alpha1().PodProtectors().Informer()}, nil 57 | } 58 | 59 | return nil, fmt.Errorf("no informer found for %v", resource) 60 | } 61 | -------------------------------------------------------------------------------- /client/informers/externalversions/internalinterfaces/factory_interfaces.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by informer-gen. DO NOT EDIT. 16 | 17 | package internalinterfaces 18 | 19 | import ( 20 | time "time" 21 | 22 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | runtime "k8s.io/apimachinery/pkg/runtime" 24 | cache "k8s.io/client-go/tools/cache" 25 | 26 | versioned "github.com/kubewharf/podseidon/client/clientset/versioned" 27 | ) 28 | 29 | // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. 30 | type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer 31 | 32 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle 33 | type SharedInformerFactory interface { 34 | Start(stopCh <-chan struct{}) 35 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer 36 | } 37 | 38 | // TweakListOptionsFunc is a function that transforms a v1.ListOptions. 39 | type TweakListOptionsFunc func(*v1.ListOptions) 40 | -------------------------------------------------------------------------------- /client/listers/apis/v1alpha1/expansion_generated.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by lister-gen. DO NOT EDIT. 16 | 17 | package v1alpha1 18 | 19 | // PodProtectorListerExpansion allows custom methods to be added to 20 | // PodProtectorLister. 21 | type PodProtectorListerExpansion interface{} 22 | 23 | // PodProtectorNamespaceListerExpansion allows custom methods to be added to 24 | // PodProtectorNamespaceLister. 25 | type PodProtectorNamespaceListerExpansion interface{} 26 | -------------------------------------------------------------------------------- /client/listers/apis/v1alpha1/podprotector.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Code generated by lister-gen. DO NOT EDIT. 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | labels "k8s.io/apimachinery/pkg/labels" 21 | listers "k8s.io/client-go/listers" 22 | cache "k8s.io/client-go/tools/cache" 23 | 24 | apisv1alpha1 "github.com/kubewharf/podseidon/apis/v1alpha1" 25 | ) 26 | 27 | // PodProtectorLister helps list PodProtectors. 28 | // All objects returned here must be treated as read-only. 29 | type PodProtectorLister interface { 30 | // List lists all PodProtectors in the indexer. 31 | // Objects returned here must be treated as read-only. 32 | List(selector labels.Selector) (ret []*apisv1alpha1.PodProtector, err error) 33 | // PodProtectors returns an object that can list and get PodProtectors. 34 | PodProtectors(namespace string) PodProtectorNamespaceLister 35 | PodProtectorListerExpansion 36 | } 37 | 38 | // podProtectorLister implements the PodProtectorLister interface. 39 | type podProtectorLister struct { 40 | listers.ResourceIndexer[*apisv1alpha1.PodProtector] 41 | } 42 | 43 | // NewPodProtectorLister returns a new PodProtectorLister. 44 | func NewPodProtectorLister(indexer cache.Indexer) PodProtectorLister { 45 | return &podProtectorLister{listers.New[*apisv1alpha1.PodProtector](indexer, apisv1alpha1.Resource("podprotector"))} 46 | } 47 | 48 | // PodProtectors returns an object that can list and get PodProtectors. 49 | func (s *podProtectorLister) PodProtectors(namespace string) PodProtectorNamespaceLister { 50 | return podProtectorNamespaceLister{listers.NewNamespaced[*apisv1alpha1.PodProtector](s.ResourceIndexer, namespace)} 51 | } 52 | 53 | // PodProtectorNamespaceLister helps list and get PodProtectors. 54 | // All objects returned here must be treated as read-only. 55 | type PodProtectorNamespaceLister interface { 56 | // List lists all PodProtectors in the indexer for a given namespace. 57 | // Objects returned here must be treated as read-only. 58 | List(selector labels.Selector) (ret []*apisv1alpha1.PodProtector, err error) 59 | // Get retrieves the PodProtector from the indexer for a given namespace and name. 60 | // Objects returned here must be treated as read-only. 61 | Get(name string) (*apisv1alpha1.PodProtector, error) 62 | PodProtectorNamespaceListerExpansion 63 | } 64 | 65 | // podProtectorNamespaceLister implements the PodProtectorNamespaceLister 66 | // interface. 67 | type podProtectorNamespaceLister struct { 68 | listers.ResourceIndexer[*apisv1alpha1.PodProtector] 69 | } 70 | -------------------------------------------------------------------------------- /docs/development.md: -------------------------------------------------------------------------------- 1 | # Development guide 2 | 3 | Most development-related tasks in the Podseidon project 4 | are executed using [earthly](https://earthly.dev). 5 | 6 | For compatibility with private Git replaces under some environments, 7 | all Go module downloading commands use `RUN --ssh`, which requires an ssh-agent socket. 8 | However, no special SSH access is required for the default setup. 9 | Simply run all `earthly` commands with an `ssh-agent` 10 | to workaround the "no SSH key forwarded from the client" error. 11 | 12 | The Earthly targets for Podseidon perform all operations 13 | (except `go mod tidy`) inside Docker containers. 14 | Thus, system settings are not respected by default. 15 | For users behind a firewall, override the corresponding build arguments, 16 | e.g. `earthly +target --golang_image='your-container-registry.com/golang:1.23-alpine3.20`. 17 | grep '^ *ARG' [Earthfile](../Earthfile) for a list of available arguments. 18 | 19 | ## Building 20 | 21 | Simply run `ssh-agent earthly +build`. 22 | This builds the binaries and docker images for each of the three components, 23 | plus a binary for the `allinone` package. 24 | Artifact paths are indicated in the `earthly` output. 25 | 26 | ## Pre-commit checks 27 | 28 | The following steps are advised before committing: 29 | 30 | ```shell 31 | git add -A # stage all changes to before formatting 32 | earthly +fmt # reformat code and reorganize imports 33 | earthly +lint # perform static analysis 34 | earthly +test # run fast unit tests 35 | earthly -P +e2e # run slow e2e tests 36 | ``` 37 | 38 | Note that `+e2e` creates a docker-in-docker container 39 | and pulls/loads 13 docker images into the guest dockerd, 40 | running 26 docker containers inside. 41 | This may be resource-consuming for your system. 42 | 43 | To debug e2e tests interactively, 44 | run the following command instead: 45 | 46 | ```shell 47 | earthly -iP +e2e --FAIL_FAST_FOR_INTERACTIVE=yes 48 | ``` 49 | 50 | If e2e test cases fail, this will open an interactive shell inside the e2e setup. 51 | Omitting `FAIL_FAST_FOR_INTERACTIVE=yes` would result in the interactive shell 52 | spawning after the DinD daemon is stopped. 53 | 54 | [Kelemetry](https://github.com/kubewharf/kelemetry) traces are exported to /var/trace/outputs. 55 | 56 | ## Code style 57 | 58 | We use [editorconfig](https://editorconfig.org) to standardize project code style. 59 | Installing the corresponding plugin for your editors is recommended. 60 | 61 | ### Go code 62 | 63 | Go code style is regulated by `gofumpt`, `gci` and some other `golangci-lint` linters. 64 | `earthly +fmt && earthly +lint` output provides suggestions to meet the code style requirements. 65 | 66 | ### Generated code 67 | 68 | All generated code must be marked as `linguist-generated=true` in [.gitattributes](../.gitattributes). 69 | 70 | ### Helm chart 71 | 72 | Podseidon has a complex chart setup to maximize reusability in different environments. 73 | Thus, there are some non-standard rules as specified in [STYLE.md](../chart/STYLE.md). 74 | 75 | ## Contact 76 | 77 | If you have any questions, don't hesitate to reach out on 78 | [GitHub discussions](https://github.com/kubewharf/podseidon/discussions). 79 | -------------------------------------------------------------------------------- /generator/constants/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package constants 16 | 17 | import "github.com/kubewharf/podseidon/util/kube" 18 | 19 | const CoreClusterName kube.ClusterName = "core" 20 | 21 | const LeaderPhase kube.InformerPhase = "main" 22 | 23 | const GeneratorElectorName kube.ElectorName = "generator" 24 | 25 | var GeneratorElectorArgs = kube.ElectorArgs{ 26 | ClusterName: CoreClusterName, 27 | ElectorName: GeneratorElectorName, 28 | } 29 | -------------------------------------------------------------------------------- /generator/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kubewharf/podseidon/generator 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | github.com/kubewharf/podseidon/apis v0.0.0 9 | github.com/kubewharf/podseidon/client v0.0.0 10 | github.com/kubewharf/podseidon/util v0.0.0 11 | github.com/stretchr/testify v1.10.0 12 | k8s.io/api v0.33.1 13 | k8s.io/apimachinery v0.33.1 14 | k8s.io/client-go v0.33.1 15 | k8s.io/klog/v2 v2.130.1 16 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 17 | ) 18 | 19 | require ( 20 | github.com/beorn7/perks v1.0.1 // indirect 21 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 22 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 23 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 24 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 25 | github.com/go-logr/logr v1.4.2 // indirect 26 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 27 | github.com/go-openapi/jsonreference v0.21.0 // indirect 28 | github.com/go-openapi/swag v0.23.0 // indirect 29 | github.com/gogo/protobuf v1.3.2 // indirect 30 | github.com/google/gnostic-models v0.6.9 // indirect 31 | github.com/google/go-cmp v0.7.0 // indirect 32 | github.com/google/uuid v1.6.0 // indirect 33 | github.com/josharian/intern v1.0.0 // indirect 34 | github.com/json-iterator/go v1.1.12 // indirect 35 | github.com/mailru/easyjson v0.7.7 // indirect 36 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 37 | github.com/modern-go/reflect2 v1.0.2 // indirect 38 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 39 | github.com/pkg/errors v0.9.1 // indirect 40 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 41 | github.com/prometheus/client_golang v1.22.0 // indirect 42 | github.com/prometheus/client_model v0.6.1 // indirect 43 | github.com/prometheus/common v0.62.0 // indirect 44 | github.com/prometheus/procfs v0.15.1 // indirect 45 | github.com/spf13/pflag v1.0.6 // indirect 46 | github.com/x448/float16 v0.8.4 // indirect 47 | golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect 48 | golang.org/x/net v0.39.0 // indirect 49 | golang.org/x/oauth2 v0.27.0 // indirect 50 | golang.org/x/sys v0.32.0 // indirect 51 | golang.org/x/term v0.31.0 // indirect 52 | golang.org/x/text v0.24.0 // indirect 53 | golang.org/x/time v0.9.0 // indirect 54 | google.golang.org/protobuf v1.36.5 // indirect 55 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 56 | gopkg.in/inf.v0 v0.9.1 // indirect 57 | gopkg.in/yaml.v3 v3.0.1 // indirect 58 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 59 | sigs.k8s.io/controller-runtime v0.21.0 // indirect 60 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 61 | sigs.k8s.io/randfill v1.0.0 // indirect 62 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 63 | sigs.k8s.io/yaml v1.4.0 // indirect 64 | ) 65 | 66 | replace ( 67 | github.com/kubewharf/podseidon/apis => ../apis 68 | github.com/kubewharf/podseidon/client => ../client 69 | github.com/kubewharf/podseidon/util => ../util 70 | ) 71 | -------------------------------------------------------------------------------- /generator/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "github.com/kubewharf/podseidon/util/cmd" 19 | "github.com/kubewharf/podseidon/util/component" 20 | healthzobserver "github.com/kubewharf/podseidon/util/healthz/observer" 21 | kubeobserver "github.com/kubewharf/podseidon/util/kube/observer" 22 | "github.com/kubewharf/podseidon/util/o11y/metrics" 23 | pprutilobserver "github.com/kubewharf/podseidon/util/podprotector/observer" 24 | "github.com/kubewharf/podseidon/util/pprof" 25 | "github.com/kubewharf/podseidon/util/util" 26 | workerobserver "github.com/kubewharf/podseidon/util/worker/observer" 27 | 28 | "github.com/kubewharf/podseidon/generator/generator" 29 | "github.com/kubewharf/podseidon/generator/monitor" 30 | generatorobserver "github.com/kubewharf/podseidon/generator/observer" 31 | "github.com/kubewharf/podseidon/generator/resource" 32 | "github.com/kubewharf/podseidon/generator/resource/deployment" 33 | ) 34 | 35 | func main() { 36 | cmd.Run( 37 | component.RequireDep(pprof.New(util.Empty{})), 38 | component.RequireDep(metrics.NewHttp(metrics.HttpArgs{})), 39 | healthzobserver.Provide, 40 | workerobserver.Provide, 41 | kubeobserver.ProvideElector, 42 | pprutilobserver.ProvideInformer, 43 | generatorobserver.Provide, 44 | component.RequireDep(generator.NewController( 45 | generator.ControllerArgs{ 46 | Types: []component.Declared[resource.TypeProvider]{ 47 | deployment.New(util.Empty{}), 48 | }, 49 | }, 50 | )), 51 | component.RequireDep(monitor.New(monitor.Args{})), 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /go.work: -------------------------------------------------------------------------------- 1 | go 1.24.0 2 | 3 | toolchain go1.24.2 4 | 5 | use ( 6 | ./aggregator 7 | ./allinone 8 | ./apis 9 | ./client 10 | ./generator 11 | ./tests 12 | ./util 13 | ./webhook 14 | ) 15 | -------------------------------------------------------------------------------- /tests/.golangci.jq: -------------------------------------------------------------------------------- 1 | .linters.exclusions.rules |= [(. // [])[], { 2 | "linters": [ 3 | "paralleltest", 4 | "mnd", 5 | "wrapcheck" 6 | ], 7 | "path": ".*" 8 | }] 9 | -------------------------------------------------------------------------------- /tests/assets/audit-kubeconfig-core.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | server: http://kelemetry-core:8080/audit/core 5 | name: audit 6 | contexts: 7 | - context: 8 | cluster: audit 9 | name: main 10 | current-context: main 11 | kind: Config 12 | preferences: {} 13 | -------------------------------------------------------------------------------- /tests/assets/audit-kubeconfig-worker-1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | server: http://kelemetry-core:8080/audit/worker-1 5 | name: audit 6 | contexts: 7 | - context: 8 | cluster: audit 9 | name: main 10 | current-context: main 11 | kind: Config 12 | preferences: {} 13 | -------------------------------------------------------------------------------- /tests/assets/audit-kubeconfig-worker-2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | server: http://kelemetry-core:8080/audit/worker-2 5 | name: audit 6 | contexts: 7 | - context: 8 | cluster: audit 9 | name: main 10 | current-context: main 11 | kind: Config 12 | preferences: {} 13 | -------------------------------------------------------------------------------- /tests/assets/audit-policy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: audit.k8s.io/v1 2 | kind: Policy 3 | rules: 4 | - level: RequestResponse 5 | -------------------------------------------------------------------------------- /tests/assets/cfssl-ca-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "podseidon-webhook", 3 | "key": {"algo": "rsa", "size": 4096}, 4 | "names": [{"C": "C", "ST": "ST", "L": "L", "O": "O", "OU": "OU"}] 5 | } 6 | -------------------------------------------------------------------------------- /tests/assets/cfssl-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "default": { 4 | "expiry": "87600h" 5 | }, 6 | "profiles": { 7 | "webhook": { 8 | "usages": [ 9 | "signing", 10 | "key encipherment", 11 | "server auth", 12 | "client auth" 13 | ], 14 | "expiry": "87600h" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/assets/cfssl-webhook-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "podseidon-webhook", 3 | "hosts": ["podseidon-webhook"], 4 | "key": {"algo": "rsa", "size": 4096}, 5 | "names": [{"C": "C", "ST": "ST", "L": "L", "O": "O", "OU": "OU"}] 6 | } 7 | -------------------------------------------------------------------------------- /tests/assets/e2e-node.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Node 4 | metadata: 5 | labels: 6 | beta.kubernetes.io/arch: amd64 7 | beta.kubernetes.io/os: linux 8 | kubernetes.io/arch: amd64 9 | kubernetes.io/hostname: e2e-node-1 10 | kubernetes.io/os: linux 11 | kubernetes.io/role: agent 12 | node-role.kubernetes.io/agent: "" 13 | type: e2e 14 | nodeLevel: default 15 | name: e2e-node-1 16 | status: 17 | allocatable: 18 | cpu: 32 19 | memory: 256Gi 20 | pods: 110 21 | capacity: 22 | cpu: 32 23 | memory: 256Gi 24 | pods: 110 25 | nodeInfo: 26 | architecture: amd64 27 | bootID: "" 28 | containerRuntimeVersion: "" 29 | kernelVersion: "" 30 | kubeProxyVersion: fake 31 | kubeletVersion: fake 32 | machineID: "" 33 | operatingSystem: linux 34 | osImage: "" 35 | systemUUID: "" 36 | phase: Running 37 | -------------------------------------------------------------------------------- /tests/assets/kwok-cluster-core.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: config.kwok.x-k8s.io/v1alpha1 2 | kind: KwokConfiguration 3 | options: 4 | --- 5 | apiVersion: config.kwok.x-k8s.io/v1alpha1 6 | kind: KwokctlConfiguration 7 | componentsPatches: 8 | - name: kube-apiserver 9 | extraArgs: 10 | - key: audit-webhook-config-file 11 | value: /etc/test-assets/audit-kubeconfig-core.yaml 12 | - key: audit-webhook-mode 13 | value: batch 14 | - key: audit-webhook-batch-max-wait 15 | value: 2s 16 | - key: audit-policy-file 17 | value: /etc/test-assets/audit-policy.yaml 18 | - key: audit-log-path 19 | value: /var/log/k8s-audit/core 20 | extraVolumes: 21 | - name: audit-config 22 | readOnly: true 23 | hostPath: /etc/test-assets 24 | mountPath: /etc/test-assets 25 | type: Directory 26 | - name: audit-log 27 | readOnly: false 28 | hostPath: /var/log/k8s-audit 29 | mountPath: /var/log/k8s-audit 30 | type: File 31 | - name: kube-controller-manager 32 | extraArgs: 33 | - key: controllers 34 | value: "-deployment-controller" 35 | -------------------------------------------------------------------------------- /tests/assets/kwok-node.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Node 4 | metadata: 5 | annotations: 6 | node.alpha.kubernetes.io/ttl: "0" 7 | kwok.x-k8s.io/node: fake 8 | labels: 9 | beta.kubernetes.io/arch: amd64 10 | beta.kubernetes.io/os: linux 11 | kubernetes.io/arch: amd64 12 | kubernetes.io/hostname: kwok-node-1 13 | kubernetes.io/os: linux 14 | kubernetes.io/role: agent 15 | node-role.kubernetes.io/agent: "" 16 | type: kwok 17 | nodeLevel: default 18 | name: kwok-node-1 19 | status: 20 | allocatable: 21 | cpu: 32 22 | memory: 256Gi 23 | pods: 110 24 | capacity: 25 | cpu: 32 26 | memory: 256Gi 27 | pods: 110 28 | nodeInfo: 29 | architecture: amd64 30 | bootID: "" 31 | containerRuntimeVersion: "" 32 | kernelVersion: "" 33 | kubeProxyVersion: fake 34 | kubeletVersion: fake 35 | machineID: "" 36 | operatingSystem: linux 37 | osImage: "" 38 | systemUUID: "" 39 | phase: Running 40 | -------------------------------------------------------------------------------- /tests/assets/values.json: -------------------------------------------------------------------------------- 1 | { 2 | "webhook": { 3 | "tls": { 4 | "cert": "", 5 | "key": "" 6 | } 7 | }, 8 | "common": { 9 | "defaultAdmissionHistoryConfig": { 10 | "maxConcurrentLag": 0, 11 | "compactThreshold": 100, 12 | "aggregationRate": "1s" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kubewharf/podseidon/tests 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | github.com/kubewharf/podseidon/apis v0.0.0 9 | github.com/kubewharf/podseidon/client v0.0.0 10 | github.com/kubewharf/podseidon/util v0.0.0-00010101000000-000000000000 11 | github.com/onsi/ginkgo/v2 v2.23.4 12 | github.com/onsi/gomega v1.37.0 13 | gopkg.in/evanphx/json-patch.v4 v4.12.0 14 | k8s.io/api v0.33.1 15 | k8s.io/apimachinery v0.33.1 16 | k8s.io/client-go v0.33.1 17 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 18 | ) 19 | 20 | require ( 21 | github.com/beorn7/perks v1.0.1 // indirect 22 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 23 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 24 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 25 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 26 | github.com/go-logr/logr v1.4.2 // indirect 27 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 28 | github.com/go-openapi/jsonreference v0.21.0 // indirect 29 | github.com/go-openapi/swag v0.23.0 // indirect 30 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 31 | github.com/gogo/protobuf v1.3.2 // indirect 32 | github.com/google/gnostic-models v0.6.9 // indirect 33 | github.com/google/go-cmp v0.7.0 // indirect 34 | github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect 35 | github.com/google/uuid v1.6.0 // indirect 36 | github.com/josharian/intern v1.0.0 // indirect 37 | github.com/json-iterator/go v1.1.12 // indirect 38 | github.com/mailru/easyjson v0.7.7 // indirect 39 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 40 | github.com/modern-go/reflect2 v1.0.2 // indirect 41 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 42 | github.com/pkg/errors v0.9.1 // indirect 43 | github.com/prometheus/client_golang v1.22.0 // indirect 44 | github.com/prometheus/client_model v0.6.1 // indirect 45 | github.com/prometheus/common v0.62.0 // indirect 46 | github.com/prometheus/procfs v0.15.1 // indirect 47 | github.com/spf13/pflag v1.0.6 // indirect 48 | github.com/x448/float16 v0.8.4 // indirect 49 | go.uber.org/automaxprocs v1.6.0 // indirect 50 | golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect 51 | golang.org/x/net v0.39.0 // indirect 52 | golang.org/x/oauth2 v0.27.0 // indirect 53 | golang.org/x/sys v0.32.0 // indirect 54 | golang.org/x/term v0.31.0 // indirect 55 | golang.org/x/text v0.24.0 // indirect 56 | golang.org/x/time v0.9.0 // indirect 57 | golang.org/x/tools v0.32.0 // indirect 58 | google.golang.org/protobuf v1.36.5 // indirect 59 | gopkg.in/inf.v0 v0.9.1 // indirect 60 | gopkg.in/yaml.v3 v3.0.1 // indirect 61 | k8s.io/klog/v2 v2.130.1 // indirect 62 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 63 | sigs.k8s.io/controller-runtime v0.21.0 // indirect 64 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 65 | sigs.k8s.io/randfill v1.0.0 // indirect 66 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 67 | sigs.k8s.io/yaml v1.4.0 // indirect 68 | ) 69 | 70 | replace ( 71 | github.com/kubewharf/podseidon/apis => ../apis 72 | github.com/kubewharf/podseidon/client => ../client 73 | github.com/kubewharf/podseidon/util => ../util 74 | ) 75 | -------------------------------------------------------------------------------- /tests/suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tests_test 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/onsi/ginkgo/v2" 21 | "github.com/onsi/gomega" 22 | gomegaformat "github.com/onsi/gomega/format" 23 | 24 | _ "github.com/kubewharf/podseidon/tests/aggregator" 25 | _ "github.com/kubewharf/podseidon/tests/generator" 26 | _ "github.com/kubewharf/podseidon/tests/webhook" 27 | ) 28 | 29 | func TestSuite(t *testing.T) { 30 | gomegaformat.MaxLength = 0 31 | 32 | gomega.RegisterFailHandler(ginkgo.Fail) 33 | 34 | ginkgo.RunSpecs(t, "E2E tests") 35 | } 36 | -------------------------------------------------------------------------------- /tools/boilerplate.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | -------------------------------------------------------------------------------- /tools/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kubewharf/podseidon/tools 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | github.com/daixiang0/gci v0.13.6 9 | github.com/itchyny/gojq v0.12.17 10 | github.com/segmentio/golines v0.12.2 11 | golang.org/x/tools v0.32.0 12 | k8s.io/code-generator v0.33.0 13 | mvdan.cc/gofumpt v0.8.0 14 | sigs.k8s.io/controller-tools v0.18.0 15 | ) 16 | 17 | require ( 18 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect 19 | github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect 20 | github.com/dave/dst v0.27.3 // indirect 21 | github.com/fatih/structtag v1.2.0 // indirect 22 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 23 | github.com/go-logr/logr v1.4.2 // indirect 24 | github.com/gobuffalo/flect v1.0.3 // indirect 25 | github.com/gogo/protobuf v1.3.2 // indirect 26 | github.com/google/go-cmp v0.7.0 // indirect 27 | github.com/hexops/gotextdiff v1.0.3 // indirect 28 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 29 | github.com/itchyny/timefmt-go v0.1.6 // indirect 30 | github.com/json-iterator/go v1.1.12 // indirect 31 | github.com/mattn/go-colorable v0.1.13 // indirect 32 | github.com/mattn/go-isatty v0.0.20 // indirect 33 | github.com/mattn/go-runewidth v0.0.15 // indirect 34 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect 35 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 36 | github.com/modern-go/reflect2 v1.0.2 // indirect 37 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 38 | github.com/rivo/uniseg v0.4.7 // indirect 39 | github.com/sirupsen/logrus v1.9.3 // indirect 40 | github.com/spf13/cobra v1.9.1 // indirect 41 | github.com/spf13/pflag v1.0.6 // indirect 42 | github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect 43 | github.com/x448/float16 v0.8.4 // indirect 44 | go.uber.org/multierr v1.11.0 // indirect 45 | go.uber.org/zap v1.27.0 // indirect 46 | golang.org/x/crypto v0.37.0 // indirect 47 | golang.org/x/mod v0.24.0 // indirect 48 | golang.org/x/net v0.39.0 // indirect 49 | golang.org/x/sync v0.13.0 // indirect 50 | golang.org/x/sys v0.32.0 // indirect 51 | golang.org/x/term v0.31.0 // indirect 52 | golang.org/x/text v0.24.0 // indirect 53 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect 54 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 55 | gopkg.in/inf.v0 v0.9.1 // indirect 56 | gopkg.in/yaml.v2 v2.4.0 // indirect 57 | gopkg.in/yaml.v3 v3.0.1 // indirect 58 | k8s.io/apiextensions-apiserver v0.33.0 // indirect 59 | k8s.io/apimachinery v0.33.1 // indirect 60 | k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 // indirect 61 | k8s.io/klog/v2 v2.130.1 // indirect 62 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 63 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 64 | sigs.k8s.io/randfill v1.0.0 // indirect 65 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 66 | sigs.k8s.io/yaml v1.4.0 // indirect 67 | ) 68 | -------------------------------------------------------------------------------- /tools/go.work: -------------------------------------------------------------------------------- 1 | // A separate workspace to separate build dependencies from runtime dependencies. 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | use . 8 | -------------------------------------------------------------------------------- /tools/imports.go: -------------------------------------------------------------------------------- 1 | package _tools 2 | 3 | // Stub imports to avoid `go mod tidy` from clearing them up 4 | 5 | import ( 6 | _ "github.com/daixiang0/gci/cmd/gci" 7 | _ "github.com/itchyny/gojq/cli" 8 | _ "github.com/segmentio/golines" 9 | _ "golang.org/x/tools/cmd/goimports" 10 | _ "k8s.io/code-generator/cmd/client-gen/args" 11 | _ "k8s.io/code-generator/cmd/deepcopy-gen/args" 12 | _ "k8s.io/code-generator/cmd/informer-gen/args" 13 | _ "k8s.io/code-generator/cmd/lister-gen/args" 14 | _ "mvdan.cc/gofumpt" 15 | _ "sigs.k8s.io/controller-tools/pkg/crd" 16 | ) 17 | -------------------------------------------------------------------------------- /typos.toml: -------------------------------------------------------------------------------- 1 | [files] 2 | extend-exclude = ["go.mod", "*.sum", "chart/crds/"] 3 | -------------------------------------------------------------------------------- /util/component/mock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package component 16 | 17 | import ( 18 | "context" 19 | "flag" 20 | "fmt" 21 | 22 | "sigs.k8s.io/controller-runtime/pkg/healthz" 23 | 24 | "github.com/kubewharf/podseidon/util/util" 25 | ) 26 | 27 | // A dummy component that does nothing, used for `ApiOnly`. 28 | type emptyComponent struct { 29 | name string 30 | } 31 | 32 | func (comp emptyComponent) Name() string { 33 | return comp.name 34 | } 35 | 36 | func (emptyComponent) dependencies() []*depRequest { 37 | return []*depRequest{} 38 | } 39 | 40 | func (comp emptyComponent) mergeInto(other Component) []*depRequest { 41 | panic(fmt.Sprintf("component %q is already registered as %T", comp.name, other)) 42 | } 43 | 44 | func (emptyComponent) AddFlags(*flag.FlagSet) {} 45 | 46 | func (emptyComponent) Init(context.Context) error { return nil } 47 | 48 | func (emptyComponent) Start(context.Context) error { return nil } 49 | 50 | func (emptyComponent) RegisterHealthChecks( 51 | *healthz.Handler, 52 | func(name string, err error), 53 | ) { 54 | } 55 | 56 | func (emptyComponent) Join(context.Context) error { return nil } 57 | 58 | func (emptyComponent) isRequestedFromMain() bool { return true } 59 | func (emptyComponent) unrequestDeps() []string { return nil } 60 | 61 | // Provides a named component with a different implementation of its API. 62 | // Used for mocking components in integration tests. 63 | func ApiOnly[Api any](name string, api Api) func(*DepRequests) { 64 | return RequireDep(&declaredImpl[util.Empty, util.Empty, Api]{ 65 | comp: emptyComponent{name: name}, 66 | api: func() Api { 67 | return api 68 | }, 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /util/errors/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package errors 16 | 17 | import ( 18 | goerrors "errors" 19 | "fmt" 20 | ) 21 | 22 | // Re-exports fmt.Errorf from standard library. 23 | var Errorf = fmt.Errorf 24 | 25 | // Re-exports errors.Is from standard library. 26 | var Is = goerrors.Is 27 | 28 | // Re-exports errors.Join from standard library. 29 | var Join = goerrors.Join 30 | 31 | // Wraps an error with context information in the message. 32 | func Wrapf(err error, format string, args ...any) error { 33 | return Errorf(format+": %w", append(args, err)...) 34 | } 35 | -------------------------------------------------------------------------------- /util/errors/tag_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package errors_test 16 | 17 | import ( 18 | "fmt" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | apierrors "k8s.io/apimachinery/pkg/api/errors" 23 | 24 | "github.com/kubewharf/podseidon/util/errors" 25 | ) 26 | 27 | func TestGetTags(t *testing.T) { 28 | t.Parallel() 29 | 30 | k8sErr1 := apierrors.NewBadRequest("test1") 31 | k8sErr2 := apierrors.NewResourceExpired("test2") 32 | normalErr3 := errors.Errorf("test3") 33 | tagErr4 := errors.TagErrorf("Test4", "test4") 34 | 35 | wrap1 := fmt.Errorf("wrap %w", k8sErr1) 36 | wrap2 := errors.Wrapf(k8sErr2, "wrap") 37 | 38 | tag1 := errors.TagWrapf("Tag1", wrap1, "tag 1") 39 | tag2 := errors.TagWrapf("Tag2", wrap2, "tag 2") 40 | 41 | joined := errors.Join(tag1, tag2, normalErr3, tagErr4) 42 | tagJoined := errors.TagWrapf("Joined", joined, "joined error") 43 | 44 | assert.Equal( 45 | t, 46 | []string{"Joined", "Tag1", "BadRequest", "Tag2", "Expired", "Test4"}, 47 | errors.GetTags(tagJoined), 48 | ) 49 | assert.Equal(t, "Joined/Tag1/BadRequest/Tag2/Expired/Test4", errors.SerializeTags(tagJoined)) 50 | } 51 | -------------------------------------------------------------------------------- /util/flag/labelselector.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utilflag 16 | 17 | import ( 18 | "flag" 19 | 20 | "k8s.io/apimachinery/pkg/labels" 21 | 22 | "github.com/kubewharf/podseidon/util/errors" 23 | ) 24 | 25 | type labelSelectorValue struct { 26 | selector labels.Selector 27 | } 28 | 29 | func (value *labelSelectorValue) String() string { 30 | return value.selector.String() 31 | } 32 | 33 | func (value *labelSelectorValue) Set(input string) error { 34 | selector, err := labels.Parse(input) 35 | if err != nil { 36 | return errors.TagWrapf("ParseLabelSelector", err, "invalid label seoector") 37 | } 38 | 39 | value.selector = selector 40 | 41 | return nil 42 | } 43 | 44 | // Registers a flag that parses into a LabelSelector using the labels.Parse syntax 45 | // and resolves into labels.Everything if unspecified. 46 | func LabelSelectorEverything(fs *flag.FlagSet, name string, usage string) *labels.Selector { 47 | return LabelSelector(fs, name, labels.Everything(), usage) 48 | } 49 | 50 | // Registers a flag that parses into a LabelSelector using the labels.Parse syntax 51 | // and resolves into labels.Nothing if unspecified. 52 | func LabelSelectorNothing(fs *flag.FlagSet, name string, usage string) *labels.Selector { 53 | return LabelSelector(fs, name, labels.Nothing(), usage) 54 | } 55 | 56 | // Registers a flag that parses into a LabelSelector using the labels.Parse syntax. 57 | func LabelSelector( 58 | fs *flag.FlagSet, 59 | name string, 60 | defaultValue labels.Selector, 61 | usage string, 62 | ) *labels.Selector { 63 | value := &labelSelectorValue{selector: defaultValue} 64 | 65 | fs.Var(value, name, usage) 66 | 67 | return &value.selector 68 | } 69 | -------------------------------------------------------------------------------- /util/flag/map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utilflag 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "strings" 21 | 22 | "github.com/kubewharf/podseidon/util/errors" 23 | "github.com/kubewharf/podseidon/util/util" 24 | ) 25 | 26 | var ErrNoEqual = errors.TagErrorf("ErrNoEqual", "map options expect values in the form k1=v1,k2=v2,k3=v3") 27 | 28 | type Parser[T any] func(string) (T, error) 29 | 30 | var StringParser Parser[string] = func(s string) (string, error) { return s, nil } 31 | 32 | // Registers a flag that accepts a comma-separated list of equal-delimited map entries. 33 | // If the same key is specified multiple times, the last occurrence wins. 34 | func Map[K comparable, V any]( 35 | fs *flag.FlagSet, 36 | name string, 37 | defaultValue map[K]V, 38 | usage string, 39 | keyParser Parser[K], 40 | valueParser Parser[V], 41 | ) *map[K]V { 42 | value := &mapValue[K, V]{value: defaultValue, keyParser: keyParser, valueParser: valueParser} 43 | fs.Var(value, name, usage) 44 | 45 | return &value.value 46 | } 47 | 48 | type mapValue[K comparable, V any] struct { 49 | value map[K]V 50 | keyParser Parser[K] 51 | valueParser Parser[V] 52 | } 53 | 54 | func (*mapValue[K, V]) Type() string { 55 | return fmt.Sprintf("map[%s]%s", util.TypeName[K](), util.TypeName[V]()) 56 | } 57 | 58 | func (mv *mapValue[K, V]) String() string { 59 | var output strings.Builder 60 | 61 | for entryKey, entryValue := range mv.value { 62 | if output.Len() == 0 { 63 | _, _ = output.WriteRune(',') 64 | } 65 | 66 | // strings.Builder is infallible 67 | _, _ = output.WriteString(fmt.Sprint(entryKey)) 68 | _, _ = output.WriteRune('=') 69 | _, _ = output.WriteString(fmt.Sprint(entryValue)) 70 | } 71 | 72 | return output.String() 73 | } 74 | 75 | func (mv *mapValue[K, V]) Set(input string) error { 76 | if input == "" { 77 | mv.value = map[K]V{} 78 | return nil 79 | } 80 | 81 | entries := strings.Split(input, ",") 82 | mv.value = make(map[K]V, len(entries)) 83 | 84 | for _, entry := range entries { 85 | eq := strings.IndexRune(entry, '=') 86 | if eq == -1 { 87 | return ErrNoEqual 88 | } 89 | 90 | entryKey, err := mv.keyParser(entry[:eq]) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | entryValue, err := mv.valueParser(entry[eq+1:]) 96 | if err != nil { 97 | return err 98 | } 99 | 100 | mv.value[entryKey] = entryValue 101 | } 102 | 103 | return nil 104 | } 105 | -------------------------------------------------------------------------------- /util/flag/slice.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utilflag 16 | 17 | import ( 18 | "flag" 19 | "strings" 20 | 21 | "k8s.io/apimachinery/pkg/util/sets" 22 | ) 23 | 24 | // Registers a flag that accepts a comma-separated list of strings. 25 | func StringSlice(fs *flag.FlagSet, name string, defaultValue []string, usage string) *[]string { 26 | value := &stringSliceValue{value: defaultValue} 27 | fs.Var(value, name, usage) 28 | 29 | return &value.value 30 | } 31 | 32 | type stringSliceValue struct { 33 | value []string 34 | } 35 | 36 | func (value *stringSliceValue) String() string { return strings.Join(value.value, ",") } 37 | 38 | func (value *stringSliceValue) Set(input string) error { 39 | if input == "" { 40 | value.value = []string{} 41 | } else { 42 | value.value = strings.Split(input, ",") 43 | } 44 | 45 | return nil 46 | } 47 | 48 | // Registers a flag that accepts a comma-separated set of strings. 49 | func StringSet(fs *flag.FlagSet, name string, defaultValue []string, usage string) sets.Set[string] { 50 | value := &stringSetValue{value: sets.New(defaultValue...)} 51 | fs.Var(value, name, usage) 52 | 53 | return value.value 54 | } 55 | 56 | type stringSetValue struct { 57 | value sets.Set[string] 58 | } 59 | 60 | func (value *stringSetValue) String() string { return strings.Join(sets.List(value.value), ",") } 61 | 62 | func (value *stringSetValue) Set(input string) error { 63 | value.value.Clear() 64 | 65 | if input != "" { 66 | value.value.Insert(strings.Split(input, ",")...) 67 | } 68 | 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /util/flag/subset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utilflag 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | 21 | "github.com/spf13/pflag" 22 | ) 23 | 24 | // Special string used to construct a flag in a subset that expect to be equal to the parent set name. 25 | const EmptyFlagName string = "zzz-empty-flag-name" 26 | 27 | func AddSubset(super *pflag.FlagSet, prefix string, subsetFn func(*flag.FlagSet)) { 28 | compFs := flag.FlagSet{Usage: nil} 29 | subsetFn(&compFs) 30 | 31 | compFs.VisitAll(func(fl *flag.Flag) { 32 | if fl.Name == EmptyFlagName { 33 | fl.Name = prefix 34 | } else { 35 | fl.Name = fmt.Sprintf("%s-%s", prefix, fl.Name) 36 | } 37 | 38 | super.AddGoFlag(fl) 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /util/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kubewharf/podseidon/util 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | github.com/axiomhq/hyperloglog v0.2.5 9 | github.com/kubewharf/podseidon/apis v0.0.0 10 | github.com/kubewharf/podseidon/client v0.0.0 11 | github.com/prometheus/client_golang v1.22.0 12 | github.com/puzpuzpuz/xsync/v3 v3.5.1 13 | github.com/spf13/pflag v1.0.6 14 | github.com/stretchr/testify v1.10.0 15 | golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 16 | k8s.io/api v0.33.1 17 | k8s.io/apimachinery v0.33.1 18 | k8s.io/client-go v0.33.1 19 | k8s.io/klog/v2 v2.130.1 20 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 21 | sigs.k8s.io/controller-runtime v0.21.0 22 | ) 23 | 24 | require ( 25 | github.com/beorn7/perks v1.0.1 // indirect 26 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 27 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 28 | github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc // indirect 29 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 30 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 31 | github.com/go-logr/logr v1.4.2 // indirect 32 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 33 | github.com/go-openapi/jsonreference v0.21.0 // indirect 34 | github.com/go-openapi/swag v0.23.0 // indirect 35 | github.com/gogo/protobuf v1.3.2 // indirect 36 | github.com/google/gnostic-models v0.6.9 // indirect 37 | github.com/google/go-cmp v0.7.0 // indirect 38 | github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect 39 | github.com/google/uuid v1.6.0 // indirect 40 | github.com/josharian/intern v1.0.0 // indirect 41 | github.com/json-iterator/go v1.1.12 // indirect 42 | github.com/kamstrup/intmap v0.5.1 // indirect 43 | github.com/mailru/easyjson v0.7.7 // indirect 44 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 45 | github.com/modern-go/reflect2 v1.0.2 // indirect 46 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 47 | github.com/pkg/errors v0.9.1 // indirect 48 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 49 | github.com/prometheus/client_model v0.6.1 // indirect 50 | github.com/prometheus/common v0.62.0 // indirect 51 | github.com/prometheus/procfs v0.15.1 // indirect 52 | github.com/x448/float16 v0.8.4 // indirect 53 | go.uber.org/automaxprocs v1.6.0 // indirect 54 | golang.org/x/net v0.39.0 // indirect 55 | golang.org/x/oauth2 v0.27.0 // indirect 56 | golang.org/x/sys v0.32.0 // indirect 57 | golang.org/x/term v0.31.0 // indirect 58 | golang.org/x/text v0.24.0 // indirect 59 | golang.org/x/time v0.9.0 // indirect 60 | google.golang.org/protobuf v1.36.5 // indirect 61 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 62 | gopkg.in/inf.v0 v0.9.1 // indirect 63 | gopkg.in/yaml.v3 v3.0.1 // indirect 64 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 65 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 66 | sigs.k8s.io/randfill v1.0.0 // indirect 67 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 68 | sigs.k8s.io/yaml v1.4.0 // indirect 69 | ) 70 | 71 | replace ( 72 | github.com/kubewharf/podseidon/apis => ../apis 73 | github.com/kubewharf/podseidon/client => ../client 74 | ) 75 | -------------------------------------------------------------------------------- /util/haschange/haschange.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package haschange 16 | 17 | import "golang.org/x/exp/constraints" 18 | 19 | // A bitmask of change causes. 20 | // 21 | // Each bit in the bitmask denotes different causes for changing. 22 | type Cause interface { 23 | constraints.Integer 24 | 25 | // Returns a string describing the cause. 26 | // 27 | // This method must only be called when the receiver is a power of 2. 28 | BitToString() string 29 | } 30 | 31 | // Tracks whether an object has been changed during reconciliation. 32 | type Changed[CauseT Cause] struct { 33 | mask CauseT 34 | } 35 | 36 | func New[CauseT Cause]() Changed[CauseT] { 37 | return Changed[CauseT]{mask: 0} 38 | } 39 | 40 | func (changed *Changed[CauseT]) HasChanged() bool { 41 | return changed.mask != 0 42 | } 43 | 44 | // Updates `changed` to include all causes from `addend`. 45 | func (changed *Changed[CauseT]) Add(addend CauseT) { 46 | changed.mask |= addend 47 | } 48 | 49 | // Assigns a new value to dst, updating `changed` if a change is detected using shallow equality `T == T`. 50 | func Assign[CauseT Cause, T comparable](changed *Changed[CauseT], dst *T, src T, cause CauseT) { 51 | if *dst != src { 52 | *dst = src 53 | changed.mask |= cause 54 | } 55 | } 56 | 57 | func (changed *Changed[CauseT]) String() string { 58 | if changed.mask == 0 { 59 | return "nil" 60 | } 61 | 62 | output := "" 63 | maskIter := changed.mask 64 | 65 | for maskIter > 0 { 66 | bit := maskIter & -maskIter 67 | maskIter &= ^bit 68 | 69 | if output != "" { 70 | output += "+" 71 | } 72 | 73 | output += bit.BitToString() 74 | } 75 | 76 | return output 77 | } 78 | -------------------------------------------------------------------------------- /util/haschange/haschange_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package haschange_test 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | 22 | "github.com/kubewharf/podseidon/util/haschange" 23 | ) 24 | 25 | type Cause uint32 26 | 27 | const ( 28 | CauseFoo = Cause(1 << iota) 29 | CauseBar 30 | CauseQux 31 | ) 32 | 33 | func (cause Cause) BitToString() string { 34 | switch cause { 35 | case CauseFoo: 36 | return "Foo" 37 | case CauseBar: 38 | return "Bar" 39 | case CauseQux: 40 | return "Qux" 41 | default: 42 | panic("bad receiver") 43 | } 44 | } 45 | 46 | func TestChangedString(t *testing.T) { 47 | t.Parallel() 48 | 49 | assertCauseString(t, "Foo+Bar+Qux", []Cause{CauseBar, CauseFoo, CauseQux}) 50 | assertCauseString(t, "Foo+Qux", []Cause{CauseFoo, CauseQux}) 51 | assertCauseString(t, "Qux", []Cause{CauseQux}) 52 | assertCauseString(t, "nil", []Cause{}) 53 | } 54 | 55 | func assertCauseString(t *testing.T, expected string, causes []Cause) { 56 | changed := haschange.New[Cause]() 57 | 58 | for _, cause := range causes { 59 | changed.Add(cause) 60 | } 61 | 62 | assert.Equal(t, expected, changed.String()) 63 | } 64 | -------------------------------------------------------------------------------- /util/healthz/observer/logging.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package observer 16 | 17 | import ( 18 | "context" 19 | 20 | "k8s.io/klog/v2" 21 | 22 | "github.com/kubewharf/podseidon/util/component" 23 | "github.com/kubewharf/podseidon/util/o11y" 24 | o11yklog "github.com/kubewharf/podseidon/util/o11y/klog" 25 | "github.com/kubewharf/podseidon/util/util" 26 | ) 27 | 28 | func ProvideLogging() component.Declared[Observer] { 29 | return o11y.Provide( 30 | func(requests *component.DepRequests) util.Empty { 31 | o11yklog.RequestKlogArgs(requests) 32 | return util.Empty{} 33 | }, 34 | func(util.Empty) Observer { 35 | return Observer{ 36 | CheckFailed: func(ctx context.Context, arg CheckFailed) { 37 | logger := klog.FromContext(ctx) 38 | logger.WithCallDepth(1). 39 | Error(arg.Err, "health check failed", "check", arg.CheckName) 40 | }, 41 | } 42 | }, 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /util/healthz/observer/metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package observer 16 | 17 | import ( 18 | "context" 19 | 20 | "github.com/kubewharf/podseidon/util/component" 21 | "github.com/kubewharf/podseidon/util/errors" 22 | "github.com/kubewharf/podseidon/util/o11y" 23 | "github.com/kubewharf/podseidon/util/o11y/metrics" 24 | ) 25 | 26 | func ProvideMetrics() component.Declared[Observer] { 27 | return o11y.Provide( 28 | metrics.MakeObserverDeps, 29 | func(deps metrics.ObserverDeps) Observer { 30 | type healthzFailureTags struct { 31 | Check string 32 | Error string 33 | } 34 | 35 | failureHandle := metrics.Register( 36 | deps.Registry(), 37 | "healthck_failure", 38 | "Health check endpoint returned an error.", 39 | metrics.IntCounter(), 40 | metrics.NewReflectTags[healthzFailureTags](), 41 | ) 42 | 43 | return Observer{ 44 | CheckFailed: func(_ context.Context, arg CheckFailed) { 45 | failureHandle.Emit(1, healthzFailureTags{ 46 | Check: arg.CheckName, 47 | Error: errors.SerializeTags(arg.Err), 48 | }) 49 | }, 50 | } 51 | }, 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /util/healthz/observer/observer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package observer 16 | 17 | import ( 18 | "github.com/kubewharf/podseidon/util/component" 19 | "github.com/kubewharf/podseidon/util/o11y" 20 | ) 21 | 22 | var Provide = component.RequireDeps( 23 | component.RequireDep(ProvideLogging()), 24 | component.RequireDep(ProvideMetrics()), 25 | ) 26 | 27 | type Observer struct { 28 | CheckFailed o11y.ObserveFunc[CheckFailed] 29 | } 30 | 31 | func (Observer) ComponentName() string { return "healthz" } 32 | 33 | func (observer Observer) Join(other Observer) Observer { return o11y.ReflectJoin(observer, other) } 34 | 35 | type CheckFailed struct { 36 | CheckName string 37 | Err error 38 | } 39 | -------------------------------------------------------------------------------- /util/healthz/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utilhealthz 16 | 17 | import ( 18 | "context" 19 | "flag" 20 | "net/http" 21 | 22 | "sigs.k8s.io/controller-runtime/pkg/healthz" 23 | 24 | "github.com/kubewharf/podseidon/util/component" 25 | "github.com/kubewharf/podseidon/util/healthz/observer" 26 | utilhttp "github.com/kubewharf/podseidon/util/http" 27 | "github.com/kubewharf/podseidon/util/o11y" 28 | ) 29 | 30 | const ( 31 | defaultHttpPort uint16 = 8081 32 | defaultHttpsPort uint16 = 8444 33 | ) 34 | 35 | var NewServer = utilhttp.DeclareServer( 36 | func(_ Args) string { return "healthz" }, 37 | defaultHttpPort, defaultHttpsPort, 38 | func(_ Args, _ *flag.FlagSet) Options { 39 | return Options{} 40 | }, 41 | func(_ Args, requests *component.DepRequests) Deps { 42 | return Deps{ 43 | Observer: o11y.Request[observer.Observer](requests), 44 | } 45 | }, 46 | func(args Args, _ Options, _ Deps, mux *http.ServeMux) (*State, error) { 47 | mux.Handle("/readyz", http.StripPrefix("/readyz", args.Handler)) 48 | mux.Handle("/readyz/", http.StripPrefix("/readyz/", args.Handler)) 49 | 50 | return &State{}, nil 51 | }, 52 | component.Lifecycle[Args, Options, Deps, State]{ 53 | Start: nil, 54 | Join: nil, 55 | HealthChecks: nil, 56 | }, 57 | func(_ Args, _ Options, deps Deps, _ *State) *Api { 58 | return &Api{ 59 | observer: deps.Observer.Get(), 60 | } 61 | }, 62 | ) 63 | 64 | type Args struct { 65 | Handler *healthz.Handler 66 | } 67 | 68 | type Options struct{} 69 | 70 | type Deps struct { 71 | Observer component.Dep[observer.Observer] 72 | } 73 | 74 | type State struct{} 75 | 76 | type Api struct { 77 | observer observer.Observer 78 | } 79 | 80 | func (api *Api) OnHealthCheckFailed(ctx context.Context, name string, err error) { 81 | api.observer.CheckFailed(ctx, observer.CheckFailed{CheckName: name, Err: err}) 82 | } 83 | -------------------------------------------------------------------------------- /util/kube/event.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package kube 16 | 17 | import ( 18 | "context" 19 | "flag" 20 | "fmt" 21 | 22 | corev1 "k8s.io/api/core/v1" 23 | "k8s.io/client-go/kubernetes/scheme" 24 | corev1client "k8s.io/client-go/kubernetes/typed/core/v1" 25 | "k8s.io/client-go/tools/record" 26 | "k8s.io/klog/v2" 27 | 28 | "github.com/kubewharf/podseidon/util/component" 29 | ) 30 | 31 | // Request an event recorder for objects in the specified cluster. 32 | // Exposes the EventRecorder directly. 33 | var NewEventRecorder = component.Declare( 34 | func(args EventRecorderArgs) string { return fmt.Sprintf("event-recorder-%s", args.Component) }, 35 | func(EventRecorderArgs, *flag.FlagSet) EventRecorderOptions { return EventRecorderOptions{} }, 36 | func(args EventRecorderArgs, requests *component.DepRequests) EventRecorderDeps { 37 | return EventRecorderDeps{ 38 | config: component.DepPtr( 39 | requests, 40 | NewClient(ClientArgs{ClusterName: args.ClusterName}), 41 | ), 42 | } 43 | }, 44 | func(_ context.Context, args EventRecorderArgs, _ EventRecorderOptions, deps EventRecorderDeps) (*EventRecorderState, error) { 45 | broadcaster := record.NewBroadcaster() 46 | broadcaster.StartStructuredLogging(klog.Level(3)) 47 | broadcaster.StartRecordingToSink( 48 | &corev1client.EventSinkImpl{ 49 | Interface: deps.config.Get(). 50 | NativeClientSet(). 51 | CoreV1(). 52 | Events(deps.config.Get().TargetNamespace()), 53 | }, 54 | ) 55 | 56 | recorder := broadcaster.NewRecorder( 57 | scheme.Scheme, 58 | corev1.EventSource{Component: args.Component, Host: ""}, 59 | ) 60 | 61 | return &EventRecorderState{recorder: recorder}, nil 62 | }, 63 | component.Lifecycle[EventRecorderArgs, EventRecorderOptions, EventRecorderDeps, EventRecorderState]{ 64 | Start: nil, 65 | Join: nil, 66 | HealthChecks: nil, 67 | }, 68 | func(d *component.Data[EventRecorderArgs, EventRecorderOptions, EventRecorderDeps, EventRecorderState]) record.EventRecorder { 69 | return d.State.recorder 70 | }, 71 | ) 72 | 73 | type EventRecorderArgs struct { 74 | ClusterName ClusterName 75 | Component string 76 | } 77 | 78 | type EventRecorderOptions struct{} 79 | 80 | type EventRecorderDeps struct { 81 | config component.Dep[*Client] 82 | } 83 | 84 | type EventRecorderState struct { 85 | recorder record.EventRecorder 86 | } 87 | -------------------------------------------------------------------------------- /util/kube/metrics/component.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package kubemetrics 16 | 17 | import ( 18 | "context" 19 | "flag" 20 | 21 | "github.com/kubewharf/podseidon/util/component" 22 | "github.com/kubewharf/podseidon/util/o11y/metrics" 23 | "github.com/kubewharf/podseidon/util/util" 24 | ) 25 | 26 | var NewComp = component.Declare( 27 | func(_ CompArgs) string { return "kube-metrics" }, 28 | func(_ CompArgs, _ *flag.FlagSet) compOptions { return compOptions{} }, 29 | func(_ CompArgs, requests *component.DepRequests) compDeps { 30 | return compDeps{ 31 | prom: metrics.MakeObserverDeps(requests), 32 | } 33 | }, 34 | func(_ context.Context, _ CompArgs, _ compOptions, deps compDeps) (*compState, error) { 35 | RegisterForPrometheus(deps.prom.Registry().Prometheus) 36 | return &compState{}, nil 37 | }, 38 | component.Lifecycle[CompArgs, compOptions, compDeps, compState]{ 39 | Start: nil, 40 | Join: nil, 41 | HealthChecks: nil, 42 | }, 43 | func(_ *component.Data[CompArgs, compOptions, compDeps, compState]) util.Empty { return util.Empty{} }, 44 | ) 45 | 46 | type CompArgs struct{} 47 | 48 | type compOptions struct{} 49 | 50 | type compDeps struct { 51 | prom metrics.ObserverDeps 52 | } 53 | 54 | type compState struct{} 55 | -------------------------------------------------------------------------------- /util/kube/observer/leaderelection_logging.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package kubeobserver 16 | 17 | import ( 18 | "context" 19 | 20 | "k8s.io/klog/v2" 21 | 22 | "github.com/kubewharf/podseidon/util/component" 23 | "github.com/kubewharf/podseidon/util/kube" 24 | "github.com/kubewharf/podseidon/util/o11y" 25 | o11yklog "github.com/kubewharf/podseidon/util/o11y/klog" 26 | "github.com/kubewharf/podseidon/util/util" 27 | ) 28 | 29 | func ProvideElectorLogs() component.Declared[kube.ElectorObserver] { 30 | return o11y.Provide( 31 | func(requests *component.DepRequests) util.Empty { 32 | o11yklog.RequestKlogArgs(requests) 33 | return util.Empty{} 34 | }, 35 | func(util.Empty) kube.ElectorObserver { 36 | return kube.ElectorObserver{ 37 | Acquired: func(ctx context.Context, arg kube.ElectorObserverArg) (context.Context, context.CancelFunc) { 38 | klog.FromContext(ctx).WithCallDepth(1).Info( 39 | "Acquired leader lease", 40 | "electorName", arg.ElectorName, 41 | "clusterName", arg.ClusterName, 42 | "identity", arg.Identity, 43 | ) 44 | return ctx, util.NoOp 45 | }, 46 | Lost: func(ctx context.Context, arg kube.ElectorObserverArg) { 47 | klog.FromContext(ctx).WithCallDepth(1).Info( 48 | "Lost leader lease", 49 | "electorName", arg.ElectorName, 50 | "clusterName", arg.ClusterName, 51 | "identity", arg.Identity, 52 | ) 53 | }, 54 | Heartbeat: func(context.Context, kube.ElectorObserverArg) {}, 55 | } 56 | }, 57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /util/kube/observer/leaderelection_metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package kubeobserver 16 | 17 | import ( 18 | "context" 19 | "time" 20 | 21 | "github.com/kubewharf/podseidon/util/component" 22 | "github.com/kubewharf/podseidon/util/kube" 23 | "github.com/kubewharf/podseidon/util/o11y" 24 | "github.com/kubewharf/podseidon/util/o11y/metrics" 25 | "github.com/kubewharf/podseidon/util/util" 26 | ) 27 | 28 | var ProvideElector = component.RequireDeps( 29 | component.RequireDep(ProvideElectorLogs()), 30 | component.RequireDep(ProvideElectorMetrics()), 31 | ) 32 | 33 | func ProvideElectorMetrics() component.Declared[kube.ElectorObserver] { 34 | type leaderEventTags struct { 35 | ClusterClass string 36 | ElectorName string 37 | Identity string 38 | Event string 39 | } 40 | 41 | type leaderTags struct { 42 | ClusterClass string 43 | ElectorName string 44 | Identity string 45 | } 46 | 47 | return o11y.Provide( 48 | metrics.MakeObserverDeps, 49 | func(deps metrics.ObserverDeps) kube.ElectorObserver { 50 | eventHandle := metrics.Register( 51 | deps.Registry(), 52 | "leader_event", 53 | "", 54 | metrics.IntCounter(), 55 | metrics.NewReflectTags[leaderEventTags](), 56 | ) 57 | 58 | heartbeatHandle := metrics.Register( 59 | deps.Registry(), 60 | "leader_heartbeat", 61 | "Time since leader lease acquisition, only emitted by the active leader.", 62 | metrics.FloatGauge(), 63 | metrics.NewReflectTags[leaderTags](), 64 | ) 65 | 66 | type acquireTimeKey struct{} 67 | 68 | return kube.ElectorObserver{ 69 | Acquired: func(ctx context.Context, arg kube.ElectorObserverArg) (context.Context, context.CancelFunc) { 70 | eventHandle.Emit(1, leaderEventTags{ 71 | ClusterClass: arg.ClusterName, 72 | ElectorName: arg.ElectorName, 73 | Identity: arg.Identity, 74 | Event: "acquired", 75 | }) 76 | 77 | return context.WithValue(ctx, acquireTimeKey{}, time.Now()), util.NoOp 78 | }, 79 | Lost: func(_ context.Context, arg kube.ElectorObserverArg) { 80 | eventHandle.Emit(1, leaderEventTags{ 81 | ClusterClass: arg.ClusterName, 82 | ElectorName: arg.ElectorName, 83 | Identity: arg.Identity, 84 | Event: "lost", 85 | }) 86 | }, 87 | Heartbeat: func(ctx context.Context, arg kube.ElectorObserverArg) { 88 | acquireTime := ctx.Value(acquireTimeKey{}).(time.Time) 89 | heartbeatHandle.Emit(time.Since(acquireTime).Seconds(), leaderTags{ 90 | ClusterClass: arg.ClusterName, 91 | ElectorName: arg.ElectorName, 92 | Identity: arg.Identity, 93 | }) 94 | }, 95 | } 96 | }, 97 | ) 98 | } 99 | -------------------------------------------------------------------------------- /util/kube/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package kube 16 | 17 | import ( 18 | "k8s.io/apimachinery/pkg/api/meta" 19 | "k8s.io/apimachinery/pkg/types" 20 | "k8s.io/client-go/tools/cache" 21 | ) 22 | 23 | func GenericEventHandler(handler func(nsName types.NamespacedName)) cache.ResourceEventHandler { 24 | anyHandler := func(obj any) { 25 | if del, ok := obj.(cache.DeletedFinalStateUnknown); ok { 26 | obj = del.Obj 27 | } 28 | 29 | if accessor, err := meta.Accessor(obj); err == nil { 30 | handler( 31 | types.NamespacedName{Namespace: accessor.GetNamespace(), Name: accessor.GetName()}, 32 | ) 33 | } 34 | } 35 | 36 | return cache.ResourceEventHandlerFuncs{ 37 | AddFunc: anyHandler, 38 | UpdateFunc: func(_, newObj any) { anyHandler(newObj) }, 39 | DeleteFunc: anyHandler, 40 | } 41 | } 42 | 43 | func GenericEventHandlerWithStaleState[InformerType any]( 44 | handler func(obj InformerType, stillPresent bool), 45 | ) cache.ResourceEventHandler { 46 | anyHandler := func(stillPresent bool) func(any) { 47 | return func(obj any) { 48 | if del, ok := obj.(cache.DeletedFinalStateUnknown); ok { 49 | obj = del.Obj 50 | } 51 | 52 | handler(obj.(InformerType), stillPresent) 53 | } 54 | } 55 | 56 | return cache.ResourceEventHandlerFuncs{ 57 | AddFunc: anyHandler(true), 58 | UpdateFunc: func(_, newObj any) { anyHandler(true)(newObj) }, 59 | DeleteFunc: anyHandler(false), 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /util/labelindex/canonical/set.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package canonical 16 | 17 | import ( 18 | "github.com/kubewharf/podseidon/util/iter" 19 | "github.com/kubewharf/podseidon/util/labelindex/mm" 20 | "github.com/kubewharf/podseidon/util/optional" 21 | utilsort "github.com/kubewharf/podseidon/util/sort" 22 | ) 23 | 24 | type Set []KvPair 25 | 26 | type KvPair struct { 27 | Key string 28 | Value string 29 | } 30 | 31 | type SetStore[Name comparable] struct { 32 | sets map[Name]Set 33 | } 34 | 35 | func NewSetStore[Name comparable]() *SetStore[Name] { 36 | return &SetStore[Name]{ 37 | sets: map[Name]Set{}, 38 | } 39 | } 40 | 41 | func CreateSet(rawSet map[string]string) Set { 42 | set := make(Set, 0, len(rawSet)) 43 | 44 | for key, value := range rawSet { 45 | set = append(set, KvPair{Key: key, Value: value}) 46 | } 47 | 48 | utilsort.ByKey(set, func(pair KvPair) string { return pair.Key }) 49 | 50 | return set 51 | } 52 | 53 | func (store *SetStore[Name]) Insert( 54 | name Name, 55 | stringMap map[string]string, 56 | ) (Set, mm.ExistedBefore) { 57 | set := CreateSet(stringMap) 58 | 59 | _, hadSet := store.sets[name] 60 | store.sets[name] = set 61 | 62 | return set, mm.ExistedBefore(hadSet) 63 | } 64 | 65 | func (store *SetStore[Name]) Remove(name Name) (Set, mm.ExistedBefore) { 66 | set, hasSet := store.sets[name] 67 | if !hasSet { 68 | return nil, mm.ExistedBefore(false) 69 | } 70 | 71 | delete(store.sets, name) 72 | 73 | return set, mm.ExistedBefore(true) 74 | } 75 | 76 | func (store *SetStore[Name]) Get(name Name) optional.Optional[Set] { 77 | return optional.GetMap(store.sets, name) 78 | } 79 | 80 | func (store *SetStore[Name]) Len() int { 81 | return len(store.sets) 82 | } 83 | 84 | func (store *SetStore[Name]) Iter() iter.Iter[iter.Pair[Name, Set]] { 85 | return iter.MapKvs(store.sets) 86 | } 87 | -------------------------------------------------------------------------------- /util/labelindex/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package labelindex 16 | 17 | import ( 18 | "github.com/kubewharf/podseidon/util/iter" 19 | "github.com/kubewharf/podseidon/util/labelindex/mm" 20 | "github.com/kubewharf/podseidon/util/util" 21 | ) 22 | 23 | type ExistedBefore = mm.ExistedBefore 24 | 25 | type Index[Name any, Tracked any, Query any, TrackErr any, QueryErr any] interface { 26 | // Starts tracking an item. 27 | // The state of the index is the same as before calling the method if an error is returned. 28 | Track(name Name, item Tracked) TrackErr 29 | 30 | // Stops tracking an item. 31 | // Returns true if the item was not tracked before. 32 | Untrack(name Name) ExistedBefore 33 | 34 | // Returns all tracked items consistent with the query. 35 | // Read-only operation. 36 | Query(query Query) (iter.Iter[Name], QueryErr) 37 | } 38 | 39 | type ErrAdapter[Err any] interface { 40 | IsErr(err Err) bool 41 | 42 | Nil() Err 43 | } 44 | 45 | type EmptyErrAdapter struct{} 46 | 47 | func (EmptyErrAdapter) IsErr(_ util.Empty) bool { return false } 48 | 49 | func (EmptyErrAdapter) Nil() util.Empty { return util.Empty{} } 50 | 51 | type ErrorErrAdapter struct{} 52 | 53 | func (ErrorErrAdapter) IsErr(err error) bool { return err != nil } 54 | 55 | func (ErrorErrAdapter) Nil() error { return nil } 56 | -------------------------------------------------------------------------------- /util/labelindex/locked.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package labelindex 16 | 17 | import ( 18 | "sync" 19 | 20 | "github.com/kubewharf/podseidon/util/iter" 21 | ) 22 | 23 | // An Index wrapper that protects read/write operations with a RWMutex. 24 | type Locked[ 25 | Name any, Tracked any, Query any, TrackErr any, QueryErr any, 26 | IndexT Index[Name, Tracked, Query, TrackErr, QueryErr], 27 | ] struct { 28 | lock sync.RWMutex 29 | index IndexT 30 | queryErrAdapter ErrAdapter[QueryErr] 31 | } 32 | 33 | func NewLocked[ 34 | Name any, Tracked any, Query any, TrackErr any, QueryErr any, 35 | IndexT Index[Name, Tracked, Query, TrackErr, QueryErr], 36 | ](index IndexT, 37 | queryErrAdapter ErrAdapter[QueryErr], 38 | ) *Locked[Name, Tracked, Query, TrackErr, QueryErr, IndexT] { 39 | return &Locked[Name, Tracked, Query, TrackErr, QueryErr, IndexT]{ 40 | lock: sync.RWMutex{}, 41 | index: index, 42 | queryErrAdapter: queryErrAdapter, 43 | } 44 | } 45 | 46 | func (index *Locked[Name, Tracked, Query, TrackErr, QueryErr, IndexT]) Track( 47 | name Name, 48 | item Tracked, 49 | ) TrackErr { 50 | index.lock.Lock() 51 | defer index.lock.Unlock() 52 | 53 | return index.index.Track(name, item) 54 | } 55 | 56 | func (index *Locked[Name, Tracked, Query, TrackErr, QueryErr, IndexT]) Untrack( 57 | name Name, 58 | ) ExistedBefore { 59 | index.lock.Lock() 60 | defer index.lock.Unlock() 61 | 62 | return index.index.Untrack(name) 63 | } 64 | 65 | // Note that the index is read-locked until the iterator is fully exhausted. 66 | // If the loop is time-consuming, consider collecting the result to a slice first. 67 | func (index *Locked[Name, Tracked, Query, TrackErr, QueryErr, IndexT]) Query( 68 | query Query, 69 | ) (iter.Iter[Name], QueryErr) { 70 | index.lock.RLock() 71 | // do not unlock index.lock if iter is returned, until the iter is exhausted 72 | 73 | names, err := index.index.Query(query) 74 | if index.queryErrAdapter.IsErr(err) { 75 | index.lock.RUnlock() 76 | return nil, err 77 | } 78 | 79 | return names.Defer(index.lock.RUnlock), index.queryErrAdapter.Nil() 80 | } 81 | -------------------------------------------------------------------------------- /util/labelindex/matchable/matchable.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package matchable 16 | 17 | import ( 18 | "github.com/kubewharf/podseidon/util/iter" 19 | "github.com/kubewharf/podseidon/util/labelindex/canonical" 20 | "github.com/kubewharf/podseidon/util/labelindex/settrie" 21 | "github.com/kubewharf/podseidon/util/util" 22 | ) 23 | 24 | // A Matchable indicates either the existence of a key or a kv pair, used as the SetTrie alphabet. 25 | type Matchable struct { 26 | key string 27 | exists bool 28 | value string 29 | } 30 | 31 | func (lhs Matchable) Less(rhs Matchable) bool { 32 | if lhs.key != rhs.key { 33 | return lhs.key < rhs.key 34 | } 35 | 36 | if lhs.exists != rhs.exists { 37 | return !lhs.exists 38 | } 39 | 40 | return lhs.value != rhs.value 41 | } 42 | 43 | func ForSet(set canonical.Set) iter.Iter[Matchable] { 44 | return iter.FlatMap(iter.FromSlice(set), func(pair canonical.KvPair) iter.Iter[Matchable] { 45 | return iter.FromSlice([]Matchable{ 46 | {key: pair.Key, exists: true, value: util.Zero[string]()}, 47 | {key: pair.Key, exists: false, value: pair.Value}, 48 | }) 49 | }) 50 | } 51 | 52 | func WordsForSelector(selector canonical.Selector) iter.Iter[settrie.Word[Matchable]] { 53 | return func(yield iter.Yield[settrie.Word[Matchable]]) iter.Flow { 54 | return recurseMatchableWordsForSelector( 55 | selector, 56 | make([]Matchable, 0, len(selector.KeyExist)+len(selector.KeyIn)), 57 | yield, 58 | ) 59 | } 60 | } 61 | 62 | func recurseMatchableWordsForSelector( 63 | selector canonical.Selector, 64 | baseWord []Matchable, 65 | yield iter.Yield[settrie.Word[Matchable]], 66 | ) iter.Flow { 67 | if len(selector.KeyExist) > 0 { 68 | popped := selector.KeyExist[0] 69 | selector.KeyExist = selector.KeyExist[1:] 70 | 71 | return recurseMatchableWordsForSelector( 72 | selector, 73 | append(baseWord, Matchable{ 74 | key: popped.Key, 75 | exists: true, 76 | value: util.Zero[string](), 77 | }), 78 | yield, 79 | ) 80 | } 81 | 82 | if len(selector.KeyIn) > 0 { 83 | popped := selector.KeyIn[0] 84 | selector.KeyIn = selector.KeyIn[1:] 85 | 86 | for _, value := range popped.Values { 87 | if recurseMatchableWordsForSelector( 88 | selector, 89 | append(baseWord, Matchable{ 90 | key: popped.Key, 91 | exists: false, 92 | value: value, 93 | }), 94 | yield, 95 | ).Break() { 96 | return iter.Break 97 | } 98 | } 99 | 100 | return iter.Continue 101 | } 102 | 103 | return yield(settrie.NewWord(baseWord)) 104 | } 105 | -------------------------------------------------------------------------------- /util/labelindex/mm/multimap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // MultiMap types. 16 | package mm 17 | 18 | import "k8s.io/apimachinery/pkg/util/sets" 19 | 20 | // Like a map[K]V, but allows mapping a single key to multiple distinct values. 21 | type MultiMap[K comparable, V comparable] map[K]sets.Set[V] 22 | 23 | type ExistedBefore bool 24 | 25 | func (mm MultiMap[K, V]) Insert(key K, value V) ExistedBefore { 26 | set, exists := mm[key] 27 | if !exists { 28 | set = sets.New[V]() 29 | mm[key] = set 30 | } 31 | 32 | hadValue := set.Has(value) 33 | set.Insert(value) 34 | 35 | return ExistedBefore(hadValue) 36 | } 37 | 38 | func (mm MultiMap[K, V]) Remove(key K, value V) ExistedBefore { 39 | set, exists := mm[key] 40 | if !exists { 41 | return ExistedBefore(false) 42 | } 43 | 44 | hadValue := set.Has(value) 45 | if !hadValue { 46 | return ExistedBefore(false) 47 | } 48 | 49 | set.Delete(value) 50 | 51 | if set.Len() == 0 { 52 | delete(mm, key) 53 | } 54 | 55 | return ExistedBefore(true) 56 | } 57 | -------------------------------------------------------------------------------- /util/labelindex/namespaced.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package labelindex 16 | 17 | import ( 18 | "k8s.io/apimachinery/pkg/types" 19 | 20 | "github.com/kubewharf/podseidon/util/iter" 21 | ) 22 | 23 | // Isolates each namespace with a separate Index. 24 | type Namespaced[ 25 | Tracked any, Query any, TrackErr any, QueryErr any, 26 | IndexT Index[string, Tracked, Query, TrackErr, QueryErr], 27 | ] struct { 28 | new func() IndexT 29 | namespaces map[string]IndexT 30 | 31 | queryErrAdapter ErrAdapter[QueryErr] 32 | } 33 | 34 | func NewNamespaced[ 35 | Tracked any, Query any, TrackErr any, QueryErr any, 36 | IndexT Index[string, Tracked, Query, TrackErr, QueryErr], 37 | ]( 38 | newIndex func() IndexT, 39 | queryErrAdapter ErrAdapter[QueryErr], 40 | ) *Namespaced[Tracked, Query, TrackErr, QueryErr, IndexT] { 41 | return &Namespaced[Tracked, Query, TrackErr, QueryErr, IndexT]{ 42 | new: newIndex, 43 | namespaces: map[string]IndexT{}, 44 | queryErrAdapter: queryErrAdapter, 45 | } 46 | } 47 | 48 | func (index *Namespaced[Tracked, Query, TrackErr, QueryErr, IndexT]) _() Index[ 49 | types.NamespacedName, Tracked, NamespacedQuery[Query], TrackErr, QueryErr, 50 | ] { 51 | return index 52 | } 53 | 54 | func (index *Namespaced[Tracked, Query, TrackErr, QueryErr, IndexT]) Track( 55 | name types.NamespacedName, 56 | item Tracked, 57 | ) TrackErr { 58 | namespaced, exists := index.namespaces[name.Namespace] 59 | if !exists { 60 | namespaced = index.new() 61 | index.namespaces[name.Namespace] = namespaced 62 | } 63 | 64 | return namespaced.Track(name.Name, item) 65 | } 66 | 67 | func (index *Namespaced[Tracked, Query, TrackErr, QueryErr, IndexT]) Untrack( 68 | name types.NamespacedName, 69 | ) ExistedBefore { 70 | namespaced, exists := index.namespaces[name.Namespace] 71 | if !exists { 72 | return ExistedBefore(false) 73 | } 74 | 75 | return namespaced.Untrack(name.Name) 76 | } 77 | 78 | type NamespacedQuery[T any] struct { 79 | Namespace string 80 | Query T 81 | } 82 | 83 | func (index *Namespaced[Tracked, Query, TrackErr, QueryErr, IndexT]) Query( 84 | query NamespacedQuery[Query], 85 | ) (iter.Iter[types.NamespacedName], QueryErr) { 86 | namespaced, exists := index.namespaces[query.Namespace] 87 | if !exists { 88 | return iter.Empty[types.NamespacedName](), index.queryErrAdapter.Nil() 89 | } 90 | 91 | names, err := namespaced.Query(query.Query) 92 | if index.queryErrAdapter.IsErr(err) { 93 | return nil, err 94 | } 95 | 96 | return iter.Map( 97 | names, 98 | func(name string) types.NamespacedName { 99 | return types.NamespacedName{ 100 | Namespace: query.Namespace, 101 | Name: name, 102 | } 103 | }, 104 | ), index.queryErrAdapter.Nil() 105 | } 106 | -------------------------------------------------------------------------------- /util/labelindex/selectors_bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package labelindex_test 16 | 17 | import ( 18 | "fmt" 19 | "math/rand/v2" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | 25 | "github.com/kubewharf/podseidon/util/iter" 26 | "github.com/kubewharf/podseidon/util/labelindex" 27 | ) 28 | 29 | func BenchmarkSelectorsInsert(b *testing.B) { 30 | index := labelindex.NewSelectors[string]() 31 | rng := rand.New(rand.NewChaCha8([32]byte{})) 32 | 33 | items := make([]iter.Pair[string, metav1.LabelSelector], b.N/2*2) 34 | appId := 0 35 | subAppId := 0 36 | 37 | for itemId := range b.N / 2 { 38 | if rng.IntN(2) == 0 { 39 | appId++ 40 | subAppId = 0 41 | } else { 42 | subAppId++ 43 | } 44 | 45 | items[itemId] = iter.NewPair(fmt.Sprintf("%d-%d", appId, subAppId), metav1.LabelSelector{MatchLabels: map[string]string{ 46 | "aaa": "aaa", 47 | "appId": fmt.Sprint(appId), 48 | "subApp": fmt.Sprint(subAppId), 49 | "zzz": "zzz", 50 | }}) 51 | items[itemId+b.N/2] = iter.NewPair(fmt.Sprintf("%d-%d", appId, subAppId), metav1.LabelSelector{MatchLabels: map[string]string{ 52 | "aaa": "aaa", 53 | "appId": fmt.Sprint(-appId), 54 | "subApp": fmt.Sprint(subAppId), 55 | "zzz": "zzz", 56 | }}) 57 | } 58 | 59 | b.ResetTimer() 60 | 61 | for _, item := range items { 62 | err := index.Track(item.Left, item.Right) 63 | if err != nil { 64 | b.Log(err) 65 | b.FailNow() 66 | } 67 | } 68 | } 69 | 70 | func BenchmarkSelectorsQueryBroad(b *testing.B) { 71 | // Generate data 72 | index := labelindex.NewSelectors[string]() 73 | 74 | rng := rand.New(rand.NewChaCha8([32]byte{})) 75 | 76 | appId := 0 77 | subAppId := 0 78 | 79 | for appId < b.N { 80 | err := index.Track(fmt.Sprintf("%d-%d", appId, subAppId), metav1.LabelSelector{MatchLabels: map[string]string{ 81 | "aaa": "aaa", 82 | "appId": fmt.Sprint(appId), 83 | "subApp": fmt.Sprint(subAppId), 84 | "zzz": "zzz", 85 | }}) 86 | if err != nil { 87 | b.Log(err) 88 | b.FailNow() 89 | } 90 | 91 | if rng.IntN(2) == 0 { 92 | appId++ 93 | subAppId = 0 94 | } else { 95 | subAppId++ 96 | } 97 | } 98 | 99 | queries := iter.Map(iter.Range(0, b.N), func(appId int) map[string]string { 100 | return map[string]string{ 101 | "aaa": "aaa", 102 | "appId": fmt.Sprint(appId), 103 | "subApp": "0", 104 | "zzz": "zzz", 105 | } 106 | }).CollectSlice() 107 | 108 | b.ResetTimer() 109 | 110 | for _, query := range queries { 111 | resultIter, _ := index.Query(query) 112 | result := resultIter.Count() 113 | assert.Equal(b, uint64(1), result) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /util/labelindex/sets_bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package labelindex_test 16 | 17 | import ( 18 | "fmt" 19 | "math/rand/v2" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/require" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | 25 | "github.com/kubewharf/podseidon/util/iter" 26 | "github.com/kubewharf/podseidon/util/labelindex" 27 | ) 28 | 29 | func BenchmarkSetsInsert(b *testing.B) { 30 | index := labelindex.NewSets[string]() 31 | rng := rand.New(rand.NewChaCha8([32]byte{})) 32 | 33 | items := make([]iter.Pair[string, map[string]string], b.N/2*2) 34 | appId := 0 35 | subAppId := 0 36 | 37 | for itemId := range b.N / 2 { 38 | if rng.IntN(2) == 0 { 39 | appId++ 40 | subAppId = 0 41 | } else { 42 | subAppId++ 43 | } 44 | 45 | items[itemId] = iter.NewPair(fmt.Sprintf("%d-%d", appId, subAppId), map[string]string{ 46 | "aaa": "aaa", 47 | "appId": fmt.Sprint(appId), 48 | "subApp": fmt.Sprint(subAppId), 49 | "zzz": "zzz", 50 | }) 51 | items[itemId+b.N/2] = iter.NewPair(fmt.Sprintf("%d-%d", appId, subAppId), map[string]string{ 52 | "aaa": "aaa", 53 | "appId": fmt.Sprint(-appId), 54 | "subApp": fmt.Sprint(subAppId), 55 | "zzz": "zzz", 56 | }) 57 | } 58 | 59 | b.ResetTimer() 60 | 61 | for _, item := range items { 62 | index.Track(item.Left, item.Right) 63 | } 64 | } 65 | 66 | func BenchmarkSetsQueryBroad(b *testing.B) { 67 | // Generate data 68 | index := labelindex.NewSets[string]() 69 | rng := rand.New(rand.NewChaCha8([32]byte{})) 70 | 71 | appId := 0 72 | subAppId := 0 73 | 74 | for appId < b.N { 75 | if rng.IntN(2) == 0 { 76 | appId++ 77 | subAppId = 0 78 | } else { 79 | subAppId++ 80 | } 81 | 82 | index.Track(fmt.Sprintf("%d-%d", appId, subAppId), map[string]string{ 83 | "aaa": "aaa", 84 | "appId": fmt.Sprint(appId), 85 | "subApp": fmt.Sprint(subAppId), 86 | "zzz": "zzz", 87 | }) 88 | } 89 | 90 | queries := iter.Map(iter.Range(0, b.N), func(appId int) metav1.LabelSelector { 91 | return metav1.LabelSelector{ 92 | MatchLabels: map[string]string{ 93 | "aaa": "aaa", 94 | "appId": fmt.Sprint(appId), 95 | "zzz": "zzz", 96 | }, 97 | } 98 | }).CollectSlice() 99 | 100 | b.ResetTimer() 101 | 102 | for _, query := range queries { 103 | resultIter, err := index.Query(query) 104 | require.NoError(b, err) 105 | 106 | resultCount := resultIter.Count() 107 | 108 | if resultCount == 0 { 109 | b.Fail() 110 | b.Logf("query %v yields no results", query) 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /util/labelindex/settrie/multi.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package settrie 16 | 17 | import ( 18 | "github.com/kubewharf/podseidon/util/iter" 19 | "github.com/kubewharf/podseidon/util/optional" 20 | ) 21 | 22 | // A multimap that stores multiple items in each SetTrie node, analogous to map[Word]map[Subkey]Data. 23 | type Multi[Alpha Ordered[Alpha], Subkey comparable, Data any] struct { 24 | trie *SetTrie[Alpha, map[Subkey]Data] 25 | } 26 | 27 | func NewMulti[Alpha Ordered[Alpha], Subkey comparable, Data any]() *Multi[Alpha, Subkey, Data] { 28 | return &Multi[Alpha, Subkey, Data]{ 29 | trie: New[Alpha, map[Subkey]Data](), 30 | } 31 | } 32 | 33 | type ExistedBefore bool 34 | 35 | func (multi *Multi[Alphabet, Subkey, Data]) Insert( 36 | word Word[Alphabet], 37 | subkey Subkey, 38 | data Data, 39 | ) ExistedBefore { 40 | var output ExistedBefore 41 | 42 | multi.trie.CreateOrUpdate(word, func() map[Subkey]Data { 43 | output = ExistedBefore(false) 44 | return map[Subkey]Data{subkey: data} 45 | }, func(map_ *map[Subkey]Data) { 46 | _, existed := (*map_)[subkey] 47 | output = ExistedBefore(existed) 48 | 49 | (*map_)[subkey] = data 50 | }) 51 | 52 | return output 53 | } 54 | 55 | func (multi *Multi[Alphabet, Subkey, Data]) Remove( 56 | word Word[Alphabet], 57 | subkey Subkey, 58 | ) ExistedBefore { 59 | var output ExistedBefore 60 | 61 | multi.trie.RetainOrDrop(word, func(map_ map[Subkey]Data) optional.Optional[map[Subkey]Data] { 62 | _, existed := map_[subkey] 63 | output = ExistedBefore(existed) 64 | 65 | delete(map_, subkey) 66 | 67 | if len(map_) == 0 { 68 | return optional.None[map[Subkey]Data]() 69 | } 70 | 71 | return optional.Some(map_) 72 | }) 73 | 74 | return output 75 | } 76 | 77 | func (multi *Multi[Alphabet, Subkey, Data]) Subsets( 78 | word Word[Alphabet], 79 | ) iter.Iter[iter.Pair[Subkey, Data]] { 80 | return iter.FlatMap( 81 | multi.trie.Subsets(word), 82 | func(pair iter.Pair[Word[Alphabet], map[Subkey]Data]) iter.Iter[iter.Pair[Subkey, Data]] { 83 | return iter.MapKvs(pair.Right) 84 | }, 85 | ) 86 | } 87 | -------------------------------------------------------------------------------- /util/o11y/klog/klog.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package o11yklog 16 | 17 | import ( 18 | "context" 19 | "flag" 20 | "fmt" 21 | "strings" 22 | 23 | "k8s.io/klog/v2" 24 | 25 | "github.com/kubewharf/podseidon/util/component" 26 | "github.com/kubewharf/podseidon/util/errors" 27 | "github.com/kubewharf/podseidon/util/util" 28 | ) 29 | 30 | func RequestKlogArgs(requests *component.DepRequests) { 31 | component.DepPtr(requests, component.Declare( 32 | func(util.Empty) string { return "klog" }, 33 | func(_ util.Empty, fs *flag.FlagSet) util.Empty { 34 | klog.InitFlags(fs) 35 | 36 | fs.VisitAll(func(f *flag.Flag) { 37 | f.Name = strings.ReplaceAll(f.Name, "_", "-") 38 | }) 39 | 40 | return util.Empty{} 41 | }, 42 | func(util.Empty, *component.DepRequests) util.Empty { return util.Empty{} }, 43 | func(context.Context, util.Empty, util.Empty, util.Empty) (*util.Empty, error) { 44 | return &util.Empty{}, nil 45 | }, 46 | component.Lifecycle[util.Empty, util.Empty, util.Empty, util.Empty]{ 47 | Start: nil, 48 | Join: nil, 49 | HealthChecks: nil, 50 | }, 51 | func(*component.Data[util.Empty, util.Empty, util.Empty, util.Empty]) util.Empty { return util.Empty{} }, 52 | )( 53 | util.Empty{}, 54 | )) 55 | } 56 | 57 | func ErrTagKvs(err error) []any { 58 | errTags := []any{} 59 | 60 | for i, tag := range errors.GetTags(err) { 61 | errTags = append(errTags, fmt.Sprintf("errorTag%d", i), tag) 62 | } 63 | 64 | return errTags 65 | } 66 | -------------------------------------------------------------------------------- /util/o11y/metrics/http.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metrics 16 | 17 | import ( 18 | "flag" 19 | "net/http" 20 | 21 | "github.com/prometheus/client_golang/prometheus/promhttp" 22 | 23 | "github.com/kubewharf/podseidon/util/component" 24 | utilhttp "github.com/kubewharf/podseidon/util/http" 25 | "github.com/kubewharf/podseidon/util/util" 26 | ) 27 | 28 | const ( 29 | defaultHttpPort uint16 = 9090 30 | defaultHttpsPort uint16 = 9443 31 | ) 32 | 33 | var NewHttp = utilhttp.DeclareServer( 34 | func(HttpArgs) string { return "prometheus-http" }, 35 | defaultHttpPort, 36 | defaultHttpsPort, 37 | func(HttpArgs, *flag.FlagSet) HttpOptions { return HttpOptions{} }, 38 | func(_ HttpArgs, requests *component.DepRequests) httpDeps { 39 | return httpDeps{ 40 | comp: component.DepPtr(requests, newRegistry()), 41 | } 42 | }, 43 | func(_ HttpArgs, _ HttpOptions, deps httpDeps, mux *http.ServeMux) (*httpState, error) { 44 | //nolint:exhaustruct 45 | mux.Handle("/", promhttp.HandlerFor(deps.comp.Get().PrometheusRegistry, promhttp.HandlerOpts{ 46 | Registry: deps.comp.Get().PrometheusRegistry, 47 | })) 48 | 49 | return &httpState{}, nil 50 | }, 51 | component.Lifecycle[HttpArgs, HttpOptions, httpDeps, httpState]{ 52 | Start: nil, 53 | Join: nil, 54 | HealthChecks: nil, 55 | }, 56 | func(HttpArgs, HttpOptions, httpDeps, *httpState) util.Empty { return util.Empty{} }, 57 | ) 58 | 59 | type HttpArgs struct{} 60 | 61 | type HttpOptions struct{} 62 | 63 | type httpDeps struct { 64 | comp component.Dep[*registryState] 65 | } 66 | 67 | type httpState struct{} 68 | -------------------------------------------------------------------------------- /util/o11y/metrics/multifield.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metrics 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/prometheus/client_golang/prometheus" 21 | ) 22 | 23 | func RegisterMultiField[Value any, Tags any]( 24 | registry Registry, 25 | namePrefix string, 26 | helpPrefix string, 27 | tagsDesc TagsDesc[Tags], 28 | fields ...AnyField[Value], 29 | ) Handle[Tags, Value] { 30 | tagsDesc = filteredTagDesc(tagsDesc, registry.TagFilter) 31 | 32 | tagKeys := tagsDesc.TagKeys() 33 | for _, field := range fields { 34 | registry.Prometheus.MustRegister(field.InitFieldCollector(namePrefix, helpPrefix, tagKeys)) 35 | } 36 | 37 | return Handle[Tags, Value]{ 38 | metricType: multiFieldType[Value](fields), 39 | tagsDesc: tagsDesc, 40 | } 41 | } 42 | 43 | type Field[StructType any, FieldType any] struct { 44 | Name string 45 | Help string 46 | MetricType Type[FieldType] 47 | Extractor func(StructType) FieldType 48 | } 49 | 50 | func NewField[StructType any, FieldType any]( 51 | name string, 52 | help string, 53 | metricType Type[FieldType], 54 | extractor func(StructType) FieldType, 55 | ) AnyField[StructType] { 56 | return Field[StructType, FieldType]{ 57 | Name: name, 58 | Help: help, 59 | MetricType: metricType, 60 | Extractor: extractor, 61 | } 62 | } 63 | 64 | // A type-erased interface for Field[StructType, *]. 65 | type AnyField[StructType any] interface { 66 | InitFieldCollector(namePrefix string, helpPrefix string, tagKeys []string) prometheus.Collector 67 | EmitField(tagValues []string, value StructType) 68 | } 69 | 70 | func (field Field[StructType, FieldType]) InitFieldCollector(namePrefix string, helpPrefix string, tagKeys []string) prometheus.Collector { 71 | return field.MetricType.InitCollector( 72 | fmt.Sprintf("%s_%s", namePrefix, field.Name), 73 | fmt.Sprintf("%s: %s", helpPrefix, field.Help), 74 | tagKeys, 75 | ) 76 | } 77 | 78 | func (field Field[StructType, FieldType]) EmitField(tagValues []string, value StructType) { 79 | field.MetricType.Emit(tagValues, field.Extractor(value)) 80 | } 81 | 82 | type multiFieldType[Value any] []AnyField[Value] 83 | 84 | func (fields multiFieldType[Value]) Emit(tagValues []string, value Value) { 85 | for _, field := range fields { 86 | field.EmitField(tagValues, value) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /util/o11y/metrics/unique/unique.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package unique 16 | 17 | import ( 18 | "strings" 19 | "sync" 20 | 21 | "github.com/axiomhq/hyperloglog" 22 | "github.com/prometheus/client_golang/prometheus" 23 | "github.com/puzpuzpuz/xsync/v3" 24 | 25 | "github.com/kubewharf/podseidon/util/o11y/metrics" 26 | ) 27 | 28 | type counterVec struct { 29 | collector *prometheus.GaugeVec 30 | counters *xsync.MapOf[string, *counter] 31 | } 32 | 33 | func (ty *counterVec) InitCollector(name string, help string, tagKeys []string) prometheus.Collector { 34 | //nolint:exhaustruct 35 | ty.collector = prometheus.NewGaugeVec(prometheus.GaugeOpts{ 36 | Name: name, 37 | Help: help, 38 | }, tagKeys) 39 | 40 | return ty.collector 41 | } 42 | 43 | func (ty *counterVec) Emit(tags []string, value string) { 44 | counter, _ := ty.counters.LoadOrCompute(strings.Join(tags, "|"), func() *counter { 45 | return &counter{ 46 | hllMu: sync.Mutex{}, 47 | hll: hyperloglog.New16(), 48 | gauge: ty.collector.WithLabelValues(tags...), 49 | } 50 | }) 51 | counter.observe(value) 52 | } 53 | 54 | func (ty *counterVec) AsType() metrics.Type[string] { return ty } 55 | 56 | func (ty *counterVec) Flush() { 57 | ty.counters.Range(func(_ string, value *counter) bool { 58 | value.gauge.Set(float64(value.reportAndReset())) 59 | 60 | return true 61 | }) 62 | } 63 | 64 | func NewCounterVec() metrics.FlushableType[string] { 65 | return &counterVec{ 66 | collector: nil, 67 | counters: xsync.NewMapOf[string, *counter](), 68 | } 69 | } 70 | 71 | type counter struct { 72 | hllMu sync.Mutex 73 | hll *hyperloglog.Sketch 74 | gauge prometheus.Gauge 75 | } 76 | 77 | func (counter *counter) observe(value string) { 78 | counter.hllMu.Lock() 79 | defer counter.hllMu.Unlock() 80 | 81 | counter.hll.Insert([]byte(value)) 82 | } 83 | 84 | func (counter *counter) reportAndReset() uint64 { 85 | counter.hllMu.Lock() 86 | defer counter.hllMu.Unlock() 87 | 88 | estimate := counter.hll.Estimate() 89 | counter.hll = hyperloglog.New16() 90 | 91 | return estimate 92 | } 93 | -------------------------------------------------------------------------------- /util/podprotector/observer/logging.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package observer 16 | 17 | import ( 18 | "context" 19 | 20 | "k8s.io/apimachinery/pkg/types" 21 | "k8s.io/klog/v2" 22 | 23 | "github.com/kubewharf/podseidon/util/component" 24 | "github.com/kubewharf/podseidon/util/o11y" 25 | o11yklog "github.com/kubewharf/podseidon/util/o11y/klog" 26 | "github.com/kubewharf/podseidon/util/util" 27 | ) 28 | 29 | func ProvideInformerLogging() component.Declared[IndexedInformerObserver] { 30 | return o11y.Provide( 31 | func(requests *component.DepRequests) util.Empty { 32 | o11yklog.RequestKlogArgs(requests) 33 | return util.Empty{} 34 | }, 35 | func(util.Empty) IndexedInformerObserver { 36 | return IndexedInformerObserver{ 37 | StartHandleEvent: func(ctx context.Context, _ types.NamespacedName) (context.Context, context.CancelFunc) { 38 | return ctx, util.NoOp 39 | }, 40 | EndHandleEvent: func(context.Context, util.Empty) {}, 41 | HandleEventError: func(ctx context.Context, arg HandleEventError) { 42 | klog.FromContext(ctx). 43 | WithCallDepth(1). 44 | Error(arg.Err, "handle reflector event", "namespace", arg.Namespace, "name", arg.Name) 45 | }, 46 | UpdateSourceList: func(ctx context.Context, arg UpdateSourceList) { 47 | if arg.Additions != 0 || arg.Removals != 0 { 48 | klog.FromContext(ctx).WithValues( 49 | "additions", arg.Additions, 50 | "removals", arg.Removals, 51 | "newLength", arg.NewLength, 52 | ).WithCallDepth(1).Info("PodProtector source list updated") 53 | } 54 | }, 55 | UpdateSourceListError: func(ctx context.Context, arg UpdateSourceListError) { 56 | klog.FromContext(ctx).WithCallDepth(1).Error(arg.Err, "updating source list") 57 | }, 58 | } 59 | }, 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /util/podprotector/observer/observer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package observer 16 | 17 | import ( 18 | "k8s.io/apimachinery/pkg/types" 19 | 20 | "github.com/kubewharf/podseidon/util/component" 21 | "github.com/kubewharf/podseidon/util/o11y" 22 | "github.com/kubewharf/podseidon/util/util" 23 | ) 24 | 25 | var ProvideInformer = component.RequireDeps( 26 | component.RequireDep(ProvideInformerLogging()), 27 | component.RequireDep(ProvideInformerMetrics()), 28 | ) 29 | 30 | type IndexedInformerObserver struct { 31 | StartHandleEvent o11y.ObserveScopeFunc[types.NamespacedName] 32 | EndHandleEvent o11y.ObserveFunc[util.Empty] 33 | HandleEventError o11y.ObserveFunc[HandleEventError] 34 | 35 | UpdateSourceList o11y.ObserveFunc[UpdateSourceList] 36 | UpdateSourceListError o11y.ObserveFunc[UpdateSourceListError] 37 | } 38 | 39 | func (IndexedInformerObserver) ComponentName() string { return "ppr-informer" } 40 | 41 | func (observer IndexedInformerObserver) Join( 42 | other IndexedInformerObserver, 43 | ) IndexedInformerObserver { 44 | return o11y.ReflectJoin(observer, other) 45 | } 46 | 47 | type HandleEventError struct { 48 | Namespace string 49 | Name string 50 | Err error 51 | } 52 | 53 | type UpdateSourceList struct { 54 | NewLength int 55 | Additions int 56 | Removals int 57 | } 58 | 59 | type UpdateSourceListError struct { 60 | Err error 61 | } 62 | -------------------------------------------------------------------------------- /util/pprof/pprof.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pprof 16 | 17 | import ( 18 | "flag" 19 | "net/http" 20 | "net/http/pprof" 21 | "runtime" 22 | 23 | "github.com/kubewharf/podseidon/util/component" 24 | utilhttp "github.com/kubewharf/podseidon/util/http" 25 | "github.com/kubewharf/podseidon/util/util" 26 | ) 27 | 28 | const ( 29 | defaultHttpPort uint16 = 6060 30 | defaultHttpsPort uint16 = 6061 31 | ) 32 | 33 | var New = utilhttp.DeclareServer( 34 | func(util.Empty) string { return "pprof" }, 35 | defaultHttpPort, 36 | defaultHttpsPort, 37 | func(_ util.Empty, fs *flag.FlagSet) Options { 38 | return Options{ 39 | BlockProfileRate: fs.Int( 40 | "block-profile", 41 | 0, 42 | "fraction reciprocal of goroutine blocking events reported in blocking profile", 43 | ), 44 | MutexProfileFraction: fs.Int( 45 | "mutex-profile", 46 | 0, 47 | "fraction reciprocal of mutex contention events reported in mutex profile", 48 | ), 49 | } 50 | }, 51 | func(util.Empty, *component.DepRequests) util.Empty { return util.Empty{} }, 52 | func(_ util.Empty, options Options, _ util.Empty, mux *http.ServeMux) (*State, error) { 53 | mux.HandleFunc("/debug/pprof/", pprof.Index) 54 | mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 55 | mux.HandleFunc("/debug/pprof/profile", pprof.Profile) 56 | mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 57 | mux.HandleFunc("/debug/pprof/trace", pprof.Trace) 58 | 59 | if *options.BlockProfileRate > 0 { 60 | runtime.SetBlockProfileRate(*options.BlockProfileRate) 61 | } 62 | 63 | if *options.MutexProfileFraction > 0 { 64 | runtime.SetMutexProfileFraction(*options.MutexProfileFraction) 65 | } 66 | 67 | return &State{}, nil 68 | }, 69 | component.Lifecycle[util.Empty, Options, util.Empty, State]{ 70 | Start: nil, 71 | Join: nil, 72 | HealthChecks: nil, 73 | }, 74 | func(util.Empty, Options, util.Empty, *State) util.Empty { return util.Empty{} }, 75 | ) 76 | 77 | type Options struct { 78 | BlockProfileRate *int 79 | MutexProfileFraction *int 80 | } 81 | 82 | type State struct{} 83 | -------------------------------------------------------------------------------- /util/retrybatch/messages.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package retrybatch 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/kubewharf/podseidon/util/util" 21 | ) 22 | 23 | type operationRequest[Arg any, Result any] struct { 24 | arg Arg 25 | retries int 26 | resultCh chan<- submitResult[Result] 27 | ctxErr func() error 28 | } 29 | 30 | // Result to be returned by Pool.Submit. 31 | // 32 | // The field ExecuteResult is only usable when Err is nil. 33 | type submitResult[Result any] struct { 34 | ExecuteResult Result 35 | Retries int 36 | Err error 37 | } 38 | 39 | func errorSubmitResult[Result any](retries int, err error) submitResult[Result] { 40 | return submitResult[Result]{ 41 | ExecuteResult: util.Zero[Result](), 42 | Retries: retries, 43 | Err: err, 44 | } 45 | } 46 | 47 | // Only one of the two optionals is present. 48 | type ExecuteResult[Result any] struct { 49 | Variant ExecuteResultVariant 50 | 51 | Success func(i int) Result 52 | NeedRetry time.Duration 53 | Err error 54 | } 55 | 56 | type ExecuteResultVariant uint8 57 | 58 | func (variant ExecuteResultVariant) String() string { 59 | switch variant { 60 | case ExecuteResultVariantSuccess: 61 | return "Success" 62 | case ExecuteResultVariantNeedRetry: 63 | return "NeedRetry" 64 | case ExecuteResultVariantErr: 65 | return "Err" 66 | } 67 | 68 | panic("invalid variant") 69 | } 70 | 71 | const ( 72 | ExecuteResultVariantSuccess = ExecuteResultVariant(iota + 1) 73 | ExecuteResultVariantNeedRetry 74 | ExecuteResultVariantErr 75 | ) 76 | 77 | func ExecuteResultSuccess[Result any](resultFn func(i int) Result) ExecuteResult[Result] { 78 | var output ExecuteResult[Result] 79 | output.Variant = ExecuteResultVariantSuccess 80 | output.Success = resultFn 81 | 82 | return output 83 | } 84 | 85 | func ExecuteResultNeedRetry[Result any](retryDelay time.Duration) ExecuteResult[Result] { 86 | var output ExecuteResult[Result] 87 | output.Variant = ExecuteResultVariantNeedRetry 88 | output.NeedRetry = retryDelay 89 | 90 | return output 91 | } 92 | 93 | func ExecuteResultErr[Result any](err error) ExecuteResult[Result] { 94 | var output ExecuteResult[Result] 95 | output.Variant = ExecuteResultVariantErr 96 | output.Err = err 97 | 98 | return output 99 | } 100 | -------------------------------------------------------------------------------- /util/retrybatch/observer/observer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package observer 16 | 17 | import ( 18 | "github.com/kubewharf/podseidon/util/component" 19 | "github.com/kubewharf/podseidon/util/o11y" 20 | ) 21 | 22 | var Provide = component.RequireDeps( 23 | component.RequireDep(ProvideMetrics()), 24 | ) 25 | 26 | type Observer struct { 27 | StartSubmit o11y.ObserveScopeFunc[StartSubmit] 28 | EndSubmit o11y.ObserveFunc[EndSubmit] 29 | 30 | StartBatch o11y.ObserveScopeFunc[StartBatch] 31 | EndBatch o11y.ObserveFunc[EndBatch] 32 | 33 | StartExecute o11y.ObserveScopeFunc[StartExecute] 34 | EndExecute o11y.ObserveFunc[EndExecute] 35 | 36 | InFlightKeys o11y.MonitorFunc[InFlightKeys, int] 37 | } 38 | 39 | func (Observer) ComponentName() string { return "retrybatch" } 40 | 41 | func (observer Observer) Join(other Observer) Observer { return o11y.ReflectJoin(observer, other) } 42 | 43 | type StartSubmit struct { 44 | PoolName string 45 | } 46 | 47 | type EndSubmit struct { 48 | RetryCount int 49 | Err error 50 | } 51 | 52 | type StartBatch struct { 53 | PoolName string 54 | } 55 | 56 | type EndBatch struct { 57 | RetryCount int 58 | } 59 | 60 | type StartExecute struct { 61 | PoolName string 62 | BatchSize int 63 | } 64 | 65 | type EndExecute struct { 66 | ExecuteResultVariant string 67 | Err error 68 | } 69 | 70 | type InFlightKeys struct { 71 | PoolName string 72 | } 73 | -------------------------------------------------------------------------------- /util/retrybatch/syncbarrier/barrier.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package syncbarrier 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/kubewharf/podseidon/util/util" 21 | ) 22 | 23 | // An internal interface to insert synchronization barriers to the operations of a retrybatch.Pool. 24 | // 25 | // Should not be used externally. 26 | type Interface[Key any] interface { 27 | util.BlockNotifier 28 | 29 | TimeAfter(duration time.Duration) <-chan time.Time 30 | 31 | StartCheckCanceled(key Key) 32 | FinishCheckCanceled(key Key) 33 | 34 | StartDispatchResult(key Key) 35 | FinishDispatchResult(key Key) 36 | 37 | PrepareFuseAfterIdle(key Key) 38 | AfterFuse(key Key, success bool) 39 | 40 | BeforeHandleAcquire(key Key) 41 | AfterHandleAcquire(key Key) 42 | } 43 | 44 | type Empty[Key any] struct{} 45 | 46 | func (Empty[Key]) TimeAfter(duration time.Duration) <-chan time.Time { return time.After(duration) } 47 | 48 | func (Empty[Key]) OnBlock() {} 49 | func (Empty[Key]) OnUnblock() {} 50 | 51 | func (Empty[Key]) StartCheckCanceled(Key) {} 52 | func (Empty[Key]) FinishCheckCanceled(Key) {} 53 | 54 | func (Empty[Key]) StartDispatchResult(Key) {} 55 | func (Empty[Key]) FinishDispatchResult(Key) {} 56 | 57 | func (Empty[Key]) PrepareFuseAfterIdle(Key) {} 58 | func (Empty[Key]) AfterFuse(Key, bool) {} 59 | 60 | func (Empty[Key]) BeforeHandleAcquire(Key) {} 61 | func (Empty[Key]) AfterHandleAcquire(Key) {} 62 | -------------------------------------------------------------------------------- /util/shutdown/shutdown.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package shutdown 16 | 17 | import ( 18 | "context" 19 | "flag" 20 | "os" 21 | "os/signal" 22 | "sync/atomic" 23 | "syscall" 24 | 25 | "github.com/kubewharf/podseidon/util/component" 26 | "github.com/kubewharf/podseidon/util/util" 27 | ) 28 | 29 | var New = component.Declare( 30 | func(util.Empty) string { return "shutdown-notifier" }, 31 | func(util.Empty, *flag.FlagSet) util.Empty { return util.Empty{} }, 32 | func(util.Empty, *component.DepRequests) util.Empty { return util.Empty{} }, 33 | func(context.Context, util.Empty, util.Empty, util.Empty) (*Notifier, error) { 34 | return &Notifier{ 35 | stopped: atomic.Bool{}, 36 | watchCh: make(chan util.Empty), 37 | }, nil 38 | }, 39 | component.Lifecycle[util.Empty, util.Empty, util.Empty, Notifier]{ 40 | Start: func(_ context.Context, _ *util.Empty, _ *util.Empty, _ *util.Empty, state *Notifier) error { 41 | state.addSignalHandler() 42 | return nil 43 | }, 44 | Join: nil, 45 | HealthChecks: nil, 46 | }, 47 | func(d *component.Data[util.Empty, util.Empty, util.Empty, Notifier]) *Notifier { 48 | return d.State 49 | }, 50 | ) 51 | 52 | type Notifier struct { 53 | stopped atomic.Bool 54 | watchCh chan util.Empty 55 | } 56 | 57 | func (notifier *Notifier) Close() { 58 | if notifier.stopped.CompareAndSwap(false, true) { 59 | close(notifier.watchCh) 60 | } 61 | } 62 | 63 | func (notifier *Notifier) StopChan() <-chan util.Empty { 64 | return notifier.watchCh 65 | } 66 | 67 | func (notifier *Notifier) CallOnStop(fn func()) { 68 | go func() { 69 | <-notifier.watchCh 70 | fn() 71 | }() 72 | } 73 | 74 | func (notifier *Notifier) addSignalHandler() { 75 | signalCh := make(chan os.Signal, 1) 76 | signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM) 77 | 78 | go func() { 79 | <-signalCh 80 | notifier.Close() 81 | }() 82 | } 83 | -------------------------------------------------------------------------------- /util/sort/sort.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utilsort 16 | 17 | import ( 18 | "cmp" 19 | "sort" 20 | 21 | "github.com/kubewharf/podseidon/util/iter" 22 | "github.com/kubewharf/podseidon/util/optional" 23 | ) 24 | 25 | func ByKey[T any, K cmp.Ordered](slice []T, keyFn func(T) K) { 26 | sort.SliceStable(slice, func(i, j int) bool { 27 | return keyFn(slice[i]) < keyFn(slice[j]) 28 | }) 29 | } 30 | 31 | type LeftZipItem[Left any, Right any] struct { 32 | Left Left 33 | Right optional.Optional[Right] 34 | } 35 | 36 | // Zip-iter two sorted slices, yielding each element of leftSlice exactly once, 37 | // together with a rightSlice element with the same key if available. 38 | // If the same key appears in rightSlice multiple times, only the first occurrence may get yielded. 39 | func LeftZip[Key cmp.Ordered, Left any, Right any]( 40 | leftSlice []Left, 41 | leftKeyFn func(Left) Key, 42 | rightSlice []Right, 43 | rightKeyFn func(Right) Key, 44 | ) iter.Iter[LeftZipItem[Left, Right]] { 45 | return func(yield iter.Yield[LeftZipItem[Left, Right]]) iter.Flow { 46 | for _, leftItem := range leftSlice { 47 | leftKey := leftKeyFn(leftItem) 48 | 49 | rightItem := optional.None[Right]() 50 | 51 | for len(rightSlice) != 0 { 52 | rightKey := rightKeyFn(rightSlice[0]) 53 | 54 | if rightKey < leftKey { 55 | // right slice head is too small, can be shifted 56 | rightSlice = rightSlice[1:] 57 | continue 58 | } 59 | 60 | if rightKey == leftKey { 61 | rightItem = optional.Some(rightSlice[0]) 62 | } 63 | 64 | // further items in right cannot match anyway 65 | break 66 | } 67 | 68 | if yield(LeftZipItem[Left, Right]{Left: leftItem, Right: rightItem}).Break() { 69 | return iter.Break 70 | } 71 | } 72 | 73 | return iter.Continue 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /util/util/atomic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import "golang.org/x/exp/constraints" 18 | 19 | type Atomic[Base any] interface { 20 | CompareAndSwap(oldValue, newValue Base) (swapped bool) 21 | Load() Base 22 | Store(newValue Base) 23 | Swap(newValue Base) (oldValue Base) 24 | } 25 | 26 | func AtomicMax[Base constraints.Ordered](ptr Atomic[Base], other Base) (_stored bool) { 27 | return AtomicExtrema(ptr, other, func(left, right Base) bool { return left < right }) 28 | } 29 | 30 | func AtomicMin[Base constraints.Ordered](ptr Atomic[Base], other Base) (_stored bool) { 31 | return AtomicExtrema(ptr, other, func(left, right Base) bool { return left > right }) 32 | } 33 | 34 | func AtomicExtrema[Base any]( 35 | ptr Atomic[Base], 36 | other Base, 37 | rightIsBetter func(Base, Base) bool, 38 | ) (_stored bool) { 39 | for { 40 | old := ptr.Load() 41 | if !rightIsBetter(old, other) { 42 | return false 43 | } 44 | 45 | success := ptr.CompareAndSwap(old, other) 46 | if success { 47 | return true 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /util/util/maps.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | // Returns an arbitrary map entry and true, 18 | // or zero KV and false if the map is empty. 19 | func GetArbitraryMapEntry[K comparable, V any](map_ map[K]V) (K, V, bool) { 20 | for k, v := range map_ { 21 | return k, v, true 22 | } 23 | 24 | return Zero[K](), Zero[V](), false 25 | } 26 | -------------------------------------------------------------------------------- /util/util/math.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import "golang.org/x/exp/constraints" 18 | 19 | // Returns the nearest integer to {numerator / divisor} with half-positive rounding. 20 | func RoundedIntDiv[T constraints.Integer](numerator, divisor T) T { 21 | if divisor < 0 { 22 | divisor = -divisor 23 | } 24 | 25 | quotient := numerator / divisor 26 | remainder := numerator % divisor 27 | 28 | if numerator > 0 && remainder*2 >= divisor { 29 | quotient++ 30 | } 31 | 32 | if numerator < 0 && (divisor+remainder)*2 < divisor { 33 | quotient-- 34 | } 35 | 36 | return quotient 37 | } 38 | -------------------------------------------------------------------------------- /util/util/math_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util_test 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | "golang.org/x/exp/constraints" 22 | 23 | "github.com/kubewharf/podseidon/util/util" 24 | ) 25 | 26 | func testRoundedIntDiv[T constraints.Integer](t *testing.T, numerator T, divisor T, expect T) { 27 | t.Helper() 28 | assert.Equal(t, expect, util.RoundedIntDiv(numerator, divisor)) 29 | } 30 | 31 | func TestRoundDownIntDiv(t *testing.T) { 32 | t.Parallel() 33 | testRoundedIntDiv(t, 14, 10, 1) 34 | } 35 | 36 | func TestRoundUpIntDiv(t *testing.T) { 37 | t.Parallel() 38 | testRoundedIntDiv(t, 16, 10, 2) 39 | } 40 | 41 | func TestRoundHalfUpIntDiv(t *testing.T) { 42 | t.Parallel() 43 | testRoundedIntDiv(t, 15, 10, 2) 44 | } 45 | 46 | func TestRoundNegativeHalfUpIntDiv(t *testing.T) { 47 | t.Parallel() 48 | testRoundedIntDiv(t, -5, 10, 0) 49 | testRoundedIntDiv(t, -15, 10, -1) 50 | } 51 | 52 | func TestRoundNegativeUpIntDiv(t *testing.T) { 53 | t.Parallel() 54 | testRoundedIntDiv(t, -4, 10, 0) 55 | testRoundedIntDiv(t, -14, 10, -1) 56 | } 57 | 58 | func TestRoundNegativeDownIntDiv(t *testing.T) { 59 | t.Parallel() 60 | testRoundedIntDiv(t, -6, 10, -1) 61 | testRoundedIntDiv(t, -16, 10, -2) 62 | } 63 | -------------------------------------------------------------------------------- /util/util/sync.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "sync/atomic" 19 | ) 20 | 21 | type LateInitReader[T any] struct { 22 | readyCh <-chan Empty 23 | value *T 24 | } 25 | 26 | type LateInitWriter[T any] func(T) 27 | 28 | func (reader LateInitReader[T]) Get() T { 29 | <-reader.readyCh 30 | return *reader.value 31 | } 32 | 33 | func NewLateInit[T any]() (_reader LateInitReader[T], _writer LateInitWriter[T]) { 34 | readyCh := make(chan Empty) 35 | box := new(T) 36 | 37 | written := new(atomic.Bool) 38 | 39 | reader := LateInitReader[T]{ 40 | readyCh: readyCh, 41 | value: box, 42 | } 43 | writer := func(value T) { 44 | if wasWritten := written.Swap(true); wasWritten { 45 | panic("LateInit cannot be written multiple times") 46 | } 47 | 48 | *box = value 49 | 50 | close(readyCh) 51 | } 52 | 53 | return reader, writer 54 | } 55 | -------------------------------------------------------------------------------- /util/util/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import "reflect" 18 | 19 | type Empty = struct{} 20 | 21 | // Returns a zero value to be initialized later. 22 | func Zero[T any]() T { 23 | var t T 24 | return t 25 | } 26 | 27 | func IsZero[T comparable](value T) bool { 28 | return value == Zero[T]() 29 | } 30 | 31 | func Type[T any]() reflect.Type { 32 | return reflect.TypeOf([0]T{}).Elem() 33 | } 34 | 35 | func TypeName[T any]() string { 36 | return Type[T]().Name() 37 | } 38 | 39 | func Identity[T any](t T) T { return t } 40 | 41 | func NoOp() {} 42 | -------------------------------------------------------------------------------- /util/worker/observer/logging.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package observer 16 | 17 | import ( 18 | "context" 19 | "sync/atomic" 20 | "time" 21 | 22 | "k8s.io/klog/v2" 23 | 24 | "github.com/kubewharf/podseidon/util/component" 25 | "github.com/kubewharf/podseidon/util/o11y" 26 | o11yklog "github.com/kubewharf/podseidon/util/o11y/klog" 27 | "github.com/kubewharf/podseidon/util/util" 28 | ) 29 | 30 | var nextReconcileId atomic.Uint64 31 | 32 | type workerStartTimeKey struct{} 33 | 34 | func ProvideLogging() component.Declared[Observer] { 35 | return o11y.Provide( 36 | func(requests *component.DepRequests) util.Empty { 37 | o11yklog.RequestKlogArgs(requests) 38 | return util.Empty{} 39 | }, 40 | func(util.Empty) Observer { 41 | return Observer{ 42 | StartReconcile: func(ctx context.Context, arg StartReconcile) (context.Context, context.CancelFunc) { 43 | ctx = context.WithValue(ctx, workerStartTimeKey{}, time.Now()) 44 | 45 | logger := klog.FromContext(ctx) 46 | logger = logger.WithValues( 47 | "worker", arg.WorkerName, 48 | "reconcile", nextReconcileId.Add(1), 49 | ) 50 | return klog.NewContext(ctx, logger), util.NoOp 51 | }, 52 | EndReconcile: func(ctx context.Context, args EndReconcile) { 53 | startTime := ctx.Value(workerStartTimeKey{}).(time.Time) 54 | 55 | logger := klog.FromContext(ctx) 56 | logger = logger.WithValues("duration", time.Since(startTime)) 57 | 58 | if args.Err != nil { 59 | logger.WithCallDepth(1). 60 | Error(args.Err, "reconcile error", o11yklog.ErrTagKvs(args.Err)...) 61 | } else { 62 | logger.V(3).WithCallDepth(1).Info("reconcile complete") 63 | } 64 | }, 65 | QueueLength: func(_ context.Context, _ QueueLength, _ func() int) {}, 66 | } 67 | }, 68 | ) 69 | } 70 | -------------------------------------------------------------------------------- /util/worker/observer/metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package observer 16 | 17 | import ( 18 | "context" 19 | "time" 20 | 21 | "github.com/kubewharf/podseidon/util/component" 22 | "github.com/kubewharf/podseidon/util/errors" 23 | "github.com/kubewharf/podseidon/util/o11y" 24 | "github.com/kubewharf/podseidon/util/o11y/metrics" 25 | "github.com/kubewharf/podseidon/util/util" 26 | ) 27 | 28 | func ProvideMetrics() component.Declared[Observer] { 29 | return o11y.Provide( 30 | metrics.MakeObserverDeps, 31 | func(deps metrics.ObserverDeps) Observer { 32 | type workerDataKey struct{} 33 | 34 | type workerDataValue struct { 35 | startTime time.Time 36 | workerName string 37 | } 38 | 39 | type reconcileTags struct { 40 | Worker string 41 | Error string 42 | } 43 | 44 | type queueLengthTags struct { 45 | Worker string 46 | } 47 | 48 | reconcileHandle := metrics.Register( 49 | deps.Registry(), 50 | "worker_reconcile", 51 | "Duration of a worker reconcile run.", 52 | metrics.FunctionDurationHistogram(), 53 | metrics.NewReflectTags[reconcileTags](), 54 | ) 55 | queueLengthHandle := metrics.Register( 56 | deps.Registry(), 57 | "worker_queue_length", 58 | "Queue length of a worker.", 59 | metrics.IntGauge(), 60 | metrics.NewReflectTags[queueLengthTags](), 61 | ) 62 | 63 | return Observer{ 64 | StartReconcile: func(ctx context.Context, arg StartReconcile) (context.Context, context.CancelFunc) { 65 | return context.WithValue(ctx, workerDataKey{}, workerDataValue{ 66 | startTime: time.Now(), 67 | workerName: arg.WorkerName, 68 | }), util.NoOp 69 | }, 70 | EndReconcile: func(ctx context.Context, arg EndReconcile) { 71 | data := ctx.Value(workerDataKey{}).(workerDataValue) 72 | 73 | duration := time.Since(data.startTime) 74 | 75 | reconcileHandle.Emit(duration, reconcileTags{ 76 | Worker: data.workerName, 77 | Error: errors.SerializeTags(arg.Err), 78 | }) 79 | }, 80 | QueueLength: func(ctx context.Context, arg QueueLength, getter func() int) { 81 | metrics.Repeating(ctx, deps, queueLengthHandle.With(queueLengthTags{Worker: arg.WorkerName}), getter) 82 | }, 83 | } 84 | }, 85 | ) 86 | } 87 | -------------------------------------------------------------------------------- /util/worker/observer/observer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package observer 16 | 17 | import ( 18 | "github.com/kubewharf/podseidon/util/component" 19 | "github.com/kubewharf/podseidon/util/o11y" 20 | ) 21 | 22 | var Provide = component.RequireDeps( 23 | component.RequireDep(ProvideLogging()), 24 | component.RequireDep(ProvideMetrics()), 25 | ) 26 | 27 | type Observer struct { 28 | StartReconcile o11y.ObserveScopeFunc[StartReconcile] 29 | EndReconcile o11y.ObserveFunc[EndReconcile] 30 | QueueLength o11y.MonitorFunc[QueueLength, int] 31 | } 32 | 33 | func (Observer) ComponentName() string { return "worker" } 34 | 35 | func (observer Observer) Join(other Observer) Observer { return o11y.ReflectJoin(observer, other) } 36 | 37 | type StartReconcile struct { 38 | WorkerName string 39 | } 40 | 41 | type EndReconcile struct { 42 | Err error 43 | } 44 | 45 | type QueueLength struct { 46 | WorkerName string 47 | } 48 | -------------------------------------------------------------------------------- /util/worker/prereq.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package worker 16 | 17 | import ( 18 | "context" 19 | 20 | "k8s.io/client-go/tools/cache" 21 | ) 22 | 23 | // A prerequisite condition before a worker can starts running. 24 | // 25 | // Worker starts running as long as Wait returns true the first time. 26 | // IsReady may return false subsequently, 27 | // but it would only affect health checks but not worker liveness. 28 | type Prereq interface { 29 | Wait(ctx context.Context) bool 30 | 31 | IsReady() bool 32 | } 33 | 34 | func InformerPrereq(informer cache.SharedIndexInformer) Prereq { 35 | return HasSyncedPrereq(informer.HasSynced) 36 | } 37 | 38 | func HasSyncedPrereq(hasSynced cache.InformerSynced) Prereq { 39 | return &hasSyncedPrereq{hasSynced: hasSynced} 40 | } 41 | 42 | type hasSyncedPrereq struct { 43 | hasSynced cache.InformerSynced 44 | } 45 | 46 | func (prereq *hasSyncedPrereq) Wait(ctx context.Context) bool { 47 | return cache.WaitForCacheSync(ctx.Done(), prereq.hasSynced) 48 | } 49 | 50 | func (prereq *hasSyncedPrereq) IsReady() bool { 51 | return prereq.hasSynced() 52 | } 53 | -------------------------------------------------------------------------------- /webhook/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kubewharf/podseidon/webhook 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | github.com/kubewharf/podseidon/apis v0.0.0 9 | github.com/kubewharf/podseidon/util v0.0.0 10 | k8s.io/api v0.33.1 11 | k8s.io/apimachinery v0.33.1 12 | k8s.io/client-go v0.33.1 13 | k8s.io/klog/v2 v2.130.1 14 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 15 | ) 16 | 17 | require ( 18 | github.com/axiomhq/hyperloglog v0.2.5 // indirect 19 | github.com/beorn7/perks v1.0.1 // indirect 20 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 21 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 22 | github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc // indirect 23 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 24 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 25 | github.com/go-logr/logr v1.4.2 // indirect 26 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 27 | github.com/go-openapi/jsonreference v0.21.0 // indirect 28 | github.com/go-openapi/swag v0.23.0 // indirect 29 | github.com/gogo/protobuf v1.3.2 // indirect 30 | github.com/google/gnostic-models v0.6.9 // indirect 31 | github.com/google/go-cmp v0.7.0 // indirect 32 | github.com/google/uuid v1.6.0 // indirect 33 | github.com/josharian/intern v1.0.0 // indirect 34 | github.com/json-iterator/go v1.1.12 // indirect 35 | github.com/kamstrup/intmap v0.5.1 // indirect 36 | github.com/kubewharf/podseidon/client v0.0.0 // indirect 37 | github.com/mailru/easyjson v0.7.7 // indirect 38 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 39 | github.com/modern-go/reflect2 v1.0.2 // indirect 40 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 41 | github.com/pkg/errors v0.9.1 // indirect 42 | github.com/prometheus/client_golang v1.22.0 // indirect 43 | github.com/prometheus/client_model v0.6.1 // indirect 44 | github.com/prometheus/common v0.62.0 // indirect 45 | github.com/prometheus/procfs v0.15.1 // indirect 46 | github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect 47 | github.com/spf13/pflag v1.0.6 // indirect 48 | github.com/x448/float16 v0.8.4 // indirect 49 | golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect 50 | golang.org/x/net v0.39.0 // indirect 51 | golang.org/x/oauth2 v0.27.0 // indirect 52 | golang.org/x/sys v0.32.0 // indirect 53 | golang.org/x/term v0.31.0 // indirect 54 | golang.org/x/text v0.24.0 // indirect 55 | golang.org/x/time v0.9.0 // indirect 56 | google.golang.org/protobuf v1.36.5 // indirect 57 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 58 | gopkg.in/inf.v0 v0.9.1 // indirect 59 | gopkg.in/yaml.v3 v3.0.1 // indirect 60 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 61 | sigs.k8s.io/controller-runtime v0.21.0 // indirect 62 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 63 | sigs.k8s.io/randfill v1.0.0 // indirect 64 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 65 | sigs.k8s.io/yaml v1.4.0 // indirect 66 | ) 67 | 68 | replace ( 69 | github.com/kubewharf/podseidon/apis => ../apis 70 | github.com/kubewharf/podseidon/client => ../client 71 | github.com/kubewharf/podseidon/util => ../util 72 | ) 73 | -------------------------------------------------------------------------------- /webhook/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Podseidon Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "github.com/kubewharf/podseidon/util/cmd" 19 | "github.com/kubewharf/podseidon/util/component" 20 | healthzobserver "github.com/kubewharf/podseidon/util/healthz/observer" 21 | "github.com/kubewharf/podseidon/util/o11y/metrics" 22 | pprutil "github.com/kubewharf/podseidon/util/podprotector" 23 | pprutilobserver "github.com/kubewharf/podseidon/util/podprotector/observer" 24 | "github.com/kubewharf/podseidon/util/pprof" 25 | retrybatchobserver "github.com/kubewharf/podseidon/util/retrybatch/observer" 26 | "github.com/kubewharf/podseidon/util/util" 27 | 28 | "github.com/kubewharf/podseidon/webhook/handler" 29 | webhookobserver "github.com/kubewharf/podseidon/webhook/observer" 30 | "github.com/kubewharf/podseidon/webhook/server" 31 | ) 32 | 33 | func main() { 34 | cmd.Run( 35 | component.RequireDep(pprof.New(util.Empty{})), 36 | component.RequireDep(metrics.NewHttp(metrics.HttpArgs{})), 37 | healthzobserver.Provide, 38 | pprutilobserver.ProvideInformer, 39 | webhookobserver.Provide, 40 | retrybatchobserver.Provide, 41 | component.RequireDep(server.New(server.Args{})), 42 | pprutil.RequireSingleSourceProvider(pprutil.SingleSourceProviderArgs{ClusterName: "core"}, true), 43 | handler.DefaultRequiresPodNameImpls, 44 | ) 45 | } 46 | --------------------------------------------------------------------------------