├── test ├── scripts │ └── snippets.sh ├── e2e │ ├── resources │ │ ├── test-envelope-object.yaml │ │ ├── test-clusterrole.yaml │ │ ├── resourcequota.yaml │ │ ├── test-configmap.yaml │ │ ├── test-service.yaml │ │ ├── test-job.yaml │ │ ├── test-envelope-configmap.yaml │ │ ├── test-deployment.yaml │ │ ├── test-daemonset.yaml │ │ ├── statefulset-with-storage.yaml │ │ ├── statefulset-basic.yaml │ │ ├── webhook.yaml │ │ └── statefulset-invalid-storage.yaml │ ├── kindconfigs │ │ ├── cluster-1.yaml │ │ ├── cluster-2.yaml │ │ └── cluster-3.yaml │ ├── azure_valid_config.yaml │ └── stop.sh ├── manifests │ └── test-resource.yaml ├── upgrade │ ├── stop.sh │ ├── after │ │ ├── resources_test.go │ │ └── utils_test.go │ └── before │ │ └── resources_test.go ├── apis │ └── v1alpha1 │ │ ├── groupversion_info.go │ │ └── testresource_types.go └── utils │ ├── metrics │ └── metrics.go │ └── controller │ └── controller.go ├── cmd ├── memberagent │ └── testdata │ │ └── token ├── authtoken │ └── main_test.go └── hubagent │ └── options │ └── webhookconnectiontype.go ├── .codespellignore ├── codecov.yml ├── screenshots ├── img.png ├── img1.png ├── img2.png └── cncf-logo.png ├── pkg ├── utils │ ├── cloudconfig │ │ └── azure │ │ │ └── test │ │ │ ├── azure_config_nojson.txt │ │ │ ├── azure_invalid_config.json │ │ │ └── azure_valid_config.json │ ├── time │ │ ├── time.go │ │ └── time_test.go │ ├── httpclient │ │ ├── round_trippper.go │ │ └── round_trippper_test.go │ ├── defaulter │ │ └── work.go │ ├── parallelizer │ │ ├── errorflag_test.go │ │ ├── parallelizer_test.go │ │ └── errorflag.go │ ├── validator │ │ ├── membercluster.go │ │ ├── clusterresourceplacementeviction.go │ │ └── clusterresourceplacementdisruptionbudget.go │ ├── controller │ │ ├── updaterun_resolver.go │ │ ├── strategy_resolver.go │ │ └── snapshot_builder.go │ ├── eviction │ │ └── eviction.go │ ├── binding │ │ └── binding.go │ ├── resource │ │ └── resource.go │ ├── informer │ │ └── readiness │ │ │ └── readiness.go │ └── labels │ │ └── labels.go ├── controllers │ ├── rollout │ │ └── manifests │ │ │ ├── test_namespace.yaml │ │ │ ├── test-configmap.yaml │ │ │ └── test_pdb.yaml │ ├── workgenerator │ │ └── manifests │ │ │ ├── test_namespace.yaml │ │ │ ├── test-configmap.yaml │ │ │ ├── clusterrole.yaml │ │ │ ├── test_pdb.yaml │ │ │ ├── test-resource-overriden.yaml │ │ │ ├── resourcequota.yaml │ │ │ ├── resourcequota2.yaml │ │ │ ├── test-resource-envelope.yaml │ │ │ ├── test-resource-envelope2.yaml │ │ │ ├── webhook.yaml │ │ │ └── test-clusterscoped-envelope.yaml │ └── clusterresourceplacementstatuswatcher │ │ └── watcher_test.go ├── scheduler │ ├── framework │ │ ├── plugins │ │ │ ├── sameplacementaffinity │ │ │ │ ├── scoring.go │ │ │ │ └── filtering.go │ │ │ └── tainttoleration │ │ │ │ ├── plugin.go │ │ │ │ └── filtering.go │ │ ├── cyclestateutils.go │ │ └── profile_test.go │ └── queue │ │ └── queue_test.go ├── authtoken │ ├── interfaces.go │ ├── token_writer_test.go │ └── token_writer.go ├── webhook │ └── add_handler.go └── metrics │ └── shared │ └── metrics.go ├── charts ├── hub-agent │ ├── crdbases │ │ ├── placement.kubernetes-fleet.io_works.yaml │ │ ├── cluster.kubernetes-fleet.io_memberclusters.yaml │ │ ├── cluster.kubernetes-fleet.io_internalmemberclusters.yaml │ │ └── placement.kubernetes-fleet.io_clusterresourceplacements.yaml │ ├── templates │ │ ├── namespace.yaml │ │ ├── crds │ │ │ ├── multicluster.x-k8s.io_clusterprofiles.yaml │ │ │ ├── placement.kubernetes-fleet.io_approvalrequests.yaml │ │ │ ├── placement.kubernetes-fleet.io_resourcebindings.yaml │ │ │ ├── placement.kubernetes-fleet.io_stagedupdateruns.yaml │ │ │ ├── placement.kubernetes-fleet.io_resourceenvelopes.yaml │ │ │ ├── placement.kubernetes-fleet.io_resourceoverrides.yaml │ │ │ ├── placement.kubernetes-fleet.io_resourceplacements.yaml │ │ │ ├── placement.kubernetes-fleet.io_resourcesnapshots.yaml │ │ │ ├── placement.kubernetes-fleet.io_clusterapprovalrequests.yaml │ │ │ ├── placement.kubernetes-fleet.io_clusterresourcebindings.yaml │ │ │ ├── placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml │ │ │ ├── placement.kubernetes-fleet.io_stagedupdatestrategies.yaml │ │ │ ├── placement.kubernetes-fleet.io_clusterresourceenvelopes.yaml │ │ │ ├── placement.kubernetes-fleet.io_clusterresourceoverrides.yaml │ │ │ ├── placement.kubernetes-fleet.io_clusterresourcesnapshots.yaml │ │ │ ├── placement.kubernetes-fleet.io_resourceoverridesnapshots.yaml │ │ │ ├── placement.kubernetes-fleet.io_schedulingpolicysnapshots.yaml │ │ │ ├── placement.kubernetes-fleet.io_clusterstagedupdatestrategies.yaml │ │ │ ├── placement.kubernetes-fleet.io_clusterresourceoverridesnapshots.yaml │ │ │ ├── placement.kubernetes-fleet.io_clusterresourceplacementevictions.yaml │ │ │ ├── placement.kubernetes-fleet.io_clusterresourceplacementstatuses.yaml │ │ │ ├── placement.kubernetes-fleet.io_clusterschedulingpolicysnapshots.yaml │ │ │ ├── works.yaml │ │ │ ├── placement.kubernetes-fleet.io_clusterresourceplacementdisruptionbudgets.yaml │ │ │ ├── crps.yaml │ │ │ ├── memberclusters.yaml │ │ │ └── internalmemberclusters.yaml │ │ ├── serviceaccount.yaml │ │ ├── rbac.yaml │ │ ├── webhookservice.yaml │ │ └── _helpers.tpl │ ├── .helmignore │ ├── Chart.yaml │ └── values.yaml └── member-agent │ ├── templates │ ├── namespace.yaml │ ├── crds │ │ └── appliedworks.yaml │ ├── serviceaccount.yaml │ ├── cloudconfig.yaml │ ├── rbac.yaml │ └── _helpers.tpl │ ├── crdbases │ └── placement.kubernetes-fleet.io_appliedworks.yaml │ ├── .helmignore │ ├── Chart.yaml │ └── values.yaml ├── examples ├── kueue │ ├── create_jobs.sh │ ├── flavors.yaml │ ├── local-queue.yaml │ ├── cluster-queue.yaml │ ├── team-b-crp.yaml │ ├── team-a-crp.yaml │ ├── job-team-a.yaml │ └── job-team-b.yaml ├── nginx │ ├── namespace.yaml │ ├── service.yaml │ ├── place-all-crp.yaml │ ├── configMap.yaml │ ├── deployment.yaml │ └── config-ro.yaml ├── eviction │ ├── clusterpdb.yaml │ ├── eviction.yaml │ └── test-crp.yaml ├── test-crp-disruptionbudget.yaml ├── test-crp-eviction.yaml ├── fleet_v1beta1_membercluster.yaml ├── test-crp9.yaml ├── test-crp1.yaml ├── stagedupdaterun │ ├── example-crp.yaml │ ├── approvalRequest.yaml │ ├── updateStrategy.yaml │ └── clusterStagedUpdateRun.yaml ├── test-crp7.yaml ├── test-crp2.yaml ├── resourceplacement │ ├── rp-cm.yaml │ ├── test-crp.yaml │ └── rp-deploy.yaml ├── migration │ ├── resourceoverride.yaml │ ├── crp-1.yaml │ └── crp-2.yaml ├── test-crp8.yaml ├── test-crp6.yaml ├── envelopes │ ├── namespacescoped.yaml │ └── clusterscoped.yaml ├── test-crp4.yaml ├── test-cro1.yaml ├── test-ro1.yaml ├── test-crp3.yaml └── test-crp5.yaml ├── hack ├── loadtest │ ├── manifests │ │ ├── test_namespace.yaml │ │ ├── test-configmap.yaml │ │ ├── test_pdb.yaml │ │ ├── test-configmap-2.yaml │ │ ├── test-role.yaml │ │ ├── endpoints.yaml │ │ ├── endpoint-slice.yaml │ │ ├── test-secret.yaml │ │ ├── test_clusterrole.yaml │ │ ├── test-rolebinding.yaml │ │ └── test-service.yaml │ ├── test-crp.yaml │ ├── test-crp4.yaml │ ├── prometheus.yml │ ├── test-crp1.yaml │ ├── test-crp3.yaml │ └── test-crp2.yaml ├── Azure │ └── setup │ │ ├── prometheus.yaml │ │ └── labelMC.sh ├── cl2 │ ├── cleanup.sh │ ├── modules │ │ └── configmaps.yaml │ ├── manifests │ │ ├── test-crp.yaml │ │ └── test-configmap.yaml │ └── README.md ├── boilerplate.go.txt ├── go-install.sh └── membership │ └── cleanup.sh ├── docker ├── .dockerignore ├── hub-agent.Dockerfile ├── member-agent.Dockerfile └── refresh-token.Dockerfile ├── .github ├── dependabot.yml ├── workflows │ ├── markdown.links.config.json │ ├── pr-title-lint.yml │ ├── markdown-lint.yml │ ├── chart.yml │ ├── codespell.yml │ └── code-lint.yml ├── pr-title-config.json ├── ISSUE_TEMPLATE │ └── bug_report.md ├── PULL_REQUEST_TEMPLATE.md └── .copilot │ └── domain_knowledge │ └── cluster vs namespace scoped resources ├── MAINTAINERS.md ├── .pipeline └── component-governance-detection.yaml ├── SUPPORT.md ├── .gitignore ├── apis ├── cluster │ ├── v1 │ │ ├── doc.go │ │ └── groupversion_info.go │ └── v1beta1 │ │ ├── doc.go │ │ └── groupversion_info.go ├── placement │ ├── v1 │ │ ├── doc.go │ │ ├── groupversion_info.go │ │ └── commons.go │ ├── v1beta1 │ │ ├── doc.go │ │ └── groupversion_info.go │ └── v1alpha1 │ │ ├── doc.go │ │ └── groupversion_info.go └── interface.go ├── .golangci.yml ├── tools ├── utils │ └── common.go └── fleet │ └── main.go ├── DCO ├── SECURITY.md ├── CONTRIBUTING.md ├── ROADMAP.md └── config └── crd └── bases ├── placement.kubernetes-fleet.io_resourceenvelopes.yaml └── placement.kubernetes-fleet.io_clusterresourceenvelopes.yaml /test/scripts/snippets.sh: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cmd/memberagent/testdata/token: -------------------------------------------------------------------------------- 1 | this is a fake token -------------------------------------------------------------------------------- /.codespellignore: -------------------------------------------------------------------------------- 1 | aks 2 | AfterAll 3 | CROs 4 | NotIn 5 | fo 6 | allReady 7 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "apis/**/*" 3 | - "cmd/**/*" 4 | - "test/**/*" 5 | -------------------------------------------------------------------------------- /screenshots/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubefleet-dev/kubefleet/HEAD/screenshots/img.png -------------------------------------------------------------------------------- /screenshots/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubefleet-dev/kubefleet/HEAD/screenshots/img1.png -------------------------------------------------------------------------------- /screenshots/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubefleet-dev/kubefleet/HEAD/screenshots/img2.png -------------------------------------------------------------------------------- /pkg/utils/cloudconfig/azure/test/azure_config_nojson.txt: -------------------------------------------------------------------------------- 1 | This is an invalid json file for testing purposes. \n -------------------------------------------------------------------------------- /screenshots/cncf-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubefleet-dev/kubefleet/HEAD/screenshots/cncf-logo.png -------------------------------------------------------------------------------- /charts/hub-agent/crdbases/placement.kubernetes-fleet.io_works.yaml: -------------------------------------------------------------------------------- 1 | ../../../config/crd/bases/placement.kubernetes-fleet.io_works.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: {{ .Values.namespace }} 5 | -------------------------------------------------------------------------------- /charts/member-agent/templates/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: {{ .Values.namespace}} 5 | -------------------------------------------------------------------------------- /charts/hub-agent/crdbases/cluster.kubernetes-fleet.io_memberclusters.yaml: -------------------------------------------------------------------------------- 1 | ../../../config/crd/bases/cluster.kubernetes-fleet.io_memberclusters.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/multicluster.x-k8s.io_clusterprofiles.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/multicluster.x-k8s.io_clusterprofiles.yaml -------------------------------------------------------------------------------- /charts/member-agent/crdbases/placement.kubernetes-fleet.io_appliedworks.yaml: -------------------------------------------------------------------------------- 1 | ../../../config/crd/bases/placement.kubernetes-fleet.io_appliedworks.yaml -------------------------------------------------------------------------------- /examples/kueue/create_jobs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while : 4 | do 5 | kubectl create -f ${1} 6 | kubectl create -f ${2} 7 | sleep ${3:-10} 8 | done 9 | -------------------------------------------------------------------------------- /examples/nginx/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: test-afd 5 | labels: 6 | kubernetes-fleet.io/app: test 7 | -------------------------------------------------------------------------------- /charts/hub-agent/crdbases/cluster.kubernetes-fleet.io_internalmemberclusters.yaml: -------------------------------------------------------------------------------- 1 | ../../../config/crd/bases/cluster.kubernetes-fleet.io_internalmemberclusters.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_approvalrequests.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_approvalrequests.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_resourcebindings.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_resourcebindings.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_stagedupdateruns.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_stagedupdateruns.yaml -------------------------------------------------------------------------------- /hack/loadtest/manifests/test_namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: app 5 | labels: 6 | fleet.azure.com/name: test 7 | -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_resourceenvelopes.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_resourceenvelopes.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_resourceoverrides.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_resourceoverrides.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_resourceplacements.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_resourceplacements.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_resourcesnapshots.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_resourcesnapshots.yaml -------------------------------------------------------------------------------- /docker/.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | testbin/ 5 | -------------------------------------------------------------------------------- /charts/hub-agent/crdbases/placement.kubernetes-fleet.io_clusterresourceplacements.yaml: -------------------------------------------------------------------------------- 1 | ../../../config/crd/bases/placement.kubernetes-fleet.io_clusterresourceplacements.yaml -------------------------------------------------------------------------------- /pkg/controllers/rollout/manifests/test_namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: app 5 | labels: 6 | fleet.azure.com/name: test 7 | -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_clusterapprovalrequests.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_clusterapprovalrequests.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_clusterresourcebindings.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_clusterresourcebindings.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdateruns.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_stagedupdatestrategies.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_stagedupdatestrategies.yaml -------------------------------------------------------------------------------- /pkg/controllers/workgenerator/manifests/test_namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: app 5 | labels: 6 | fleet.azure.com/name: test 7 | -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_clusterresourceenvelopes.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_clusterresourceenvelopes.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_clusterresourceoverrides.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_clusterresourceoverrides.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_clusterresourcesnapshots.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_clusterresourcesnapshots.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_resourceoverridesnapshots.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_resourceoverridesnapshots.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_schedulingpolicysnapshots.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_schedulingpolicysnapshots.yaml -------------------------------------------------------------------------------- /test/e2e/resources/test-envelope-object.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ResourceEnvelope 3 | metadata: 4 | name: envelope-object 5 | namespace: app 6 | -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_clusterstagedupdatestrategies.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_clusterstagedupdatestrategies.yaml -------------------------------------------------------------------------------- /examples/kueue/flavors.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kueue.x-k8s.io/v1beta1 2 | kind: ResourceFlavor 3 | metadata: 4 | name: default-flavor # This ResourceFlavor will be used for all the resources 5 | -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_clusterresourceoverridesnapshots.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_clusterresourceoverridesnapshots.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_clusterresourceplacementevictions.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_clusterresourceplacementevictions.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_clusterresourceplacementstatuses.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_clusterresourceplacementstatuses.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_clusterschedulingpolicysnapshots.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_clusterschedulingpolicysnapshots.yaml -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/works.yaml: -------------------------------------------------------------------------------- 1 | {{ $files := .Files }} 2 | {{ if .Values.enableV1Beta1APIs }} 3 | {{ $files.Get "crdbases/placement.kubernetes-fleet.io_works.yaml" }} 4 | {{ end }} 5 | -------------------------------------------------------------------------------- /examples/eviction/clusterpdb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacementDisruptionBudget 3 | metadata: 4 | name: test-crp 5 | spec: 6 | minAvailable: 1 7 | -------------------------------------------------------------------------------- /hack/loadtest/manifests/test-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: test-configmap 5 | namespace: app 6 | data: 7 | fielda: one 8 | fieldb: two 9 | fieldc: three -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/placement.kubernetes-fleet.io_clusterresourceplacementdisruptionbudgets.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/placement.kubernetes-fleet.io_clusterresourceplacementdisruptionbudgets.yaml -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | commit-message: 8 | prefix: "chore" 9 | -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/crps.yaml: -------------------------------------------------------------------------------- 1 | {{ $files := .Files }} 2 | {{ if .Values.enableV1Beta1APIs }} 3 | {{ $files.Get "crdbases/placement.kubernetes-fleet.io_clusterresourceplacements.yaml" }} 4 | {{ end }} 5 | -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/memberclusters.yaml: -------------------------------------------------------------------------------- 1 | {{ $files := .Files }} 2 | {{ if .Values.enableV1Beta1APIs }} 3 | {{ $files.Get "crdbases/cluster.kubernetes-fleet.io_memberclusters.yaml" }} 4 | {{ end }} 5 | -------------------------------------------------------------------------------- /charts/member-agent/templates/crds/appliedworks.yaml: -------------------------------------------------------------------------------- 1 | {{ $files := .Files }} 2 | {{ if .Values.enableV1Beta1APIs }} 3 | {{ $files.Get "crdbases/placement.kubernetes-fleet.io_appliedworks.yaml" }} 4 | {{ end }} 5 | -------------------------------------------------------------------------------- /pkg/controllers/rollout/manifests/test-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: test-configmap 5 | namespace: app 6 | data: 7 | fielda: one 8 | fieldb: two 9 | fieldc: three -------------------------------------------------------------------------------- /pkg/controllers/workgenerator/manifests/test-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: test-configmap 5 | namespace: app 6 | data: 7 | fielda: one 8 | fieldb: two 9 | fieldc: three -------------------------------------------------------------------------------- /charts/hub-agent/templates/crds/internalmemberclusters.yaml: -------------------------------------------------------------------------------- 1 | {{ $files := .Files }} 2 | {{ if .Values.enableV1Beta1APIs }} 3 | {{ $files.Get "crdbases/cluster.kubernetes-fleet.io_internalmemberclusters.yaml" }} 4 | {{ end }} 5 | -------------------------------------------------------------------------------- /examples/test-crp-disruptionbudget.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1alpha1 2 | kind: ClusterResourcePlacementDisruptionBudget 3 | metadata: 4 | name: test-crp-disruption-budget 5 | spec: 6 | maxUnavailable: 1 7 | -------------------------------------------------------------------------------- /examples/eviction/eviction.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacementEviction 3 | metadata: 4 | name: test-eviction 5 | spec: 6 | placementName: test-crp 7 | clusterName: kind-cluster-1 8 | -------------------------------------------------------------------------------- /examples/test-crp-eviction.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1alpha1 2 | kind: ClusterResourcePlacementEviction 3 | metadata: 4 | name: test-crp-eviction 5 | spec: 6 | placementName: test-crp 7 | clusterName: cluster-1 8 | -------------------------------------------------------------------------------- /test/manifests/test-resource.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: test.kubernetes-fleet.io/v1alpha1 2 | kind: TestResource 3 | metadata: 4 | name: random-test-resource 5 | namespace: app 6 | spec: 7 | foo: foo1 8 | items: 9 | - a 10 | - b -------------------------------------------------------------------------------- /hack/loadtest/manifests/test_pdb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: policy/v1 2 | kind: PodDisruptionBudget 3 | metadata: 4 | name: zk-pdb 5 | namespace: app 6 | spec: 7 | minAvailable: 2 8 | selector: 9 | matchLabels: 10 | app: zookeeper -------------------------------------------------------------------------------- /test/e2e/resources/test-clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: pod-reader 5 | rules: 6 | - apiGroups: [""] 7 | resources: ["pods"] 8 | verbs: ["get", "list", "watch"] 9 | -------------------------------------------------------------------------------- /pkg/controllers/rollout/manifests/test_pdb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: policy/v1 2 | kind: PodDisruptionBudget 3 | metadata: 4 | name: zk-pdb 5 | namespace: app 6 | spec: 7 | minAvailable: 2 8 | selector: 9 | matchLabels: 10 | app: zookeeper -------------------------------------------------------------------------------- /hack/Azure/setup/prometheus.yaml: -------------------------------------------------------------------------------- 1 | prometheus: 2 | service: 3 | type: LoadBalancer 4 | prometheusSpec: 5 | additionalScrapeConfigs: 6 | - job_name: "fleet" 7 | static_configs: 8 | - targets: [":8080"] 9 | -------------------------------------------------------------------------------- /pkg/controllers/workgenerator/manifests/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: pod-reader 5 | rules: 6 | - apiGroups: [""] 7 | resources: ["pods"] 8 | verbs: ["get", "list", "watch"] 9 | -------------------------------------------------------------------------------- /pkg/controllers/workgenerator/manifests/test_pdb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: policy/v1 2 | kind: PodDisruptionBudget 3 | metadata: 4 | name: zk-pdb 5 | namespace: app 6 | spec: 7 | minAvailable: 2 8 | selector: 9 | matchLabels: 10 | app: zookeeper -------------------------------------------------------------------------------- /charts/hub-agent/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: {{ include "hub-agent.fullname" . }}-sa 5 | namespace: {{ .Values.namespace }} 6 | labels: 7 | {{- include "hub-agent.labels" . | nindent 4 }} 8 | -------------------------------------------------------------------------------- /hack/loadtest/manifests/test-configmap-2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: test-configmap-2 5 | namespace: app 6 | labels: 7 | fleet.azure.com/name: app 8 | data: 9 | field1: one 10 | field2: two 11 | field3: three -------------------------------------------------------------------------------- /.github/workflows/markdown.links.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "aliveStatusCodes": [ 3 | 200, 4 | 203, 5 | 0 6 | ], 7 | "timeout": "5s", 8 | "retryOn429": true, 9 | "retryCount": 5, 10 | "fallbackRetryDelay": "30s" 11 | } 12 | -------------------------------------------------------------------------------- /charts/member-agent/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: {{ include "member-agent.fullname" . }}-sa 5 | namespace: {{ .Values.namespace }} 6 | labels: 7 | {{- include "member-agent.labels" . | nindent 4 }} 8 | -------------------------------------------------------------------------------- /pkg/controllers/workgenerator/manifests/test-resource-overriden.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: test.kubernetes-fleet.io/v1alpha1 2 | kind: TestResource 3 | metadata: 4 | name: random-test-resource 5 | namespace: app 6 | spec: 7 | foo: foo2 8 | items: 9 | - a 10 | - b -------------------------------------------------------------------------------- /test/e2e/resources/resourcequota.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ResourceQuota 3 | metadata: 4 | name: mem-cpu-demo 5 | namespace: app 6 | spec: 7 | hard: 8 | requests.cpu: "2" 9 | requests.memory: 2Gi 10 | limits.cpu: "4" 11 | limits.memory: 4Gi 12 | -------------------------------------------------------------------------------- /test/e2e/resources/test-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: test-configmap 5 | namespace: app 6 | annotations: 7 | kubernetes-fleet.io/envelope-configmap: "true" 8 | data: 9 | fielda: one 10 | fieldb: two 11 | fieldc: three -------------------------------------------------------------------------------- /hack/loadtest/manifests/test-role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | namespace: app 5 | name: test-pod-reader 6 | rules: 7 | - apiGroups: [""] # "" indicates the core API group 8 | resources: ["pods"] 9 | verbs: ["get", "watch", "list"] -------------------------------------------------------------------------------- /test/e2e/resources/test-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: test-nginx 5 | namespace: app 6 | labels: 7 | run: test-nginx 8 | spec: 9 | ports: 10 | - port: 80 11 | protocol: TCP 12 | selector: 13 | run: test-nginx 14 | -------------------------------------------------------------------------------- /pkg/controllers/workgenerator/manifests/resourcequota.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ResourceQuota 3 | metadata: 4 | name: mem-cpu-demo 5 | namespace: app 6 | spec: 7 | hard: 8 | requests.cpu: "1" 9 | requests.memory: 1Gi 10 | limits.cpu: "2" 11 | limits.memory: 2Gi 12 | -------------------------------------------------------------------------------- /pkg/controllers/workgenerator/manifests/resourcequota2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ResourceQuota 3 | metadata: 4 | name: mem-cpu-demo 5 | namespace: app 6 | spec: 7 | hard: 8 | requests.cpu: "2" 9 | requests.memory: 2Gi 10 | limits.cpu: "4" 11 | limits.memory: 4Gi 12 | -------------------------------------------------------------------------------- /hack/loadtest/manifests/endpoints.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Endpoints 3 | metadata: 4 | name: user-created-endpoint 5 | namespace: app 6 | subsets: 7 | - addresses: 8 | - ip: 20.106.105.216 9 | ports: 10 | - name: https 11 | port: 443 12 | protocol: TCP 13 | -------------------------------------------------------------------------------- /hack/cl2/cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | : "${KUBECONFIG:?Environment variable KUBECONFIG must be set}" 8 | 9 | echo "Deleting all CRPs generated during the load test from the hub cluster..." 10 | kubectl delete crp -l test=cl2-test 11 | -------------------------------------------------------------------------------- /examples/fleet_v1beta1_membercluster.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cluster.kubernetes-fleet.io/v1beta1 2 | kind: MemberCluster 3 | metadata: 4 | name: kind-cluster-1 5 | spec: 6 | identity: 7 | name: fleet-member-agent-cluster-1 8 | kind: ServiceAccount 9 | namespace: fleet-system 10 | apiGroup: "" 11 | -------------------------------------------------------------------------------- /test/e2e/resources/test-job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: sleep 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - name: sleep 10 | image: alpine:3 11 | command: ["sh", "-c", "sleep 3"] 12 | restartPolicy: Never 13 | backoffLimit: 4 -------------------------------------------------------------------------------- /.github/pr-title-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "LABEL": { 3 | "name": "title-needs-formatting", 4 | "color": "EEEEEE" 5 | }, 6 | "CHECKS": { 7 | "prefixes": [ "[WIP] ", "feat: ", "test: ", "fix: ", "docs: ", "style: ", "interface: ", "util: ", "chore: ", "ci: ", "perf: ", "refactor: ", "revert: " ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /charts/member-agent/templates/cloudconfig.yaml: -------------------------------------------------------------------------------- 1 | {{- if eq .Values.propertyProvider "azure" }} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: cloud-config 6 | namespace: {{ .Values.namespace }} 7 | type: Opaque 8 | data: 9 | config.json: {{ .Values.config.azureCloudConfig | toJson | indent 4 | b64enc | quote }} 10 | {{- end }} 11 | -------------------------------------------------------------------------------- /examples/nginx/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: internal-app 5 | namespace: test-afd 6 | annotations: 7 | service.beta.kubernetes.io/azure-load-balancer-internal: "true" 8 | service.beta.kubernetes.io/azure-pls-create: "true" 9 | spec: 10 | type: LoadBalancer 11 | ports: 12 | - port: 80 13 | selector: 14 | app: internal-app 15 | -------------------------------------------------------------------------------- /examples/test-crp9.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: crp-9 5 | spec: 6 | resourceSelectors: 7 | - group: "" 8 | kind: Namespace 9 | name: test-ns 10 | version: v1 11 | policy: 12 | placementType: PickAll 13 | strategy: 14 | type: RollingUpdate 15 | statusReportingScope: NamespaceAccessible 16 | -------------------------------------------------------------------------------- /test/e2e/kindconfigs/cluster-1.yaml: -------------------------------------------------------------------------------- 1 | # Important: due to kind limitations 2 | # (kind node always has resource capacity == host resource capacity) and the way our planned test 3 | # cases (resource and non-resource properties) are designed, modification of this setup might lead 4 | # to test failures. 5 | kind: Cluster 6 | apiVersion: kind.x-k8s.io/v1alpha4 7 | nodes: 8 | - role: control-plane 9 | - role: worker 10 | -------------------------------------------------------------------------------- /charts/hub-agent/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | kind: ClusterRoleBinding 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: {{ include "hub-agent.fullname" . }}-role-binding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: cluster-admin 9 | subjects: 10 | - kind: ServiceAccount 11 | name: {{ include "hub-agent.fullname" . }}-sa 12 | namespace: {{ .Values.namespace }} -------------------------------------------------------------------------------- /examples/test-crp1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: crp-1 5 | spec: 6 | resourceSelectors: 7 | - group: apiextensions.k8s.io 8 | kind: CustomResourceDefinition 9 | name: testresources.test.kubernetes-fleet.io 10 | version: v1 11 | policy: 12 | placementType: PickN 13 | strategy: 14 | type: RollingUpdate -------------------------------------------------------------------------------- /hack/cl2/modules/configmaps.yaml: -------------------------------------------------------------------------------- 1 | {{$namespaces := .namespaceCount}} 2 | {{$count := .configmapCount}} 3 | 4 | steps: 5 | - name: Create ConfigMaps 6 | phases: 7 | - namespaceRange: 8 | min: 1 9 | max: {{$namespaces}} 10 | replicasPerNamespace: {{$count}} 11 | tuningSet: SteppedLoad 12 | objectBundle: 13 | - basename: test-configmap 14 | objectTemplatePath: "manifests/test-configmap.yaml" 15 | -------------------------------------------------------------------------------- /test/e2e/kindconfigs/cluster-2.yaml: -------------------------------------------------------------------------------- 1 | # Important: due to kind limitations 2 | # (kind node always has resource capacity == host resource capacity) and the way our planned test 3 | # cases (resource and non-resource properties) are designed, modification of this setup might lead 4 | # to test failures. 5 | kind: Cluster 6 | apiVersion: kind.x-k8s.io/v1alpha4 7 | nodes: 8 | - role: control-plane 9 | - role: worker 10 | - role: worker 11 | -------------------------------------------------------------------------------- /hack/loadtest/manifests/endpoint-slice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: discovery.k8s.io/v1 2 | kind: EndpointSlice 3 | metadata: 4 | labels: 5 | service-name: test-nginx-export 6 | name: test-user-created-endpointslice 7 | namespace: app 8 | addressType: IPv4 9 | ports: 10 | - name: https 11 | port: 443 12 | protocol: TCP 13 | endpoints: 14 | - addresses: 15 | - 20.106.105.216 16 | conditions: 17 | ready: true 18 | -------------------------------------------------------------------------------- /pkg/utils/cloudconfig/azure/test/azure_invalid_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "cloud": "AzurePublicCloud", 3 | "tenantId": "00000000-0000-0000-0000-000000000000", 4 | "subscriptionId": "00000000-0000-0000-0000-000000000000", 5 | "useManagedIdentityExtension": false, 6 | "aadClientId": "00000000-0000-0000-0000-000000000000", 7 | "resourceGroup": " test-rg ", 8 | "location": " eastus ", 9 | "vnetName": "test -vnet" 10 | } 11 | -------------------------------------------------------------------------------- /test/e2e/azure_valid_config.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | azureCloudConfig: 3 | cloud: "AzurePublicCloud" 4 | userAgent: "fleet-member-agent" 5 | tenantId: "00000000-0000-0000-0000-000000000000" 6 | subscriptionId: "00000000-0000-0000-0000-000000000000" 7 | useManagedIdentityExtension: true 8 | location: "westus" 9 | vnetName: "test-vnet" 10 | vnetResourceGroup: "test-rg" 11 | resourceGroup: "test-rg" 12 | -------------------------------------------------------------------------------- /charts/member-agent/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | kind: ClusterRoleBinding 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: {{ include "member-agent.fullname" . }}-cluster-admin-binding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: cluster-admin 9 | subjects: 10 | - kind: ServiceAccount 11 | name: {{ include "member-agent.fullname" . }}-sa 12 | namespace: {{.Values.namespace}} 13 | -------------------------------------------------------------------------------- /test/e2e/kindconfigs/cluster-3.yaml: -------------------------------------------------------------------------------- 1 | # Important: due to kind limitations 2 | # (kind node always has resource capacity == host resource capacity) and the way our planned test 3 | # cases (resource and non-resource properties) are designed, modification of this setup might lead 4 | # to test failures. 5 | kind: Cluster 6 | apiVersion: kind.x-k8s.io/v1alpha4 7 | nodes: 8 | - role: control-plane 9 | - role: worker 10 | - role: worker 11 | - role: worker 12 | -------------------------------------------------------------------------------- /hack/loadtest/manifests/test-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: test-secret 5 | namespace: app 6 | data: 7 | somekey: Q2xpZW50SWQ6IDUxOTEwNTY4LTM0YzktNGQ0ZS1iODA1LTNmNTY3NWQyMDdiYwpDbGllbnRTZWNyZXQ6IDZSLThRfkJvSDNNYm1+eGJpaDhmNVZibHBkWGxzeGQyRnp+WXhjWjYKVGVuYW50SWQ6IDcyZjk4OGJmLTg2ZjEtNDFhZi05MWFiLTJkN2NkMDExZGI0NwpTdWJzY3JpcHRpb25JZDogMmIwM2JmYjgtZTg4NS00NTY2LWE2MmEtOTA5YTExZDcxNjkyClJlc291cmNlR3JvdXA6IGNhcmF2ZWwtZGVtbw== 8 | type: generic -------------------------------------------------------------------------------- /charts/hub-agent/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /charts/member-agent/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /examples/nginx/place-all-crp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: afd-crp 5 | spec: 6 | resourceSelectors: 7 | - group: "" 8 | kind: Namespace 9 | name: test-afd 10 | version: v1 11 | policy: 12 | placementType: PickAll 13 | strategy: 14 | type: RollingUpdate 15 | rollingUpdate: 16 | maxUnavailable: 2 17 | maxSurge: 25% 18 | unavailablePeriodSeconds: 60 19 | -------------------------------------------------------------------------------- /examples/stagedupdaterun/example-crp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: example-placement 5 | spec: 6 | resourceSelectors: 7 | - group: "" 8 | kind: Namespace 9 | name: test-namespace 10 | version: v1 11 | policy: 12 | placementType: PickAll 13 | tolerations: 14 | - key: gpu-workload 15 | operator: Exists 16 | strategy: 17 | type: External 18 | -------------------------------------------------------------------------------- /examples/kueue/local-queue.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kueue.x-k8s.io/v1beta1 2 | kind: LocalQueue 3 | metadata: 4 | namespace: team-a # LocalQueue under team-a namespace 5 | name: lq-team-a 6 | spec: 7 | clusterQueue: cluster-queue # Point to the ClusterQueue 8 | --- 9 | apiVersion: kueue.x-k8s.io/v1beta1 10 | kind: LocalQueue 11 | metadata: 12 | namespace: team-b # LocalQueue under team-b namespace 13 | name: lq-team-b 14 | spec: 15 | clusterQueue: cluster-queue # Point to the ClusterQueue 16 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # The KubeFleet Maintainers 2 | 3 | | Maintainer | Organization | GitHub Username | 4 | |-------------|--------------|-------------------------------------------------------| 5 | | Ryan Zhang | Microsoft | [@ryanzhang-oss](https://github.com/ryanzhang-oss) | 6 | | Zhiying Lin | Microsoft | [@zhiying-lin](https://github.com/zhiying-lin) | 7 | | Chen Yu | Microsoft | [@michaelawyu](https://github.com/michaelawyu) | 8 | -------------------------------------------------------------------------------- /examples/stagedupdaterun/approvalRequest.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterApprovalRequest 3 | metadata: 4 | name: example-run-canary 5 | labels: 6 | kubernetes-fleet.io/targetupdaterun: example-run 7 | kubernetes-fleet.io/targetUpdatingStage: canary 8 | kubernetes-fleet.io/isLatestUpdateRunApproval: "true" 9 | spec: 10 | parentStageRollout: example-run 11 | targetStage: canary 12 | status: 13 | conditions: 14 | - type: Approved 15 | status: "True" 16 | -------------------------------------------------------------------------------- /.github/workflows/pr-title-lint.yml: -------------------------------------------------------------------------------- 1 | name: PR Title Checker 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | - edited 7 | - synchronize 8 | - labeled 9 | - unlabeled 10 | 11 | jobs: 12 | check: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: thehanimo/pr-title-checker@v1.4.3 16 | with: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | pass_on_octokit_error: true 19 | configuration_path: ".github/pr-title-config.json" 20 | -------------------------------------------------------------------------------- /hack/loadtest/manifests/test_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: test-cluster-role 5 | labels: 6 | fleet.azure.com/name: test 7 | rules: 8 | - apiGroups: [""] 9 | resources: ["secrets"] 10 | verbs: ["get", "list", "watch"] 11 | - apiGroups: [ "" ] 12 | resources: [ "events" ] 13 | verbs: [ "get", "list", "watch", "create", "patch" ] 14 | - apiGroups: [ "" ] 15 | resources: ["nodes"] 16 | verbs: [ "get", "list", "watch"] -------------------------------------------------------------------------------- /pkg/utils/cloudconfig/azure/test/azure_valid_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "cloud": "AzurePublicCloud", 3 | "userAgent": "fleet-member-agent", 4 | "tenantId": "00000000-0000-0000-0000-000000000000", 5 | "subscriptionId": "00000000-0000-0000-0000-000000000000", 6 | "useManagedIdentityExtension": true, 7 | "userAssignedIdentityID": "11111111-1111-1111-1111-111111111111", 8 | "resourceGroup": "test-rg", 9 | "location": "eastus", 10 | "vnetName": "test-vnet", 11 | "vnetResourceGroup": "test-rg" 12 | } 13 | -------------------------------------------------------------------------------- /.pipeline/component-governance-detection.yaml: -------------------------------------------------------------------------------- 1 | # This pipeline hosted in ADO will use the auto-injected component detection build task to detect possible incidents 2 | # and report alerts related to OSS consumed by this repository. 3 | trigger: 4 | branches: 5 | include: 6 | - main 7 | paths: 8 | include: 9 | - go.sum 10 | - go.mod 11 | 12 | pool: 13 | vmImage: ubuntu-latest 14 | 15 | steps: 16 | - bash: | 17 | echo "This task is used to trigger code base scan." 18 | displayName: ADO Task 19 | -------------------------------------------------------------------------------- /hack/loadtest/test-crp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: crp 5 | spec: 6 | resourceSelectors: 7 | - group: apiextensions.k8s.io 8 | kind: CustomResourceDefinition 9 | name: testresources.test.kubernetes-fleet.io 10 | version: v1 11 | strategy: 12 | type: RollingUpdate 13 | rollingUpdate: 14 | maxUnavailable: 25% 15 | maxSurge: 25% 16 | unavailablePeriodSeconds: 60 17 | revisionHistoryLimit: 15 18 | -------------------------------------------------------------------------------- /test/e2e/stop.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script is provided as a utility to easily stop the test environment when running the 4 | # suites locally. 5 | 6 | set -o errexit 7 | set -o nounset 8 | set -o pipefail 9 | 10 | HUB_CLUSTER="hub" 11 | MEMBER_CLUSTER_COUNT=$1 12 | declare -a ALL_CLUSTERS=($HUB_CLUSTER) 13 | 14 | for (( i=1;i<=MEMBER_CLUSTER_COUNT;i++ )) 15 | do 16 | ALL_CLUSTERS+=("cluster-$i") 17 | done 18 | 19 | for i in "${ALL_CLUSTERS[@]}" 20 | do 21 | kind delete cluster --name "$i" 22 | done 23 | -------------------------------------------------------------------------------- /test/upgrade/stop.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script is provided as a utility to easily stop the test environment when running the 4 | # suites locally. 5 | 6 | set -o errexit 7 | set -o nounset 8 | set -o pipefail 9 | 10 | HUB_CLUSTER="hub" 11 | MEMBER_CLUSTER_COUNT=$1 12 | declare -a ALL_CLUSTERS=($HUB_CLUSTER) 13 | 14 | for (( i=1;i<=MEMBER_CLUSTER_COUNT;i++ )) 15 | do 16 | ALL_CLUSTERS+=("cluster-$i") 17 | done 18 | 19 | for i in "${ALL_CLUSTERS[@]}" 20 | do 21 | kind delete cluster --name "$i" 22 | done 23 | -------------------------------------------------------------------------------- /pkg/controllers/workgenerator/manifests/test-resource-envelope.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ResourceEnvelope 3 | metadata: 4 | name: namespaced-resource-envelope 5 | namespace: app 6 | data: 7 | "resourceQuota.yaml": 8 | apiVersion: v1 9 | kind: ResourceQuota 10 | metadata: 11 | name: mem-cpu-demo 12 | namespace: app 13 | spec: 14 | hard: 15 | requests.cpu: "1" 16 | requests.memory: 1Gi 17 | limits.cpu: "2" 18 | limits.memory: 2Gi 19 | -------------------------------------------------------------------------------- /pkg/controllers/workgenerator/manifests/test-resource-envelope2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ResourceEnvelope 3 | metadata: 4 | name: namespaced-resource-envelope 5 | namespace: app 6 | data: 7 | "resourceQuota.yaml": 8 | apiVersion: v1 9 | kind: ResourceQuota 10 | metadata: 11 | name: mem-cpu-demo 12 | namespace: app 13 | spec: 14 | hard: 15 | requests.cpu: "2" 16 | requests.memory: 2Gi 17 | limits.cpu: "4" 18 | limits.memory: 4Gi 19 | -------------------------------------------------------------------------------- /pkg/controllers/workgenerator/manifests/webhook.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: admissionregistration.k8s.io/v1 2 | kind: ValidatingWebhookConfiguration 3 | metadata: 4 | name: guard 5 | webhooks: 6 | - name: guard.example.com 7 | rules: 8 | - operations: ["CREATE"] 9 | apiGroups: ["*"] 10 | apiVersions: ["*"] 11 | resources: ["*"] 12 | clientConfig: 13 | service: 14 | name: guard 15 | namespace: ops 16 | admissionReviewVersions: ["v1"] 17 | sideEffects: None 18 | timeoutSeconds: 10 19 | -------------------------------------------------------------------------------- /test/e2e/resources/test-envelope-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: envelope-configmap 5 | namespace: app 6 | annotations: 7 | kubernetes-fleet.io/envelope-configmap: "true" 8 | data: 9 | resourceQuota.yaml: | 10 | apiVersion: v1 11 | kind: ResourceQuota 12 | metadata: 13 | name: mem-cpu-demo 14 | namespace: app 15 | spec: 16 | hard: 17 | requests.cpu: "1" 18 | requests.memory: 1Gi 19 | limits.cpu: "2" 20 | limits.memory: 2Gi 21 | -------------------------------------------------------------------------------- /hack/cl2/manifests/test-crp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: {{.Name}} 5 | labels: 6 | test: cl2-test 7 | spec: 8 | resourceSelectors: 9 | - group: "" 10 | kind: Namespace 11 | name: busy-cluster-test-ns-{{AddInt .Index 1}} 12 | version: v1 13 | strategy: 14 | type: RollingUpdate 15 | rollingUpdate: 16 | maxUnavailable: 25% 17 | maxSurge: 25% 18 | unavailablePeriodSeconds: 60 19 | revisionHistoryLimit: 15 20 | -------------------------------------------------------------------------------- /examples/test-crp7.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: crp-7 5 | spec: 6 | resourceSelectors: 7 | - group: "" 8 | kind: Namespace 9 | name: test-namespace 10 | version: v1 11 | policy: 12 | placementType: PickAll 13 | affinity: 14 | clusterAffinity: 15 | requiredDuringSchedulingIgnoredDuringExecution: 16 | clusterSelectorTerms: 17 | - labelSelector: 18 | matchLabels: 19 | test-key: test-value2 -------------------------------------------------------------------------------- /examples/test-crp2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: crp-2 5 | spec: 6 | resourceSelectors: 7 | - group: apiextensions.k8s.io 8 | kind: CustomResourceDefinition 9 | name: testresources.test.kubernetes-fleet.io 10 | version: v1 11 | policy: 12 | placementType: PickN 13 | numberOfClusters: 3 14 | topologySpreadConstraints: 15 | - maxSkew: 1 16 | topologyKey: color 17 | whenUnsatisfiable: DoNotSchedule 18 | strategy: 19 | type: RollingUpdate -------------------------------------------------------------------------------- /test/e2e/resources/test-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: test-deploy 5 | namespace: default 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: test-deploy 10 | replicas: 2 11 | template: 12 | metadata: 13 | labels: 14 | app: test-deploy 15 | spec: 16 | containers: 17 | - name: pause 18 | image: k8s.gcr.io/pause:3.8 19 | resources: 20 | requests: 21 | cpu: 1m 22 | memory: 2Mi 23 | limits: 24 | cpu: 100m 25 | memory: 200Mi 26 | -------------------------------------------------------------------------------- /.github/workflows/markdown-lint.yml: -------------------------------------------------------------------------------- 1 | name: Markdown Link Checker 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.md' 7 | - "docs/**" 8 | 9 | jobs: 10 | markdown-link-check: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v6.0.1 14 | - uses: tcort/github-action-markdown-link-check@v1 15 | with: 16 | # this will only show errors in the output 17 | use-quiet-mode: 'yes' 18 | # this will show detailed HTTP status for checked links 19 | use-verbose-mode: 'yes' 20 | config-file: '.github/workflows/markdown.links.config.json' 21 | -------------------------------------------------------------------------------- /charts/hub-agent/templates/webhookservice.yaml: -------------------------------------------------------------------------------- 1 | # The webhook will normally use a service reference with a cluster assigned IP. 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | labels: 6 | {{- include "hub-agent.labels" . | nindent 4 }} 7 | name: fleetwebhook 8 | namespace: {{ .Values.namespace }} 9 | spec: 10 | ipFamilies: 11 | - IPv4 12 | ipFamilyPolicy: SingleStack 13 | ports: 14 | - name: client 15 | port: 9443 16 | protocol: TCP 17 | targetPort: 9443 18 | selector: 19 | {{- include "hub-agent.selectorLabels" . | nindent 4 }} 20 | sessionAffinity: None 21 | type: ClusterIP 22 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /examples/resourceplacement/rp-cm.yaml: -------------------------------------------------------------------------------- 1 | # This tests selecting a single resource in a namespace, 2 | # and applying it to all clusters. 3 | # Prerequisite: create a configMap named "test-cm" in namespace "test-ns". 4 | apiVersion: placement.kubernetes-fleet.io/v1beta1 5 | kind: ResourcePlacement 6 | metadata: 7 | name: rp-cm 8 | namespace: test-ns 9 | spec: 10 | resourceSelectors: 11 | - group: "" 12 | kind: ConfigMap 13 | name: test-cm 14 | version: v1 15 | policy: 16 | placementType: PickAll 17 | strategy: 18 | type: RollingUpdate 19 | rollingUpdate: 20 | maxUnavailable: 1 21 | maxSurge: 1 22 | -------------------------------------------------------------------------------- /examples/nginx/configMap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: index 5 | namespace: test-afd 6 | data: 7 | v1.html: | 8 | 9 | 10 | 11 | 12 | Docker Nginx 13 | 14 | 15 |

Hello from Nginx container v1

16 | 17 | 18 | v2.html: | 19 | 20 | 21 | 22 | 23 | Fleet Nginx 24 | 25 | 26 |

Hello from Nginx container v2

27 | 28 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 6 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 7 | feature request as a new Issue. 8 | 9 | For help and questions about using this project, please 10 | 11 | * start the conversation in the [GitHub Discussions](https://github.com/kubefleet-dev/kubefleet/discussions/). 12 | 13 | We are actively exploring other means for developers, system admins, and anyone who has an interest 14 | in the multi-cluster domain to engage with us. Please stay tuned. -------------------------------------------------------------------------------- /examples/resourceplacement/test-crp.yaml: -------------------------------------------------------------------------------- 1 | # This tests a CRP selecting a namespace only. 2 | # Prerequisite: create a namespace named "test-ns". 3 | apiVersion: placement.kubernetes-fleet.io/v1beta1 4 | kind: ClusterResourcePlacement 5 | metadata: 6 | name: ns-only-crp 7 | spec: 8 | resourceSelectors: 9 | - group: "" 10 | kind: Namespace 11 | name: test-ns 12 | version: v1 13 | selectionScope: NamespaceOnly # only namespace itself is placed, no resources within the namespace 14 | policy: 15 | placementType: PickAll 16 | strategy: 17 | type: RollingUpdate 18 | rollingUpdate: 19 | maxUnavailable: 1 20 | maxSurge: 1 21 | -------------------------------------------------------------------------------- /examples/migration/resourceoverride.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1alpha1 2 | kind: ResourceOverride 3 | metadata: 4 | name: ro-1 5 | namespace: test-namespace 6 | spec: 7 | policy: 8 | overrideRules: 9 | - clusterSelector: 10 | clusterSelectorTerms: 11 | - labelSelector: 12 | matchLabels: 13 | fleet.azure.com/location: westeurope 14 | jsonPatchOverrides: 15 | - op: replace 16 | path: /spec/replicas 17 | value: 4 18 | resourceSelectors: 19 | - group: apps 20 | kind: Deployment 21 | name: nginx-deployment 22 | version: v1 23 | -------------------------------------------------------------------------------- /test/e2e/resources/test-daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: test-ds 5 | namespace: test-ns 6 | labels: 7 | k8s-app: test-ds 8 | spec: 9 | selector: 10 | matchLabels: 11 | name: test-ds 12 | template: 13 | metadata: 14 | labels: 15 | name: test-ds 16 | spec: 17 | containers: 18 | - name: pause 19 | image: k8s.gcr.io/pause:3.8 20 | resources: 21 | limits: 22 | cpu: 100m 23 | memory: 200Mi 24 | requests: 25 | cpu: 1m 26 | memory: 1Mi 27 | terminationGracePeriodSeconds: 30 28 | -------------------------------------------------------------------------------- /examples/kueue/cluster-queue.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kueue.x-k8s.io/v1beta1 2 | kind: ClusterQueue 3 | metadata: 4 | name: cluster-queue 5 | spec: 6 | namespaceSelector: {} # Available to all namespaces 7 | queueingStrategy: BestEffortFIFO # Default queueing strategy 8 | resourceGroups: 9 | - coveredResources: ["cpu", "memory", "nvidia.com/gpu", "ephemeral-storage"] 10 | flavors: 11 | - name: "default-flavor" 12 | resources: 13 | - name: "cpu" 14 | nominalQuota: 10 15 | - name: "memory" 16 | nominalQuota: 10Gi 17 | - name: "nvidia.com/gpu" 18 | nominalQuota: 10 19 | - name: "ephemeral-storage" 20 | nominalQuota: 10Gi 21 | -------------------------------------------------------------------------------- /hack/Azure/setup/labelMC.sh: -------------------------------------------------------------------------------- 1 | # CAN ONLY BE RUN AFTER CREATING NEEDED AKS CLUSTERS AND HUB CLUSTER. This script add labels to specified number of 2 | # member clusters at random. 3 | 4 | label=$1 5 | count=$2 6 | 7 | # Get the list of member clusters 8 | clusters=($(kubectl get memberclusters -A -o jsonpath='{.items[*].metadata.name}')) 9 | 10 | # Shuffle the indices 11 | shuffled_indices=($(shuf -i 0-$((${#clusters[@]} - 1)))) 12 | 13 | # Label the specified number of clusters 14 | for index in "${shuffled_indices[@]:0:$count}"; do 15 | cluster=${clusters[$index]} 16 | kubectl label membercluster "$cluster" "$label" --overwrite 17 | echo "Labeled $cluster with label: $label" 18 | done 19 | -------------------------------------------------------------------------------- /examples/nginx/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: test-nginx 5 | namespace: test-afd 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: internal-app 10 | replicas: 2 11 | template: 12 | metadata: 13 | labels: 14 | app: internal-app 15 | spec: 16 | containers: 17 | - name: nginx 18 | image: nginx:1.14.2 19 | ports: 20 | - containerPort: 80 21 | volumeMounts: 22 | - mountPath: /usr/share/nginx/html/index.html 23 | name: cfgmap 24 | subPath: v1.html 25 | volumes: 26 | - name: cfgmap 27 | configMap: 28 | name: index 29 | -------------------------------------------------------------------------------- /examples/nginx/config-ro.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1alpha1 2 | kind: ResourceOverride 3 | metadata: 4 | name: config-ro 5 | namespace: test-afd 6 | spec: 7 | resourceSelectors: 8 | - group: "apps" 9 | kind: Deployment 10 | version: v1 11 | name: test-nginx 12 | policy: 13 | overrideRules: 14 | - clusterSelector: 15 | clusterSelectorTerms: 16 | - labelSelector: 17 | matchLabels: 18 | fleet.azure.com/location: eastus 19 | jsonPatchOverrides: 20 | - op: replace 21 | path: /spec/template/spec/containers/0/volumeMounts/0/subPath 22 | value: v2.html 23 | -------------------------------------------------------------------------------- /test/e2e/resources/statefulset-with-storage.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: test-ss 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: test-ss 9 | serviceName: "test-ss-svc" 10 | replicas: 2 11 | template: 12 | metadata: 13 | labels: 14 | app: test-ss 15 | spec: 16 | terminationGracePeriodSeconds: 10 17 | containers: 18 | - name: pause 19 | image: k8s.gcr.io/pause:3.8 20 | volumeClaimTemplates: 21 | - metadata: 22 | name: test-ss-pvc 23 | spec: 24 | accessModes: [ "ReadWriteOnce" ] 25 | storageClassName: "standard" 26 | resources: 27 | requests: 28 | storage: 100Mi 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | vendor/ 16 | 17 | # Kubernetes Generated files - skip generated files, except for vendored files 18 | !vendor/**/zz_generated.* 19 | 20 | # binary output 21 | bin/ 22 | hack/tools/bin/ 23 | 24 | # cover profile 25 | coverage.xml 26 | it-coverage.xml 27 | ut-coverage.xml 28 | 29 | # editor and IDE paraphernalia 30 | .idea 31 | .DS_Store 32 | *.swp 33 | *.swo 34 | *~ 35 | 36 | .vscode/ 37 | .qoder/ 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | ### **Environment** 14 | Please provide the following: 15 | - Hub cluster details 16 | - Member cluster details 17 | 18 | ### **To Reproduce** 19 | Steps to reproduce the behavior: 20 | 21 | ### **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | ### **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | ### **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /hack/loadtest/manifests/test-rolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | # This role binding allows "jane" to read pods in the "default" namespace. 3 | # You need to already have a Role named "pod-reader" in that namespace. 4 | kind: RoleBinding 5 | metadata: 6 | name: read-pods 7 | namespace: app 8 | subjects: 9 | # You can specify more than one "subject" 10 | - kind: User 11 | name: jane # "name" is case sensitive 12 | apiGroup: rbac.authorization.k8s.io 13 | roleRef: 14 | # "roleRef" specifies the binding to a Role / ClusterRole 15 | kind: Role #this must be Role or ClusterRole 16 | apiGroup: rbac.authorization.k8s.io 17 | name: test-pod-reader # this must match the name of the Role or ClusterRole you wish to bind to -------------------------------------------------------------------------------- /hack/loadtest/test-crp4.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: crp 5 | spec: 6 | resourceSelectors: 7 | - group: apiextensions.k8s.io 8 | kind: CustomResourceDefinition 9 | name: testresources.test.kubernetes-fleet.io 10 | version: v1 11 | policy: 12 | placementType: PickN 13 | numberOfClusters: 20 14 | topologySpreadConstraints: 15 | - maxSkew: 2 16 | topologyKey: env 17 | whenUnsatisfiable: DoNotSchedule 18 | strategy: 19 | type: RollingUpdate 20 | rollingUpdate: 21 | maxUnavailable: 100% 22 | maxSurge: 25% 23 | unavailablePeriodSeconds: 15 24 | revisionHistoryLimit: 15 25 | -------------------------------------------------------------------------------- /.github/workflows/chart.yml: -------------------------------------------------------------------------------- 1 | name: Helm Chart Publisher 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - ".github/workflows/chart.yaml" 9 | - "charts/**" 10 | create: 11 | # Publish semver tags as releases. 12 | tags: [ 'v*.*.*' ] 13 | 14 | permissions: 15 | contents: write 16 | 17 | jobs: 18 | deploy: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v6.0.1 22 | with: 23 | submodules: true 24 | fetch-depth: 0 25 | - name: Publish Helm chart 26 | uses: stefanprodan/helm-gh-pages@v1.7.0 27 | with: 28 | token: ${{ secrets.GITHUB_TOKEN }} 29 | charts_dir: charts 30 | target_dir: charts 31 | linting: off 32 | -------------------------------------------------------------------------------- /hack/loadtest/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s # By default, scrape targets every 15 seconds. 3 | 4 | # Attach these labels to any time series or alerts when communicating with 5 | # external systems (federation, remote storage, Alertmanager). 6 | external_labels: 7 | monitor: 'fleet-monitor' 8 | 9 | # A scrape configuration containing exactly one endpoint to scrape: 10 | # Here it's fleet load test. 11 | scrape_configs: 12 | # The job name is added as a label `job=` to any timeseries scraped from this config. 13 | - job_name: 'fleet-load-test' 14 | 15 | # Override the global default and scrape targets from this job every 5 seconds. 16 | scrape_interval: 5s 17 | 18 | static_configs: 19 | - targets: ['localhost:4848'] 20 | -------------------------------------------------------------------------------- /examples/test-crp8.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: crp-8 5 | spec: 6 | resourceSelectors: 7 | - group: apiextensions.k8s.io 8 | kind: CustomResourceDefinition 9 | name: testresources.test.kubernetes-fleet.io 10 | version: v1 11 | policy: 12 | placementType: PickN 13 | numberOfClusters: 3 14 | tolerations: 15 | - key: test-key1 16 | operator: Exists 17 | affinity: 18 | clusterAffinity: 19 | requiredDuringSchedulingIgnoredDuringExecution: 20 | clusterSelectorTerms: 21 | - labelSelector: 22 | matchExpressions: 23 | - key: taint 24 | operator: Exists -------------------------------------------------------------------------------- /examples/test-crp6.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: crp-6 5 | spec: 6 | resourceSelectors: 7 | - group: apiextensions.k8s.io 8 | kind: CustomResourceDefinition 9 | name: testresources.test.kubernetes-fleet.io 10 | version: v1 11 | policy: 12 | placementType: PickAll 13 | affinity: 14 | clusterAffinity: 15 | requiredDuringSchedulingIgnoredDuringExecution: 16 | clusterSelectorTerms: 17 | - labelSelector: 18 | matchLabels: 19 | color: blue 20 | - labelSelector: 21 | matchLabels: 22 | test-key: test-value2 23 | strategy: 24 | type: RollingUpdate -------------------------------------------------------------------------------- /examples/envelopes/namespacescoped.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ResourceEnvelope 3 | metadata: 4 | name: example 5 | namespace: app 6 | data: 7 | "cm.yaml": 8 | apiVersion: v1 9 | kind: ConfigMap 10 | metadata: 11 | name: config 12 | namespace: app 13 | data: 14 | foo: bar 15 | "deploy.yaml": 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: ingress 20 | namespace: app 21 | spec: 22 | replicas: 1 23 | selector: 24 | matchLabels: 25 | app: nginx 26 | template: 27 | metadata: 28 | labels: 29 | app: nginx 30 | spec: 31 | containers: 32 | - name: web 33 | image: nginx 34 | -------------------------------------------------------------------------------- /test/e2e/resources/statefulset-basic.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: test-ss 5 | namespace: test-ns 6 | labels: 7 | test-key: test-value 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: test-ss # has to match .spec.template.metadata.labels 12 | serviceName: "test-ss-svc" 13 | replicas: 3 # by default is 1 14 | template: 15 | metadata: 16 | labels: 17 | app: test-ss # has to match .spec.selector.matchLabels 18 | spec: 19 | containers: 20 | - name: pause 21 | image: k8s.gcr.io/pause:3.8 22 | resources: 23 | requests: 24 | cpu: 1m 25 | memory: 2Mi 26 | limits: 27 | cpu: 100m 28 | memory: 200Mi 29 | 30 | -------------------------------------------------------------------------------- /test/e2e/resources/webhook.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: admissionregistration.k8s.io/v1 2 | kind: MutatingWebhookConfiguration 3 | metadata: 4 | labels: 5 | azure-workload-identity.io/system: "true" 6 | name: azure-wi-webhook-mutating-webhook-configuration 7 | webhooks: 8 | - admissionReviewVersions: 9 | - v1 10 | - v1beta1 11 | clientConfig: 12 | service: 13 | name: azure-wi-webhook-webhook-service 14 | namespace: app 15 | path: /mutate-v1-pod 16 | failurePolicy: Ignore 17 | matchPolicy: Equivalent 18 | name: mutation.azure-workload-identity.io 19 | rules: 20 | - apiGroups: 21 | - "" 22 | apiVersions: 23 | - v1 24 | operations: 25 | - CREATE 26 | - UPDATE 27 | resources: 28 | - pods 29 | sideEffects: None 30 | timeoutSeconds: 1 31 | -------------------------------------------------------------------------------- /examples/resourceplacement/rp-deploy.yaml: -------------------------------------------------------------------------------- 1 | # This tests selecting multiple resources in a namespace, 2 | # and only applying to a subset of clusters. 3 | # Prerequisite: create and expose a deployment named "test-nginx" in namespace "test-ns". 4 | apiVersion: placement.kubernetes-fleet.io/v1beta1 5 | kind: ResourcePlacement 6 | metadata: 7 | name: rp-nginx 8 | namespace: test-ns 9 | spec: 10 | resourceSelectors: 11 | - group: apps 12 | kind: Deployment 13 | name: test-nginx 14 | version: v1 15 | - group: "" 16 | kind: Service 17 | name: test-nginx 18 | version: v1 19 | policy: 20 | placementType: PickN 21 | numberOfClusters: 2 22 | strategy: 23 | type: RollingUpdate 24 | rollingUpdate: 25 | maxUnavailable: 1 26 | maxSurge: 1 27 | -------------------------------------------------------------------------------- /examples/kueue/team-b-crp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: team-b-crp 5 | spec: 6 | resourceSelectors: 7 | - group: kueue.x-k8s.io 8 | version: v1beta1 9 | kind: ResourceFlavor 10 | name: default-flavor 11 | - group: kueue.x-k8s.io 12 | version: v1beta1 13 | kind: ClusterQueue 14 | name: cluster-queue 15 | - group: "" 16 | kind: Namespace 17 | version: v1 18 | name: team-b 19 | policy: 20 | placementType: PickFixed 21 | clusterNames: 22 | - ryanzhang-aks-member-5 23 | strategy: 24 | type: RollingUpdate 25 | rollingUpdate: 26 | maxUnavailable: 100% 27 | maxSurge: 25% 28 | unavailablePeriodSeconds: 60 29 | revisionHistoryLimit: 15 30 | 31 | -------------------------------------------------------------------------------- /examples/kueue/team-a-crp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: team-a-crp 5 | spec: 6 | resourceSelectors: 7 | - group: kueue.x-k8s.io 8 | version: v1beta1 9 | kind: ResourceFlavor 10 | name: default-flavor 11 | - group: kueue.x-k8s.io 12 | version: v1beta1 13 | kind: ClusterQueue 14 | name: cluster-queue 15 | - group: "" 16 | kind: Namespace 17 | version: v1 18 | name: team-a 19 | policy: 20 | placementType: PickFixed 21 | clusterNames: 22 | - ryanzhang-aks-member-2 23 | strategy: 24 | type: RollingUpdate 25 | rollingUpdate: 26 | maxUnavailable: 100% 27 | maxSurge: 25% 28 | unavailablePeriodSeconds: 60 29 | revisionHistoryLimit: 15 30 | 31 | -------------------------------------------------------------------------------- /examples/stagedupdaterun/updateStrategy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterStagedUpdateStrategy 3 | metadata: 4 | name: example-strategy 5 | spec: 6 | stages: 7 | - name: staging 8 | labelSelector: 9 | matchLabels: 10 | environment: staging 11 | afterStageTasks: 12 | - type: TimedWait 13 | waitTime: 1h 14 | - name: canary 15 | labelSelector: 16 | matchLabels: 17 | environment: canary 18 | sortingLabelKey: name 19 | afterStageTasks: 20 | - type: Approval 21 | - name: production 22 | labelSelector: 23 | matchLabels: 24 | environment: production 25 | sortingLabelKey: order 26 | afterStageTasks: 27 | - type: Approval 28 | - type: TimedWait 29 | waitTime: 1h 30 | -------------------------------------------------------------------------------- /pkg/utils/time/time.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package time 18 | 19 | import "time" 20 | 21 | // MaxTime returns the later of two given time.Time. 22 | func MaxTime(a, b time.Time) time.Time { 23 | if a.After(b) { 24 | return a 25 | } 26 | return b 27 | } 28 | -------------------------------------------------------------------------------- /test/e2e/resources/statefulset-invalid-storage.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: test-ss-with-volume 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: test-ss-with-volume # has to match .spec.template.metadata.labels 9 | serviceName: "test-ss-with-volume-svc" 10 | replicas: 3 # by default is 1 11 | template: 12 | metadata: 13 | labels: 14 | app: test-ss-with-volume # has to match .spec.selector.matchLabels 15 | spec: 16 | terminationGracePeriodSeconds: 10 17 | containers: 18 | - name: pause 19 | image: k8s.gcr.io/pause:3.8 20 | volumeClaimTemplates: 21 | - metadata: 22 | name: test-ss-with-volume-pvc 23 | spec: 24 | accessModes: [ "ReadWriteOnce" ] 25 | storageClassName: "non-existent-sc" 26 | resources: 27 | requests: 28 | storage: 1Gi -------------------------------------------------------------------------------- /apis/cluster/v1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1 contains API Schema definitions for the fleet cluster v1 API group. 18 | 19 | // +kubebuilder:object:generate=true 20 | // +k8s:deepcopy-gen=package,register 21 | // +groupName=cluster.kubernetes-fleet.io 22 | package v1 23 | -------------------------------------------------------------------------------- /apis/placement/v1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1 contains API Schema definitions for the fleet placement v1 API group 18 | 19 | // +kubebuilder:object:generate=true 20 | // +k8s:deepcopy-gen=package,register 21 | // +groupName=placement.kubernetes-fleet.io 22 | package v1 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | ### Description of your changes 3 | 4 | 11 | 12 | Fixes # 13 | 14 | I have: 15 | 16 | - [ ] Run `make reviewable` to ensure this PR is ready for review. 17 | 18 | ### How has this code been tested 19 | 20 | 24 | 25 | 26 | ### Special notes for your reviewer 27 | 28 | 33 | -------------------------------------------------------------------------------- /apis/cluster/v1beta1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1beta1 contains API Schema definitions for the fleet cluster v1beta1 API group. 18 | 19 | // +kubebuilder:object:generate=true 20 | // +k8s:deepcopy-gen=package,register 21 | // +groupName=cluster.kubernetes-fleet.io 22 | package v1beta1 23 | -------------------------------------------------------------------------------- /apis/placement/v1beta1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1beta1 contains API Schema definitions for the fleet placement v1beta1 API group 18 | 19 | // +kubebuilder:object:generate=true 20 | // +k8s:deepcopy-gen=package,register 21 | // +groupName=placement.kubernetes-fleet.io 22 | package v1beta1 23 | -------------------------------------------------------------------------------- /apis/placement/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the fleet placement v1alpha1 API group. 18 | 19 | // +kubebuilder:object:generate=true 20 | // +k8s:deepcopy-gen=package,register 21 | // +groupName=placement.kubernetes-fleet.io 22 | package v1alpha1 23 | -------------------------------------------------------------------------------- /hack/loadtest/manifests/test-service.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 The Kubernetes 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 | apiVersion: v1 16 | kind: Service 17 | metadata: 18 | name: test-nginx 19 | namespace: app 20 | labels: 21 | run: test-nginx 22 | spec: 23 | ports: 24 | - port: 80 25 | protocol: TCP 26 | selector: 27 | run: test-nginx 28 | -------------------------------------------------------------------------------- /hack/loadtest/test-crp1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: crp ## updated in code to be load-test-placement-utilrand.String(10) 5 | spec: 6 | resourceSelectors: 7 | - group: apiextensions.k8s.io 8 | kind: CustomResourceDefinition 9 | name: testresources.test.kubernetes-fleet.io 10 | version: v1 11 | policy: 12 | placementType: PickFixed 13 | clusterNames: 14 | - member-0 15 | - member-1 16 | - member-10 17 | - member-11 18 | - member-12 19 | - member-13 20 | - member-14 21 | - member-15 22 | - member-16 23 | - member-17 24 | - member-18 25 | - member-19 26 | strategy: 27 | type: RollingUpdate 28 | rollingUpdate: 29 | maxUnavailable: 100% 30 | maxSurge: 25% 31 | unavailablePeriodSeconds: 15 32 | revisionHistoryLimit: 15 33 | -------------------------------------------------------------------------------- /pkg/utils/httpclient/round_trippper.go: -------------------------------------------------------------------------------- 1 | package httpclient 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | type customHeadersRoundTripper struct { 9 | header http.Header 10 | delegatedRoundTripper http.RoundTripper 11 | } 12 | 13 | func NewCustomHeadersRoundTripper(header http.Header, rt http.RoundTripper) http.RoundTripper { 14 | return &customHeadersRoundTripper{ 15 | header: header, 16 | delegatedRoundTripper: rt, 17 | } 18 | } 19 | 20 | var _ http.RoundTripper = &customHeadersRoundTripper{} 21 | 22 | func (c *customHeadersRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 23 | for key, values := range c.header { 24 | if req.Header.Get(key) != "" { 25 | return nil, fmt.Errorf("custom header %q can't override other header", key) 26 | } 27 | for _, v := range values { 28 | req.Header.Add(key, v) 29 | } 30 | } 31 | return c.delegatedRoundTripper.RoundTrip(req) 32 | } 33 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 15m 3 | go: '1.24.9' 4 | 5 | linters-settings: 6 | stylecheck: 7 | checks: [ "all", "-ST1001" ] # Disables dot-import warnings 8 | revive: 9 | rules: 10 | - name: dot-imports 11 | disabled: true 12 | gosec: 13 | excludes: 14 | - G404 #Use of weak random number generator (math/rand or math/rand/v2). It is only used in tests. 15 | 16 | linters: 17 | disable-all: true 18 | enable: 19 | - decorder 20 | - errcheck 21 | - errorlint 22 | - goconst 23 | - gocyclo 24 | - gofmt 25 | - goimports 26 | - gosec 27 | - gosimple 28 | - govet 29 | - ineffassign 30 | - misspell 31 | - nakedret 32 | - nilerr 33 | - prealloc 34 | - revive 35 | - staticcheck 36 | - tparallel 37 | - typecheck 38 | - unconvert 39 | - unused 40 | - whitespace 41 | # Run with --fast=false for more extensive checks 42 | fast: true 43 | -------------------------------------------------------------------------------- /examples/test-crp4.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: crp-4 5 | spec: 6 | resourceSelectors: 7 | - group: apiextensions.k8s.io 8 | kind: CustomResourceDefinition 9 | name: testresources.test.kubernetes-fleet.io 10 | version: v1 11 | policy: 12 | placementType: PickN 13 | numberOfClusters: 2 14 | affinity: 15 | clusterAffinity: 16 | preferredDuringSchedulingIgnoredDuringExecution: 17 | - weight: 20 18 | preference: 19 | propertySorter: 20 | name: kubernetes.azure.com/per-gb-memory-core-cost 21 | sortOrder: Descending 22 | - weight: 20 23 | preference: 24 | propertySorter: 25 | name: kubernetes.azure.com/per-cpu-core-cost 26 | sortOrder: Descending 27 | strategy: 28 | type: RollingUpdate -------------------------------------------------------------------------------- /examples/envelopes/clusterscoped.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourceEnvelope 3 | metadata: 4 | name: example 5 | data: 6 | "webhook.yaml": 7 | apiVersion: admissionregistration.k8s.io/v1 8 | kind: ValidatingWebhookConfiguration 9 | metadata: 10 | name: guard 11 | webhooks: 12 | - name: guard.example.com 13 | rules: 14 | - operations: ["CREATE"] 15 | apiGroups: ["*"] 16 | apiVersions: ["*"] 17 | resources: ["*"] 18 | clientConfig: 19 | service: 20 | name: guard 21 | namespace: ops 22 | admissionReviewVersions: ["v1"] 23 | sideEffects: None 24 | timeoutSeconds: 10 25 | "clusterrole.yaml": 26 | apiVersion: rbac.authorization.k8s.io/v1 27 | kind: ClusterRole 28 | metadata: 29 | name: pod-reader 30 | rules: 31 | - apiGroups: [""] 32 | resources: ["pods"] 33 | verbs: ["get", "list", "watch"] 34 | -------------------------------------------------------------------------------- /.github/workflows/codespell.yml: -------------------------------------------------------------------------------- 1 | # GitHub Action to automate the identification of common misspellings in text files. 2 | # https://github.com/codespell-project/actions-codespell 3 | # https://github.com/codespell-project/codespell 4 | name: Code Spelling Checker 5 | on: [pull_request] 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | codespell: 11 | name: Check for spelling errors 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Harden Runner 15 | uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 16 | with: 17 | egress-policy: audit 18 | 19 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7 20 | - uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 # master 21 | with: 22 | check_filenames: true 23 | skip: ./.git,./.github/workflows/codespell.yml,.git,*.png,*.jpg,*.svg,*.sum,./vendor,go.sum,testdata 24 | ignore_words_file: .codespellignore 25 | -------------------------------------------------------------------------------- /pkg/controllers/workgenerator/manifests/test-clusterscoped-envelope.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourceEnvelope 3 | metadata: 4 | name: clusterscoped-resource-envelope 5 | data: 6 | "webhook.yaml": 7 | apiVersion: admissionregistration.k8s.io/v1 8 | kind: ValidatingWebhookConfiguration 9 | metadata: 10 | name: guard 11 | webhooks: 12 | - name: guard.example.com 13 | rules: 14 | - operations: ["CREATE"] 15 | apiGroups: ["*"] 16 | apiVersions: ["*"] 17 | resources: ["*"] 18 | clientConfig: 19 | service: 20 | name: guard 21 | namespace: ops 22 | admissionReviewVersions: ["v1"] 23 | sideEffects: None 24 | timeoutSeconds: 10 25 | "clusterrole.yaml": 26 | apiVersion: rbac.authorization.k8s.io/v1 27 | kind: ClusterRole 28 | metadata: 29 | name: pod-reader 30 | rules: 31 | - apiGroups: [""] 32 | resources: ["pods"] 33 | verbs: ["get", "list", "watch"] 34 | -------------------------------------------------------------------------------- /examples/migration/crp-1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: crp-example 5 | spec: 6 | resourceSelectors: 7 | - group: "" 8 | kind: Namespace 9 | version: v1 10 | labelSelector: 11 | matchLabels: 12 | kubernetes-fleet.io/app: prod 13 | policy: 14 | placementType: PickN 15 | numberOfClusters: 2 16 | affinity: 17 | clusterAffinity: 18 | requiredDuringSchedulingIgnoredDuringExecution: 19 | clusterSelectorTerms: 20 | - labelSelector: 21 | matchLabels: 22 | fleet.azure.com/location: westus 23 | preferredDuringSchedulingIgnoredDuringExecution: 24 | - weight: 20 25 | preference: 26 | propertySorter: 27 | name: kubernetes-fleet.io/node-count 28 | sortOrder: Descending 29 | strategy: 30 | type: RollingUpdate 31 | rollingUpdate: 32 | maxUnavailable: 1 33 | maxSurge: 50% -------------------------------------------------------------------------------- /examples/test-cro1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1alpha1 2 | kind: ClusterResourceOverride 3 | metadata: 4 | name: cro-1 5 | spec: 6 | placement: 7 | name: crp-example 8 | clusterResourceSelectors: 9 | - group: apiextensions.k8s.io 10 | kind: CustomResourceDefinition 11 | name: testresources.test.kubernetes-fleet.io 12 | version: v1 13 | policy: 14 | overrideRules: 15 | - clusterSelector: 16 | clusterSelectorTerms: 17 | - labelSelector: 18 | matchLabels: 19 | env: canary 20 | jsonPatchOverrides: 21 | - op: add 22 | # Note: the override will fail if there are no labels on the resource. 23 | # To add a new label to an empty map or overwrite the existing labels, please use /metadata/labels. 24 | # Path: /metadata/labels 25 | # value: {"new-label": "new-value"} 26 | path: /metadata/labels/new-label 27 | value: "new-value" 28 | -------------------------------------------------------------------------------- /examples/migration/crp-2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: crp-example 5 | spec: 6 | resourceSelectors: 7 | - group: "" 8 | kind: Namespace 9 | version: v1 10 | labelSelector: 11 | matchLabels: 12 | kubernetes-fleet.io/app: prod 13 | policy: 14 | placementType: PickN 15 | numberOfClusters: 2 16 | affinity: 17 | clusterAffinity: 18 | requiredDuringSchedulingIgnoredDuringExecution: 19 | clusterSelectorTerms: 20 | - propertySelector: 21 | matchExpressions: 22 | - name: kubernetes.azure.com/per-gb-memory-cost 23 | operator: Lt 24 | values: 25 | - "0.025" 26 | labelSelector: 27 | matchLabels: 28 | fleet.azure.com/location: westeurope 29 | strategy: 30 | type: RollingUpdate 31 | rollingUpdate: 32 | maxUnavailable: 1 33 | maxSurge: 50% -------------------------------------------------------------------------------- /examples/kueue/job-team-a.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | namespace: team-a # Job under team-a namespace 5 | generateName: sample-job-team-a- 6 | annotations: 7 | kueue.x-k8s.io/queue-name: lq-team-a # Point to the LocalQueue 8 | spec: 9 | ttlSecondsAfterFinished: 60 # Job will be deleted after 60 seconds 10 | parallelism: 3 # This Job will have 3 replicas running at the same time 11 | completions: 3 # This Job requires 3 completions 12 | suspend: true # Set to true to allow Kueue to control the Job when it starts 13 | template: 14 | spec: 15 | containers: 16 | - name: dummy-job 17 | image: gcr.io/k8s-staging-perf-tests/sleep:latest 18 | args: ["10s"] # Sleep for 10 seconds 19 | resources: 20 | requests: 21 | cpu: "500m" 22 | memory: "512Mi" 23 | ephemeral-storage: "512Mi" 24 | limits: 25 | cpu: "500m" 26 | memory: "512Mi" 27 | ephemeral-storage: "512Mi" 28 | restartPolicy: Never 29 | -------------------------------------------------------------------------------- /docker/hub-agent.Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the hubagent binary 2 | FROM mcr.microsoft.com/oss/go/microsoft/golang:1.24.9 AS builder 3 | 4 | ARG GOOS=linux 5 | ARG GOARCH=amd64 6 | 7 | WORKDIR /workspace 8 | # Copy the Go Modules manifests 9 | COPY go.mod go.mod 10 | COPY go.sum go.sum 11 | # cache deps before building and copying source so that we don't need to re-download as much 12 | # and so that source changes don't invalidate our downloaded layer 13 | RUN go mod download 14 | 15 | # Copy the go source 16 | COPY cmd/hubagent/ cmd/hubagent/ 17 | COPY apis/ apis/ 18 | COPY pkg/ pkg/ 19 | 20 | # Build 21 | RUN echo "Building images with GOOS=$GOOS GOARCH=$GOARCH" 22 | RUN CGO_ENABLED=1 GOOS=$GOOS GOARCH=$GOARCH GOEXPERIMENT=systemcrypto GO111MODULE=on go build -o hubagent cmd/hubagent/main.go 23 | 24 | # Use distroless as minimal base image to package the hubagent binary 25 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 26 | FROM gcr.io/distroless/base:nonroot 27 | WORKDIR / 28 | COPY --from=builder /workspace/hubagent . 29 | USER 65532:65532 30 | 31 | ENTRYPOINT ["/hubagent"] 32 | -------------------------------------------------------------------------------- /docker/member-agent.Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the memberagent binary 2 | FROM mcr.microsoft.com/oss/go/microsoft/golang:1.24.9 AS builder 3 | 4 | ARG GOOS=linux 5 | ARG GOARCH=amd64 6 | 7 | WORKDIR /workspace 8 | # Copy the Go Modules manifests 9 | COPY go.mod go.mod 10 | COPY go.sum go.sum 11 | # cache deps before building and copying source so that we don't need to re-download as much 12 | # and so that source changes don't invalidate our downloaded layer 13 | RUN go mod download 14 | 15 | # Copy the go source 16 | COPY cmd/memberagent/main.go main.go 17 | COPY apis/ apis/ 18 | COPY pkg/ pkg/ 19 | 20 | # Build 21 | RUN echo "Building images with GOOS=$GOOS GOARCH=$GOARCH" 22 | RUN CGO_ENABLED=1 GOOS=$GOOS GOARCH=$GOARCH GOEXPERIMENT=systemcrypto GO111MODULE=on go build -o memberagent main.go 23 | 24 | # Use distroless as minimal base image to package the memberagent binary 25 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 26 | FROM gcr.io/distroless/base:nonroot 27 | WORKDIR / 28 | COPY --from=builder /workspace/memberagent . 29 | USER 65532:65532 30 | 31 | ENTRYPOINT ["/memberagent"] 32 | -------------------------------------------------------------------------------- /examples/test-ro1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1alpha1 2 | kind: ResourceOverride 3 | metadata: 4 | name: ro-1 5 | namespace: test-namespace 6 | spec: 7 | placement: 8 | name: crp-example 9 | resourceSelectors: 10 | - group: "" 11 | kind: ConfigMap 12 | version: v1 13 | name: test-configmap 14 | - group: "" 15 | kind: Namespace 16 | name: test-namespace 17 | version: v1 18 | policy: 19 | overrideRules: 20 | - clusterSelector: 21 | clusterSelectorTerms: 22 | - labelSelector: 23 | matchLabels: 24 | test-key: test-value2 25 | jsonPatchOverrides: 26 | - op: add 27 | # Note: the override will fail if there are no labels on the resource. 28 | # To add a new label to an empty map or overwrite the existing labels, please use /metadata/labels. 29 | # Path: /metadata/labels 30 | # value: {"new-label": "new-value"} 31 | path: /metadata/labels/new-label 32 | value: "new-value" 33 | -------------------------------------------------------------------------------- /pkg/utils/defaulter/work.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package defaulter sets default values for the fleet resources. 18 | package defaulter 19 | 20 | import placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" 21 | 22 | // SetDefaultsWork sets the default values for the Work resource. 23 | func SetDefaultsWork(w *placementv1beta1.Work) { 24 | if w.Spec.ApplyStrategy == nil { 25 | w.Spec.ApplyStrategy = &placementv1beta1.ApplyStrategy{} 26 | } 27 | SetDefaultsApplyStrategy(w.Spec.ApplyStrategy) 28 | } 29 | -------------------------------------------------------------------------------- /hack/go-install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # https://github.com/kubernetes-sigs/cluster-api-provider-azure/blob/master/scripts/go_install.sh 3 | 4 | set -o errexit 5 | set -o nounset 6 | set -o pipefail 7 | 8 | if [[ -z "${1}" ]]; then 9 | echo "must provide module as first parameter" 10 | exit 1 11 | fi 12 | 13 | if [[ -z "${2}" ]]; then 14 | echo "must provide binary name as second parameter" 15 | exit 1 16 | fi 17 | 18 | if [[ -z "${3}" ]]; then 19 | echo "must provide version as third parameter" 20 | exit 1 21 | fi 22 | 23 | if [[ -z "${GOBIN}" ]]; then 24 | echo "GOBIN is not set. Must set GOBIN to install the bin in a specified directory." 25 | exit 1 26 | fi 27 | 28 | tmp_dir=$(mktemp -d -t goinstall_XXXXXXXXXX) 29 | function clean { 30 | rm -rf "${tmp_dir}" 31 | } 32 | trap clean EXIT 33 | 34 | rm "${GOBIN}/${2}"* || true 35 | 36 | cd "${tmp_dir}" 37 | 38 | # create a new module in the tmp directory 39 | go mod init fake/mod 40 | 41 | # install the golang module specified as the first argument 42 | go install "${1}@${3}" 43 | mv "${GOBIN}/${2}" "${GOBIN}/${2}-${3}" 44 | ln -sf "${GOBIN}/${2}-${3}" "${GOBIN}/${2}" 45 | -------------------------------------------------------------------------------- /docker/refresh-token.Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the refreshtoken binary 2 | FROM mcr.microsoft.com/oss/go/microsoft/golang:1.24.9 AS builder 3 | 4 | ARG GOOS="linux" 5 | ARG GOARCH="amd64" 6 | 7 | WORKDIR /workspace 8 | # Copy the Go Modules manifests 9 | COPY go.mod go.mod 10 | COPY go.sum go.sum 11 | # cache deps before building and copying source so that we don't need to re-download as much 12 | # and so that source changes don't invalidate our downloaded layer 13 | RUN go mod download 14 | 15 | # Copy the go source 16 | COPY cmd/authtoken/main.go main.go 17 | COPY pkg/authtoken pkg/authtoken 18 | 19 | ARG TARGETARCH 20 | 21 | # Build 22 | RUN echo "Building images with GOOS=${GOOS} GOARCH=${GOARCH}" 23 | RUN CGO_ENABLED=1 GOOS=$GOOS GOARCH=$GOARCH GOEXPERIMENT=systemcrypto GO111MODULE=on go build -o refreshtoken main.go 24 | 25 | # Use distroless as minimal base image to package the refreshtoken binary 26 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 27 | FROM gcr.io/distroless/base:nonroot 28 | WORKDIR / 29 | COPY --from=builder /workspace/refreshtoken . 30 | USER 65532:65532 31 | 32 | ENTRYPOINT ["/refreshtoken"] 33 | -------------------------------------------------------------------------------- /hack/loadtest/test-crp3.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: crp1kube 5 | spec: 6 | resourceSelectors: 7 | - group: apiextensions.k8s.io 8 | kind: CustomResourceDefinition 9 | name: testresources.test.kubernetes-fleet.io 10 | version: v1 11 | policy: 12 | placementType: PickAll 13 | affinity: 14 | clusterAffinity: 15 | requiredDuringSchedulingIgnoredDuringExecution: 16 | - weight: 20 17 | preference: 18 | labelSelector: 19 | matchExpressions: 20 | - key: system 21 | operator: DoesNotExist 22 | - weight: -20 23 | preference: 24 | labelSelector: 25 | matchExpressions: 26 | - key: env 27 | operator: In 28 | values: 29 | - canary 30 | strategy: 31 | type: RollingUpdate 32 | rollingUpdate: 33 | maxUnavailable: 100% 34 | maxSurge: 25% 35 | unavailablePeriodSeconds: 15 36 | revisionHistoryLimit: 15 37 | -------------------------------------------------------------------------------- /hack/cl2/manifests/test-configmap.yaml: -------------------------------------------------------------------------------- 1 | # Test configmap with 1k payload 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{.Name}} 6 | data: 7 | random: O0RKEYYCLRvp03c8gUPGINapuUPUpJrL1WQQmP5UOngJJsbiPF876mELH1MyD1bw2FKrr4IuOHzjl0hiL7SvO46HRCKsEHAVFhelhTzwdR9CI5A9jjthpNR4t812R15MyumLrqMAhXv4ynykUa7bt1XtzsIhYjARSKthtPvN9Hbge26PX7lhQjsT1vm42bw7HHEfRVc2P0IwZluchc6aqJfhpJcNQGSjN0GjeuzkNP7V7exWkZqtAt7JnZneq3X72XMxrdQB3sdbK3WEYdsajUAyBO0Eznn8Ab8FSfwrvKUCbCQ8C2UophqThWwzAiMw1y5BQjT3EaIqeLPWDhrqkbMHzSbpcdXLHv8Ine5594QTZZYm8II0PZd3HdpoXjzeAAix6w1c4hXFwtQejti3ZDqZNDPrEUuepRradonBRcZcFccLTGiK6BUNNzZMC3yP5DNR2yWE6P30NdeBMb7mkAFhAVUp46KZUghF60Jpu0HSmEHs0gA0dBR2gyyRsWFjT03IGxixZ2sCuOTCEqdvatTVKi6FBjigRNnxZXyL9pzyyXhTRrdcts72lvKsbFp1n0rQmQUItfKVOXC1bdfGUPaoRA4jQXdUAVdJadYfTG12ETgxXGuKc44lkTFGMKuMA4RRZYZRZGJubW18rhfnleiiOFjPL83NJ8g81xFuqf4TWhXdgwhPwe2CvIeF2lfdmyer10Y8GC4Yl7LAFp5GshpmjeDZcuGj31NankEMZLY0znRW7M551yy2TCNHzsDaYtnPR4611wNu3nVQIN8iAXmPnnMev2bDfh8l5A14CUtONGno4DQVs4IyTmpwN1oyOxsXPaL8m2r9FtUzn7JCdepirNfm0ImVUDvlR96v6axjjGvoVWzQtNDQWdnJC6x1KKs9mDuERexSwGc4F56Iec5Q2a2QB9EFxdEOSDKFjGq1az7PHOf8uqOurzFvG6dylwQnKN5oy0PFW8wgxyJO6hsUDfrslO3zrh9gtcOIt9tHL3xo70bF8hPkTinn8xRs 8 | -------------------------------------------------------------------------------- /examples/eviction/test-crp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: test-crp 5 | spec: 6 | resourceSelectors: 7 | - group: apiextensions.k8s.io 8 | kind: CustomResourceDefinition 9 | name: testresources.test.kubernetes-fleet.io 10 | version: v1 11 | policy: 12 | placementType: PickN 13 | numberOfClusters: 3 14 | affinity: 15 | clusterAffinity: 16 | preferredDuringSchedulingIgnoredDuringExecution: 17 | - weight: 20 18 | preference: 19 | labelSelector: 20 | matchExpressions: 21 | - key: env 22 | operator: In 23 | values: 24 | - canary 25 | - weight: 80 26 | preference: 27 | propertySorter: 28 | name: kubernetes.azure.com/per-gb-memory-core-cost 29 | sortOrder: Descending 30 | topologySpreadConstraints: 31 | - maxSkew: 1 32 | topologyKey: color 33 | whenUnsatisfiable: DoNotSchedule 34 | strategy: 35 | type: RollingUpdate -------------------------------------------------------------------------------- /pkg/utils/parallelizer/errorflag_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package parallelizer 18 | 19 | import ( 20 | "fmt" 21 | "testing" 22 | 23 | "github.com/google/go-cmp/cmp" 24 | "github.com/google/go-cmp/cmp/cmpopts" 25 | ) 26 | 27 | // TestErrorFlag tests the basic ops of an ErrorFlag. 28 | func TestErrorFlag(t *testing.T) { 29 | errFlag := NewErrorFlag() 30 | err := fmt.Errorf("test error") 31 | 32 | errFlag.Raise(err) 33 | returnedErr := errFlag.Lower() 34 | if !cmp.Equal(err, returnedErr, cmpopts.EquateErrors()) { 35 | t.Fatalf("Lower() = %v, want %v", returnedErr, err) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/kueue/job-team-b.yaml: -------------------------------------------------------------------------------- 1 | # [START gke_batch_kueue_intro_team_b_job] 2 | apiVersion: batch/v1 3 | kind: Job 4 | metadata: 5 | namespace: team-b # Job under team-b namespace 6 | generateName: sample-job-team-b- 7 | annotations: 8 | kueue.x-k8s.io/queue-name: lq-team-b # Point to the LocalQueue 9 | spec: 10 | ttlSecondsAfterFinished: 60 # Job will be deleted after 60 seconds 11 | parallelism: 3 # This Job will have 3 replicas running at the same time 12 | completions: 3 # This Job requires 3 completions 13 | suspend: true # Set to true to allow Kueue to control the Job when it starts 14 | template: 15 | spec: 16 | containers: 17 | - name: dummy-job 18 | image: gcr.io/k8s-staging-perf-tests/sleep:latest 19 | args: ["10s"] # Sleep for 10 seconds 20 | resources: 21 | requests: 22 | cpu: "500m" 23 | memory: "512Mi" 24 | ephemeral-storage: "512Mi" 25 | nvidia.com/gpu: "1" 26 | limits: 27 | cpu: "500m" 28 | memory: "512Mi" 29 | ephemeral-storage: "512Mi" 30 | nvidia.com/gpu: "1" 31 | restartPolicy: Never 32 | # [END gke_batch_kueue_intro_team_b_job] 33 | -------------------------------------------------------------------------------- /hack/cl2/README.md: -------------------------------------------------------------------------------- 1 | This folder contains a list of [clusterloader2](https://github.com/kubernetes/perf-tests/blob/master/clusterloader2/docs/GETTING_STARTED.md) configuration manifests for large scale testing. 2 | 3 | ## Prerequisites 4 | 5 | Follow [clusterloader2 GETTING STARTED](https://github.com/kubernetes/perf-tests/blob/master/clusterloader2/docs/GETTING_STARTED.md) to clone the `perf-tests` repository. 6 | 7 | ## Execute Tests 8 | 9 | Run `clusterloader2` under `perf-tests/clusterloader2` directory: 10 | ```bash 11 | go run cmd/clusterloader.go --testconfig= --provider=local --kubeconfig= --v=2 --enable-exec-service=false 12 | ``` 13 | We need to set `--enable-exec-service=false` to prevent creating agnostic deployment on the hub cluster as hub 14 | cluster does not allow pod creation. 15 | 16 | ## Cleanup Resources After Tests 17 | 18 | By default, clusterloader2 automatically deletes all the generated namespaces after the tests. 19 | In addition, we also want to delete all the CRPs created. To facilitate cleanup process, run the simple script 20 | provided in this folder: 21 | ``` 22 | export KUBECONFIG= 23 | ./cleanup.sh 24 | ``` -------------------------------------------------------------------------------- /test/upgrade/after/resources_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package after 18 | 19 | import ( 20 | placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" 21 | ) 22 | 23 | func workResourceIdentifiers(workNamespaceName, appConfigMapName string) []placementv1beta1.ResourceIdentifier { 24 | return []placementv1beta1.ResourceIdentifier{ 25 | { 26 | Kind: "Namespace", 27 | Name: workNamespaceName, 28 | Version: "v1", 29 | }, 30 | { 31 | Kind: "ConfigMap", 32 | Name: appConfigMapName, 33 | Version: "v1", 34 | Namespace: workNamespaceName, 35 | }, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cmd/authtoken/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/kubefleet-dev/kubefleet/pkg/authtoken/providers/azure" 10 | ) 11 | 12 | func TestParseArgs(t *testing.T) { 13 | t.Run("all arguments", func(t *testing.T) { 14 | os.Args = []string{"refreshtoken", "azure", "--clientid=test-client-id", "--scope=test-scope"} 15 | t.Cleanup(func() { 16 | os.Args = nil 17 | }) 18 | tokenProvider, err := parseArgs() 19 | assert.NotNil(t, tokenProvider) 20 | assert.Nil(t, err) 21 | 22 | azTokenProvider, ok := tokenProvider.(*azure.AuthTokenProvider) 23 | assert.Equal(t, true, ok) 24 | assert.Equal(t, "test-scope", azTokenProvider.Scope) 25 | }) 26 | t.Run("no optional arguments", func(t *testing.T) { 27 | os.Args = []string{"refreshtoken", "azure", "--clientid=test-client-id"} 28 | t.Cleanup(func() { 29 | os.Args = nil 30 | }) 31 | tokenProvider, err := parseArgs() 32 | assert.NotNil(t, tokenProvider) 33 | assert.Nil(t, err) 34 | 35 | azTokenProvider, ok := tokenProvider.(*azure.AuthTokenProvider) 36 | assert.Equal(t, true, ok) 37 | assert.Equal(t, "6dae42f8-4368-4678-94ff-3960e28e3630", azTokenProvider.Scope) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /examples/test-crp3.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: crp-3 5 | spec: 6 | resourceSelectors: 7 | - group: apiextensions.k8s.io 8 | kind: CustomResourceDefinition 9 | name: testresources.test.kubernetes-fleet.io 10 | version: v1 11 | policy: 12 | placementType: PickN 13 | numberOfClusters: 3 14 | affinity: 15 | clusterAffinity: 16 | preferredDuringSchedulingIgnoredDuringExecution: 17 | - weight: 20 18 | preference: 19 | labelSelector: 20 | matchExpressions: 21 | - key: env 22 | operator: In 23 | values: 24 | - canary 25 | - weight: 30 26 | preference: 27 | labelSelector: 28 | matchExpressions: 29 | - key: env 30 | operator: In 31 | values: 32 | - prod 33 | topologySpreadConstraints: 34 | - maxSkew: 1 35 | topologyKey: color 36 | whenUnsatisfiable: DoNotSchedule 37 | strategy: 38 | type: RollingUpdate -------------------------------------------------------------------------------- /charts/hub-agent/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: hub-agent 3 | description: A Helm chart for hub agent 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "0.1.0" 25 | -------------------------------------------------------------------------------- /charts/member-agent/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: member-agent 3 | description: Helm chart for member agent. 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "v0.1.0" 25 | -------------------------------------------------------------------------------- /charts/hub-agent/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for hub-agent. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: ghcr.io/azure/fleet/hub-agent 9 | pullPolicy: Always 10 | # Overrides the image tag whose default is the chart appVersion. 11 | tag: main 12 | 13 | logVerbosity: 5 14 | 15 | enableWebhook: true 16 | webhookServiceName: fleetwebhook 17 | enableGuardRail: true 18 | webhookClientConnectionType: service 19 | enableWorkload: false 20 | forceDeleteWaitTime: 15m0s 21 | clusterUnhealthyThreshold: 3m0s 22 | resourceSnapshotCreationMinimumInterval: 30s 23 | resourceChangesCollectionDuration: 15s 24 | 25 | namespace: 26 | fleet-system 27 | 28 | resources: 29 | limits: 30 | cpu: 500m 31 | memory: 1Gi 32 | requests: 33 | cpu: 100m 34 | memory: 128Mi 35 | 36 | tolerations: [] 37 | 38 | affinity: {} 39 | 40 | enableV1Beta1APIs: true 41 | enableClusterInventoryAPI: true 42 | enableStagedUpdateRunAPIs: true 43 | enableEvictionAPIs: true 44 | 45 | enablePprof: true 46 | pprofPort: 6065 47 | 48 | hubAPIQPS: 250 49 | hubAPIBurst: 1000 50 | MaxConcurrentClusterPlacement: 100 51 | ConcurrentResourceChangeSyncs: 20 52 | MaxFleetSizeSupported: 100 53 | -------------------------------------------------------------------------------- /apis/placement/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | "k8s.io/apimachinery/pkg/runtime/schema" 21 | "sigs.k8s.io/controller-runtime/pkg/scheme" 22 | ) 23 | 24 | var ( 25 | // GroupVersion is group version used to register these objects 26 | GroupVersion = schema.GroupVersion{Group: "placement.kubernetes-fleet.io", Version: "v1alpha1"} 27 | 28 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 29 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 30 | 31 | // AddToScheme adds the types in this group-version to the given scheme. 32 | AddToScheme = SchemeBuilder.AddToScheme 33 | ) 34 | -------------------------------------------------------------------------------- /examples/test-crp5.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: crp-5 5 | spec: 6 | resourceSelectors: 7 | - group: apiextensions.k8s.io 8 | kind: CustomResourceDefinition 9 | name: testresources.test.kubernetes-fleet.io 10 | version: v1 11 | policy: 12 | placementType: PickN 13 | numberOfClusters: 3 14 | affinity: 15 | clusterAffinity: 16 | preferredDuringSchedulingIgnoredDuringExecution: 17 | - weight: 20 18 | preference: 19 | labelSelector: 20 | matchExpressions: 21 | - key: system 22 | operator: DoesNotExist 23 | - weight: 20 24 | preference: 25 | labelSelector: 26 | matchExpressions: 27 | - key: env 28 | operator: In 29 | values: 30 | - canary 31 | - weight: -20 32 | preference: 33 | labelSelector: 34 | matchExpressions: 35 | - key: env 36 | operator: In 37 | values: 38 | - prod -------------------------------------------------------------------------------- /examples/stagedupdaterun/clusterStagedUpdateRun.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterStagedUpdateRun 3 | metadata: 4 | name: example-run 5 | spec: 6 | placementName: example-placement 7 | resourceSnapshotIndex: "0" 8 | stagedRolloutStrategyName: example-strategy 9 | status: 10 | policySnapshotIndexUsed: "0" 11 | policyObservedClusterCount: 3 12 | appliedStrategy: 13 | type: Immediate 14 | stagedUpdateStrategySnapshot: 15 | stages: 16 | - name: stage1 17 | labelSelector: 18 | matchLabels: 19 | environment: production 20 | sortingLabelKey: priority 21 | afterStageTasks: 22 | - type: TimedWait 23 | waitTime: 1h 24 | stagesStatus: 25 | - stageName: stage1 26 | clusters: 27 | - clusterName: cluster1 28 | conditions: 29 | - type: Started 30 | status: "True" 31 | startTime: "2023-10-01T00:00:00Z" 32 | endTime: "2023-10-01T01:00:00Z" 33 | conditions: 34 | - type: Progressing 35 | status: "True" 36 | conditions: 37 | - type: Initialized 38 | status: "True" 39 | - type: Progressing 40 | status: "True" 41 | - type: Succeeded 42 | status: "False" 43 | -------------------------------------------------------------------------------- /pkg/utils/parallelizer/parallelizer_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package parallelizer 18 | 19 | import ( 20 | "context" 21 | "sync/atomic" 22 | "testing" 23 | ) 24 | 25 | // TestParallelizer tests the basic ops of a Parallelizer. 26 | func TestParallelizer(t *testing.T) { 27 | p := NewParallelizer(DefaultNumOfWorkers) 28 | nums := []int32{1, 2, 3, 4, 5, 6, 7, 8} 29 | 30 | var sum int32 31 | doWork := func(pieces int) { 32 | num := nums[pieces] 33 | atomic.AddInt32(&sum, num) 34 | } 35 | 36 | ctx := context.Background() 37 | p.ParallelizeUntil(ctx, len(nums), doWork, "test") 38 | if sum != 36 { 39 | t.Errorf("sum of nums, want %d, got %d", 36, sum) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cmd/hubagent/options/webhookconnectiontype.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package options 18 | 19 | import ( 20 | "errors" 21 | "strings" 22 | ) 23 | 24 | type WebhookClientConnectionType string 25 | 26 | const ( 27 | URL WebhookClientConnectionType = "url" 28 | Service WebhookClientConnectionType = "service" 29 | ) 30 | 31 | var ( 32 | capabilitiesMap = map[string]WebhookClientConnectionType{ 33 | "service": Service, 34 | "url": URL, 35 | } 36 | ) 37 | 38 | func parseWebhookClientConnectionString(str string) (WebhookClientConnectionType, error) { 39 | t, ok := capabilitiesMap[strings.ToLower(str)] 40 | if !ok { 41 | return "", errors.New("must be \"service\" or \"url\"") 42 | } 43 | return t, nil 44 | } 45 | -------------------------------------------------------------------------------- /tools/utils/common.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) Microsoft Corporation. 3 | Licensed under the MIT license. 4 | */ 5 | 6 | package utils 7 | 8 | import ( 9 | "os" 10 | 11 | "k8s.io/apimachinery/pkg/runtime" 12 | "k8s.io/client-go/tools/clientcmd" 13 | "sigs.k8s.io/controller-runtime/pkg/client" 14 | 15 | clusterv1beta1 "github.com/kubefleet-dev/kubefleet/apis/cluster/v1beta1" 16 | ) 17 | 18 | var ( 19 | kubeConfigPath = os.Getenv("KUBECONFIG") 20 | CordonTaint = clusterv1beta1.Taint{ 21 | Key: "cordon-key", 22 | Value: "cordon-value", 23 | Effect: "NoSchedule", 24 | } 25 | ) 26 | 27 | // GetClusterClientFromClusterContext creates a new client.Client for the given cluster context and scheme. 28 | func GetClusterClientFromClusterContext(clusterContext string, scheme *runtime.Scheme) (client.Client, error) { 29 | clusterConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 30 | &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeConfigPath}, 31 | &clientcmd.ConfigOverrides{ 32 | CurrentContext: clusterContext, 33 | }) 34 | 35 | restConfig, err := clusterConfig.ClientConfig() 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | hubClient, err := client.New(restConfig, client.Options{Scheme: scheme}) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return hubClient, err 46 | } 47 | -------------------------------------------------------------------------------- /apis/cluster/v1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +kubebuilder:object:generate=true 18 | // +groupName=cluster.kubernetes-fleet.io 19 | package v1 20 | 21 | import ( 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | "sigs.k8s.io/controller-runtime/pkg/scheme" 24 | ) 25 | 26 | var ( 27 | // GroupVersion is group version used to register these objects. 28 | GroupVersion = schema.GroupVersion{Group: "cluster.kubernetes-fleet.io", Version: "v1"} 29 | 30 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme. 31 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 32 | 33 | // AddToScheme adds the types in this group-version to the given scheme. 34 | AddToScheme = SchemeBuilder.AddToScheme 35 | ) 36 | -------------------------------------------------------------------------------- /.github/.copilot/domain_knowledge/cluster vs namespace scoped resources: -------------------------------------------------------------------------------- 1 | # naming convention 2 | All APIs whose name starts with Cluster are clusterScoped resources while its counterpart whose name matches the remainder of the API represents a namespace scoped resource. 3 | For example, we have ClusterResourcePlacement API and ResourcePlacement API. The former is cluster scoped while the latter is namespace scoped. 4 | # The difference between cluster scoped and namespace scoped resources 5 | The main difference between cluster scoped and namespace scoped resources is that cluster scoped resources are not bound to a specific namespace and can be accessed across the entire cluster, while namespace scoped resources are bound to a specific namespace and can only be accessed within that namespace. This translates to the following differences in how to get, list, create, update, and delete these resources: 6 | ## Cluster Scoped Resources 7 | When one does CRUD on a cluster scoped resources, it only needs to know its name and type, i.e. something like this client.Get(ctx, types.NamespacedName{Name: string(name)}, crp) 8 | ## Namespace Scoped Resources 9 | When one does CRUD on a namespace scoped resources, it needs to know its name, type, and namespace, i.e. something like this client.Get(ctx, types.NamespacedName{Name: string(name), Namespace: namespace}, rp) 10 | -------------------------------------------------------------------------------- /apis/placement/v1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +kubebuilder:object:generate=true 18 | // +groupName=placement.kubernetes-fleet.io 19 | package v1 20 | 21 | import ( 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | "sigs.k8s.io/controller-runtime/pkg/scheme" 24 | ) 25 | 26 | var ( 27 | // GroupVersion is group version used to register these objects 28 | GroupVersion = schema.GroupVersion{Group: "placement.kubernetes-fleet.io", Version: "v1"} 29 | 30 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 31 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 32 | 33 | // AddToScheme adds the types in this group-version to the given scheme. 34 | AddToScheme = SchemeBuilder.AddToScheme 35 | ) 36 | -------------------------------------------------------------------------------- /apis/cluster/v1beta1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +kubebuilder:object:generate=true 18 | // +groupName=cluster.kubernetes-fleet.io 19 | package v1beta1 20 | 21 | import ( 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | "sigs.k8s.io/controller-runtime/pkg/scheme" 24 | ) 25 | 26 | var ( 27 | // GroupVersion is group version used to register these objects. 28 | GroupVersion = schema.GroupVersion{Group: "cluster.kubernetes-fleet.io", Version: "v1beta1"} 29 | 30 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme. 31 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 32 | 33 | // AddToScheme adds the types in this group-version to the given scheme. 34 | AddToScheme = SchemeBuilder.AddToScheme 35 | ) 36 | -------------------------------------------------------------------------------- /pkg/utils/httpclient/round_trippper_test.go: -------------------------------------------------------------------------------- 1 | package httpclient 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func Test_CustomHeadersRoundTripper(t *testing.T) { 12 | t.Parallel() 13 | t.Run("add new header", func(t *testing.T) { 14 | t.Parallel() 15 | f := &fakeRoundTripper{} 16 | header := make(http.Header) 17 | header.Set("new-header", "new-header-value") 18 | rt := NewCustomHeadersRoundTripper(header, f) 19 | req := httptest.NewRequest(http.MethodGet, "/host", nil) 20 | _, err := rt.RoundTrip(req) 21 | assert.Nil(t, err) 22 | assert.Equal(t, f.req.Header.Get("new-header"), "new-header-value") 23 | }) 24 | 25 | t.Run("don't override exist header", func(t *testing.T) { 26 | t.Parallel() 27 | f := &fakeRoundTripper{} 28 | header := make(http.Header) 29 | header.Set("exist-header", "new-value") 30 | rt := NewCustomHeadersRoundTripper(header, f) 31 | req := httptest.NewRequest(http.MethodGet, "/host", nil) 32 | req.Header.Set("exist-header", "old-value") 33 | _, err := rt.RoundTrip(req) 34 | assert.NotNil(t, err) 35 | }) 36 | } 37 | 38 | type fakeRoundTripper struct { 39 | req *http.Request 40 | } 41 | 42 | func (f *fakeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 43 | f.req = req 44 | return nil, nil 45 | } 46 | -------------------------------------------------------------------------------- /apis/placement/v1beta1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +kubebuilder:object:generate=true 18 | // +groupName=placement.kubernetes-fleet.io 19 | package v1beta1 20 | 21 | import ( 22 | "k8s.io/apimachinery/pkg/runtime/schema" 23 | "sigs.k8s.io/controller-runtime/pkg/scheme" 24 | ) 25 | 26 | var ( 27 | // GroupVersion is group version used to register these objects 28 | GroupVersion = schema.GroupVersion{Group: "placement.kubernetes-fleet.io", Version: "v1beta1"} 29 | 30 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 31 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 32 | 33 | // AddToScheme adds the types in this group-version to the given scheme. 34 | AddToScheme = SchemeBuilder.AddToScheme 35 | ) 36 | -------------------------------------------------------------------------------- /apis/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package apis contains API interfaces for the fleet API group. 18 | package apis 19 | 20 | import ( 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | "sigs.k8s.io/controller-runtime/pkg/client" 23 | ) 24 | 25 | // A Conditioned may have conditions set or retrieved. Conditions typically 26 | // indicate the status of both a resource and its reconciliation process. 27 | // +kubebuilder:object:generate=false 28 | type Conditioned interface { 29 | SetConditions(...metav1.Condition) 30 | GetCondition(string) *metav1.Condition 31 | } 32 | 33 | // A ConditionedObj is for kubernetes resource with conditions. 34 | // +kubebuilder:object:generate=false 35 | type ConditionedObj interface { 36 | client.Object 37 | Conditioned 38 | } 39 | -------------------------------------------------------------------------------- /apis/placement/v1/commons.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1 18 | 19 | const ( 20 | // fleetPrefix is the prefix used for official fleet labels/annotations. 21 | // Unprefixed labels/annotations are reserved for end-users 22 | // See https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#label-selector-and-annotation-conventions 23 | fleetPrefix = "kubernetes-fleet.io/" 24 | ) 25 | 26 | // NamespacedName comprises a resource name, with a mandatory namespace. 27 | type NamespacedName struct { 28 | // Name is the name of the namespaced scope resource. 29 | // +required 30 | Name string `json:"name"` 31 | // Namespace is namespace of the namespaced scope resource. 32 | // +required 33 | Namespace string `json:"namespace"` 34 | } 35 | -------------------------------------------------------------------------------- /pkg/utils/validator/membercluster.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ( 4 | "fmt" 5 | 6 | apiErrors "k8s.io/apimachinery/pkg/util/errors" 7 | "k8s.io/apimachinery/pkg/util/validation" 8 | 9 | clusterv1beta1 "github.com/kubefleet-dev/kubefleet/apis/cluster/v1beta1" 10 | ) 11 | 12 | var ( 13 | invalidTaintKeyErrFmt = "invalid taint key %+v: %s" 14 | invalidTaintValueErrFmt = "invalid taint value %+v: %s" 15 | uniqueTaintErrFmt = "taint %+v already exists, taints must be unique" 16 | ) 17 | 18 | // ValidateMemberCluster validates member cluster fields and returns error. 19 | func ValidateMemberCluster(mc clusterv1beta1.MemberCluster) error { 20 | return validateTaints(mc.Spec.Taints) 21 | } 22 | 23 | func validateTaints(taints []clusterv1beta1.Taint) error { 24 | allErr := make([]error, 0) 25 | taintMap := make(map[clusterv1beta1.Taint]bool) 26 | for _, taint := range taints { 27 | for _, msg := range validation.IsQualifiedName(taint.Key) { 28 | allErr = append(allErr, fmt.Errorf(invalidTaintKeyErrFmt, taint, msg)) 29 | } 30 | if taint.Value != "" { 31 | for _, msg := range validation.IsValidLabelValue(taint.Value) { 32 | allErr = append(allErr, fmt.Errorf(invalidTaintValueErrFmt, taint, msg)) 33 | } 34 | } 35 | if taintMap[taint] { 36 | allErr = append(allErr, fmt.Errorf(uniqueTaintErrFmt, taint)) 37 | } 38 | taintMap[taint] = true 39 | } 40 | return apiErrors.NewAggregate(allErr) 41 | } 42 | -------------------------------------------------------------------------------- /test/apis/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the test v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=test.kubernetes-fleet.io 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "test.kubernetes-fleet.io", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /hack/loadtest/test-crp2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: placement.kubernetes-fleet.io/v1beta1 2 | kind: ClusterResourcePlacement 3 | metadata: 4 | name: crp 5 | spec: 6 | resourceSelectors: 7 | - group: apiextensions.k8s.io 8 | kind: CustomResourceDefinition 9 | name: testresources.test.kubernetes-fleet.io 10 | version: v1 11 | policy: 12 | placementType: PickN 13 | numberOfClusters: 25 14 | affinity: 15 | clusterAffinity: 16 | preferredDuringSchedulingIgnoredDuringExecution: 17 | - weight: 20 18 | preference: 19 | labelSelector: 20 | matchExpressions: 21 | - key: system 22 | operator: DoesNotExist 23 | - weight: -20 24 | preference: 25 | labelSelector: 26 | matchExpressions: 27 | - key: fleet.azure.com/location 28 | operator: In 29 | values: 30 | - centralus 31 | - weight: 20 32 | preference: 33 | labelSelector: 34 | matchExpressions: 35 | - key: fleet.azure.com/location 36 | operator: In 37 | values: 38 | - eastus 39 | strategy: 40 | type: RollingUpdate 41 | rollingUpdate: 42 | maxUnavailable: 100% 43 | maxSurge: 25% 44 | unavailablePeriodSeconds: 15 45 | revisionHistoryLimit: 15 46 | -------------------------------------------------------------------------------- /DCO: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 6 | Everyone is permitted to copy and distribute verbatim copies of this 7 | license document, but changing it is not allowed. 8 | 9 | 10 | Developer's Certificate of Origin 1.1 11 | 12 | By making a contribution to this project, I certify that: 13 | 14 | (a) The contribution was created in whole or in part by me and I 15 | have the right to submit it under the open source license 16 | indicated in the file; or 17 | 18 | (b) The contribution is based upon previous work that, to the best 19 | of my knowledge, is covered under an appropriate open source 20 | license and I have the right under that license to submit that 21 | work with modifications, whether created in whole or in part 22 | by me, under the same open source license (unless I am 23 | permitted to submit under a different license), as indicated 24 | in the file; or 25 | 26 | (c) The contribution was provided directly to me by some other 27 | person who certified (a), (b) or (c) and I have not modified 28 | it. 29 | 30 | (d) I understand and agree that this project and the contribution 31 | are public and that a record of the contribution (including all 32 | personal information I submit with it, including my sign-off) is 33 | maintained indefinitely and may be redistributed consistent with 34 | this project or the open source license(s) involved. 35 | -------------------------------------------------------------------------------- /tools/fleet/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "log" 21 | 22 | "github.com/spf13/cobra" 23 | 24 | "github.com/kubefleet-dev/kubefleet/tools/fleet/cmd/approve" 25 | "github.com/kubefleet-dev/kubefleet/tools/fleet/cmd/draincluster" 26 | "github.com/kubefleet-dev/kubefleet/tools/fleet/cmd/uncordoncluster" 27 | ) 28 | 29 | func main() { 30 | rootCmd := &cobra.Command{ 31 | Use: "kubectl-fleet", 32 | Short: "KubeFleet cluster management plugin", 33 | Long: "kubectl-fleet is a kubectl plugin for managing KubeFleet member clusters", 34 | } 35 | 36 | // Add subcommands 37 | rootCmd.AddCommand(approve.NewCmdApprove()) 38 | rootCmd.AddCommand(draincluster.NewCmdDrainCluster()) 39 | rootCmd.AddCommand(uncordoncluster.NewCmdUncordonCluster()) 40 | 41 | if err := rootCmd.Execute(); err != nil { 42 | log.Fatalf("Error executing command: %v", err) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /charts/member-agent/values.yaml: -------------------------------------------------------------------------------- 1 | replicaCount: 1 2 | 3 | image: 4 | repository: ghcr.io/azure/fleet/member-agent 5 | pullPolicy: Always 6 | tag: main 7 | 8 | logVerbosity: 5 9 | 10 | refreshtoken: 11 | repository: ghcr.io/azure/fleet/refresh-token 12 | pullPolicy: Always 13 | tag: main 14 | 15 | resources: 16 | limits: 17 | cpu: 500m 18 | memory: 1Gi 19 | requests: 20 | cpu: 100m 21 | memory: 128Mi 22 | 23 | tolerations: [] 24 | 25 | affinity: {} 26 | 27 | namespace: 28 | fleet-system 29 | 30 | config: 31 | provider: secret 32 | hubURL : https://: 33 | memberClusterName: membercluster-sample 34 | hubCA: 35 | identityKey: "identity-key-path" 36 | identityCert: "identity-cert-path" 37 | CABundle: "ca-bundle-path" 38 | azureCloudConfig: 39 | cloud: "" 40 | tenantId: "" 41 | subscriptionId: "" 42 | useManagedIdentityExtension: false 43 | userAssignedIdentityID: "" 44 | aadClientId: "" 45 | aadClientSecret: "" 46 | resourceGroup: "" 47 | userAgent: "" 48 | location: "" 49 | vnetName: "" 50 | vnetResourceGroup: "" 51 | 52 | secret: 53 | name: "hub-kubeconfig-secret" 54 | namespace: "default" 55 | 56 | azure: 57 | clientid: 58 | 59 | tlsClientInsecure: true #TODO should be false in the production 60 | useCAAuth: false 61 | 62 | enableV1Beta1APIs: true 63 | 64 | enablePprof: true 65 | pprofPort: 6065 66 | hubPprofPort: 6066 67 | -------------------------------------------------------------------------------- /pkg/utils/parallelizer/errorflag.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package parallelizer 18 | 19 | // ErrorFlag is a flag for indicating whether an error has occurred when running tasks in 20 | // parallel with the parallelizer. 21 | type ErrorFlag struct { 22 | errChan chan error 23 | } 24 | 25 | // Raise raises the error flag with an error; if the flag has been raised, it will return 26 | // immediately. 27 | func (f *ErrorFlag) Raise(err error) { 28 | select { 29 | case f.errChan <- err: 30 | default: 31 | } 32 | } 33 | 34 | // Lower lowers the error flag and returns the error that was used to raise the flag; it returns 35 | // nil if the flag has not been raised. 36 | func (f *ErrorFlag) Lower() error { 37 | select { 38 | case err := <-f.errChan: 39 | return err 40 | default: 41 | return nil 42 | } 43 | } 44 | 45 | // NewErrorFlag returns an error flag. 46 | func NewErrorFlag() *ErrorFlag { 47 | return &ErrorFlag{ 48 | errChan: make(chan error, 1), 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pkg/scheduler/framework/plugins/sameplacementaffinity/scoring.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sameplacementaffinity 18 | 19 | import ( 20 | "context" 21 | 22 | clusterv1beta1 "github.com/kubefleet-dev/kubefleet/apis/cluster/v1beta1" 23 | placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" 24 | "github.com/kubefleet-dev/kubefleet/pkg/scheduler/framework" 25 | ) 26 | 27 | // Score allows the plugin to connect to the Score extension point in the scheduling framework. 28 | func (p *Plugin) Score( 29 | _ context.Context, 30 | state framework.CycleStatePluginReadWriter, 31 | _ placementv1beta1.PolicySnapshotObj, 32 | cluster *clusterv1beta1.MemberCluster, 33 | ) (score *framework.ClusterScore, status *framework.Status) { 34 | if state.HasObsoleteBindingFor(cluster.Name) { 35 | return &framework.ClusterScore{ObsoletePlacementAffinityScore: 1}, nil 36 | } 37 | // All done. 38 | return &framework.ClusterScore{ObsoletePlacementAffinityScore: 0}, nil 39 | } 40 | -------------------------------------------------------------------------------- /pkg/scheduler/queue/queue_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package queue 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/google/go-cmp/cmp" 23 | ) 24 | 25 | // TestSimplePlacementSchedulingQueueBasicOps tests the basic ops 26 | // (Add, Next, Done) of a simpleClusterResourcePlacementSchedulingQueue. 27 | func TestSimplePlacementSchedulingQueueBasicOps(t *testing.T) { 28 | sq := NewSimplePlacementSchedulingQueue() 29 | sq.Run() 30 | 31 | keysToAdd := []PlacementKey{"A", "B", "C", "D", "E"} 32 | for _, key := range keysToAdd { 33 | sq.Add(key) 34 | } 35 | 36 | keysRecved := []PlacementKey{} 37 | for i := 0; i < len(keysToAdd); i++ { 38 | key, closed := sq.NextPlacementKey() 39 | if closed { 40 | t.Fatalf("Queue closed unexpected") 41 | } 42 | keysRecved = append(keysRecved, key) 43 | sq.Done(key) 44 | sq.Forget(key) 45 | } 46 | 47 | if !cmp.Equal(keysToAdd, keysRecved) { 48 | t.Fatalf("Received keys %v, want %v", keysRecved, keysToAdd) 49 | } 50 | 51 | sq.Close() 52 | } 53 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | The KubeFleet maintainers takes the security of the project very seriously; we greatly welcomes 4 | and appreciates any responsible disclosures of security vulnerabilities. 5 | 6 | If you believe you have found a security vulnerability in the repository, please follow the steps 7 | below to report it to the KubeFleet team. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** Instead, 12 | report them to the [KubeFleet maintainers](mailto:kubefleet-maintainers@googlegroups.com). 13 | We prefer all communications to be in English. 14 | 15 | You should receive a response as soon as possible. If for some reason you do not, please 16 | follow up via email to ensure we received your original message. 17 | 18 | Please include the requested information listed below (as much as you can provide) to help 19 | us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us process your report more quickly. 30 | 31 | Thanks for helping KubeFleet to become more secure! 32 | -------------------------------------------------------------------------------- /pkg/scheduler/framework/plugins/sameplacementaffinity/filtering.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sameplacementaffinity 18 | 19 | import ( 20 | "context" 21 | 22 | clusterv1beta1 "github.com/kubefleet-dev/kubefleet/apis/cluster/v1beta1" 23 | placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" 24 | 25 | "github.com/kubefleet-dev/kubefleet/pkg/scheduler/framework" 26 | ) 27 | 28 | // Filter allows the plugin to connect to the Filter extension point in the scheduling framework. 29 | func (p *Plugin) Filter( 30 | _ context.Context, 31 | state framework.CycleStatePluginReadWriter, 32 | _ placementv1beta1.PolicySnapshotObj, 33 | cluster *clusterv1beta1.MemberCluster, 34 | ) (status *framework.Status) { 35 | if !state.HasScheduledOrBoundBindingFor(cluster.Name) { 36 | // all done. 37 | return nil 38 | } 39 | 40 | reason := "resource placement has already been scheduled or bounded on the cluster" 41 | return framework.NewNonErrorStatus(framework.ClusterAlreadySelected, p.Name(), reason) 42 | } 43 | -------------------------------------------------------------------------------- /pkg/authtoken/interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package authtoken 17 | 18 | import ( 19 | "context" 20 | "time" 21 | ) 22 | 23 | // An AuthToken is an authentication token used to communicate with the hub API server. 24 | type AuthToken struct { 25 | Token string // The authentication token string. 26 | ExpiresOn time.Time // The expiration time of the token. 27 | } 28 | 29 | // Provider defines a method for fetching an authentication token. 30 | type Provider interface { 31 | // FetchToken fetches an authentication token to make requests to its associated fleet's hub cluster. 32 | // It returns the token for a given input context, or an error if the retrieval fails. 33 | FetchToken(ctx context.Context) (AuthToken, error) 34 | } 35 | 36 | // Writer defines a method for writing an authentication token to a specified location. 37 | type Writer interface { 38 | // WriteToken writes the provided authentication token to a filepath location specified in a TokenWriter. 39 | // It returns an error if the writing process fails. 40 | WriteToken(token AuthToken) error 41 | } 42 | -------------------------------------------------------------------------------- /charts/hub-agent/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "hub-agent.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "hub-agent.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "hub-agent.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "hub-agent.labels" -}} 37 | helm.sh/chart: {{ include "hub-agent.chart" . }} 38 | {{ include "hub-agent.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "hub-agent.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "hub-agent.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | -------------------------------------------------------------------------------- /charts/member-agent/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "member-agent.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "member-agent.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "member-agent.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "member-agent.labels" -}} 37 | helm.sh/chart: {{ include "member-agent.chart" . }} 38 | {{ include "member-agent.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "member-agent.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "member-agent.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | -------------------------------------------------------------------------------- /pkg/utils/controller/updaterun_resolver.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controller 18 | 19 | import ( 20 | "context" 21 | 22 | "k8s.io/apimachinery/pkg/types" 23 | ctrl "sigs.k8s.io/controller-runtime" 24 | "sigs.k8s.io/controller-runtime/pkg/client" 25 | 26 | placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" 27 | ) 28 | 29 | // FetchUpdateRunFromRequest resolves a controller runtime request to a concrete update run object that implements UpdateRunObj. 30 | func FetchUpdateRunFromRequest(ctx context.Context, c client.Reader, req ctrl.Request) (placementv1beta1.UpdateRunObj, error) { 31 | var updateRun placementv1beta1.UpdateRunObj 32 | if req.NamespacedName.Namespace != "" { 33 | // This is a namespaced StagedUpdateRun 34 | updateRun = &placementv1beta1.StagedUpdateRun{} 35 | } else { 36 | // This is a cluster-scoped ClusterStagedUpdateRun 37 | updateRun = &placementv1beta1.ClusterStagedUpdateRun{} 38 | } 39 | 40 | if err := c.Get(ctx, types.NamespacedName{Namespace: req.NamespacedName.Namespace, Name: req.NamespacedName.Name}, updateRun); err != nil { 41 | return nil, err 42 | } 43 | return updateRun, nil 44 | } 45 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | KubeFleet welcomes contributions and suggestions! 4 | 5 | ## Terms 6 | 7 | All contributions to the repository must be submitted under the terms of the [Apache Public License 2.0](https://www.apache.org/licenses/LICENSE-2.0). 8 | 9 | ## Certificate of Origin 10 | 11 | By contributing to this project, you agree to the Developer Certificate of Origin (DCO). This document was created by the Linux Kernel community and is a simple statement that you, as a contributor, have the legal right to make the contribution. See the [DCO](DCO) file for details. 12 | 13 | ## DCO Sign Off 14 | 15 | You must sign off your commit to state that you certify the [DCO](DCO). To certify your commit for DCO, add a line like the following at the end of your commit message: 16 | 17 | ``` 18 | Signed-off-by: John Smith 19 | ``` 20 | 21 | This can be done with the `--signoff` option to `git commit`. See the [Git documentation](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s) for details. 22 | 23 | ## Code of Conduct 24 | 25 | The KubeFleet project has adopted the CNCF Code of Conduct. Refer to our [Community Code of Conduct](CODE_OF_CONDUCT.md) for details. 26 | 27 | ## Contributing a patch 28 | 29 | 1. Submit an issue describing your proposed change to the repository in question. The repository owners will respond to your issue promptly. 30 | 2. Fork the desired repository, then develop and test your code changes. 31 | 3. Submit a pull request. 32 | 33 | ## Issue and pull request management 34 | 35 | Anyone can comment on issues and submit reviews for pull requests. In order to be assigned an issue or pull request, you can leave a `/assign ` comment on the issue or pull request. 36 | -------------------------------------------------------------------------------- /.github/workflows/code-lint.yml: -------------------------------------------------------------------------------- 1 | name: Code Linter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release-* 8 | workflow_dispatch: {} 9 | pull_request: 10 | branches: 11 | - main 12 | - release-* 13 | paths-ignore: [docs/**, "**.md", "**.mdx", "**.png", "**.jpg"] 14 | 15 | env: 16 | # Common versions 17 | GO_VERSION: '1.24.9' 18 | 19 | jobs: 20 | 21 | detect-noop: 22 | runs-on: ubuntu-latest 23 | outputs: 24 | noop: ${{ steps.noop.outputs.should_skip }} 25 | steps: 26 | - name: Detect No-op Changes 27 | id: noop 28 | uses: fkirc/skip-duplicate-actions@v5.3.1 29 | with: 30 | github_token: ${{ secrets.GITHUB_TOKEN }} 31 | do_not_skip: '["workflow_dispatch", "schedule", "push"]' 32 | concurrent_skipping: false 33 | 34 | staticcheck: 35 | runs-on: ubuntu-latest 36 | needs: detect-noop 37 | if: needs.detect-noop.outputs.noop != 'true' 38 | 39 | steps: 40 | - name: Setup Go 41 | uses: actions/setup-go@v6 42 | with: 43 | go-version: ${{ env.GO_VERSION }} 44 | 45 | - name: Checkout 46 | uses: actions/checkout@v6.0.1 47 | with: 48 | submodules: true 49 | 50 | - name: StaticCheck 51 | run: GO111MODULE=auto make staticcheck 52 | 53 | lint: 54 | name: "Lint" 55 | runs-on: ubuntu-latest 56 | timeout-minutes: 10 57 | permissions: 58 | contents: read 59 | 60 | steps: 61 | - name: Set up Go ${{ env.GO_VERSION }} 62 | uses: actions/setup-go@v6 63 | with: 64 | go-version: ${{ env.GO_VERSION }} 65 | 66 | - name: Check out code into the Go module directory 67 | uses: actions/checkout@v6.0.1 68 | 69 | - name: golangci-lint 70 | run: make lint 71 | -------------------------------------------------------------------------------- /pkg/scheduler/framework/cyclestateutils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package framework 18 | 19 | import ( 20 | fleetv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" 21 | ) 22 | 23 | // prepareScheduledOrBoundBindingsMap returns a map that allows quick lookup of whether a cluster 24 | // already has a binding of the scheduled or bound state relevant to the current scheduling 25 | // cycle. 26 | func prepareScheduledOrBoundBindingsMap(scheduledOrBoundBindings ...[]fleetv1beta1.BindingObj) map[string]bool { 27 | bm := make(map[string]bool) 28 | 29 | for _, bindingSet := range scheduledOrBoundBindings { 30 | for _, binding := range bindingSet { 31 | bm[binding.GetBindingSpec().TargetCluster] = true 32 | } 33 | } 34 | 35 | return bm 36 | } 37 | 38 | // prepareObsoleteBindingsMap returns a map that allows quick lookup of whether a cluster 39 | // already has an obsolete binding relevant to the current scheduling cycle. 40 | func prepareObsoleteBindingsMap(obsoleteBindings []fleetv1beta1.BindingObj) map[string]bool { 41 | bm := make(map[string]bool) 42 | 43 | for _, binding := range obsoleteBindings { 44 | bm[binding.GetBindingSpec().TargetCluster] = true 45 | } 46 | 47 | return bm 48 | } 49 | -------------------------------------------------------------------------------- /pkg/utils/time/time_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package time 18 | 19 | import ( 20 | "testing" 21 | "time" 22 | ) 23 | 24 | func TestMaxTime(t *testing.T) { 25 | t1 := time.Now() 26 | t2 := t1.Add(-1 * time.Hour) 27 | zero := time.Time{} 28 | tests := []struct { 29 | name string 30 | a, b time.Time 31 | want time.Time 32 | }{ 33 | { 34 | name: "a after b", 35 | a: t1, 36 | b: t2, 37 | want: t1, 38 | }, 39 | { 40 | name: "b after a", 41 | a: t2, 42 | b: t1, 43 | want: t1, 44 | }, 45 | { 46 | name: "equal times", 47 | a: t1, 48 | b: t1, 49 | want: t1, 50 | }, 51 | { 52 | name: "zero and non-zero", 53 | a: zero, 54 | b: t1, 55 | want: t1, 56 | }, 57 | { 58 | name: "non-zero and zero", 59 | a: t2, 60 | b: zero, 61 | want: t2, 62 | }, 63 | { 64 | name: "both zero", 65 | a: zero, 66 | b: zero, 67 | want: zero, 68 | }, 69 | } 70 | 71 | for _, tc := range tests { 72 | t.Run(tc.name, func(t *testing.T) { 73 | got := MaxTime(tc.a, tc.b) 74 | if !got.Equal(tc.want) { 75 | t.Errorf("MaxTime(%v, %v) = %v; want %v", tc.a, tc.b, got, tc.want) 76 | } 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /pkg/scheduler/framework/plugins/tainttoleration/plugin.go: -------------------------------------------------------------------------------- 1 | package tainttoleration 2 | 3 | import "github.com/kubefleet-dev/kubefleet/pkg/scheduler/framework" 4 | 5 | // Plugin is the scheduler plugin that checks to see if taints on the MemberCluster 6 | // can be tolerated by tolerations on ClusterResourcePlacement 7 | type Plugin struct { 8 | // The name of the plugin. 9 | name string 10 | 11 | // The framework handle. 12 | handle framework.Handle 13 | } 14 | 15 | var ( 16 | // Verify that Plugin can connect to relevant extension points at compile time. 17 | // 18 | // This plugin leverages the following the extension points: 19 | // * Filter 20 | // 21 | // Note that successful connection to any of the extension points implies that the 22 | // plugin already implements the Plugin interface. 23 | _ framework.FilterPlugin = &Plugin{} 24 | ) 25 | 26 | type taintTolerationPluginOptions struct { 27 | // The name of the plugin. 28 | name string 29 | } 30 | 31 | type Option func(*taintTolerationPluginOptions) 32 | 33 | var defaultPluginOptions = taintTolerationPluginOptions{ 34 | name: "TaintToleration", 35 | } 36 | 37 | // WithName sets the name of the plugin. 38 | func WithName(name string) Option { 39 | return func(o *taintTolerationPluginOptions) { 40 | o.name = name 41 | } 42 | } 43 | 44 | // New returns a new Plugin. 45 | func New(opts ...Option) Plugin { 46 | options := defaultPluginOptions 47 | for _, opt := range opts { 48 | opt(&options) 49 | } 50 | 51 | return Plugin{ 52 | name: options.name, 53 | } 54 | } 55 | 56 | // Name returns the name of the plugin. 57 | func (p *Plugin) Name() string { 58 | return p.name 59 | } 60 | 61 | // SetUpWithFramework sets up this plugin with a scheduler framework. 62 | func (p *Plugin) SetUpWithFramework(handle framework.Handle) { 63 | p.handle = handle 64 | } 65 | -------------------------------------------------------------------------------- /pkg/utils/eviction/eviction.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) Microsoft Corporation. 3 | Licensed under the MIT license. 4 | */ 5 | 6 | package eviction 7 | 8 | import ( 9 | "k8s.io/klog/v2" 10 | 11 | placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" 12 | "github.com/kubefleet-dev/kubefleet/pkg/utils/condition" 13 | ) 14 | 15 | // IsEvictionInTerminalState checks to see if eviction is in a terminal state. 16 | func IsEvictionInTerminalState(eviction *placementv1beta1.ClusterResourcePlacementEviction) bool { 17 | if validCondition := eviction.GetCondition(string(placementv1beta1.PlacementEvictionConditionTypeValid)); condition.IsConditionStatusFalse(validCondition, eviction.GetGeneration()) { 18 | klog.V(2).InfoS("Invalid eviction, no need to reconcile", "clusterResourcePlacementEviction", eviction.Name) 19 | return true 20 | } 21 | 22 | if executedCondition := eviction.GetCondition(string(placementv1beta1.PlacementEvictionConditionTypeExecuted)); executedCondition != nil { 23 | klog.V(2).InfoS("Eviction has executed condition specified, no need to reconcile", "clusterResourcePlacementEviction", eviction.Name) 24 | return true 25 | } 26 | return false 27 | } 28 | 29 | // IsPlacementPresent checks to see if placement on target cluster could be present. 30 | func IsPlacementPresent(binding *placementv1beta1.ClusterResourceBinding) bool { 31 | if binding.Spec.State == placementv1beta1.BindingStateBound { 32 | return true 33 | } 34 | if binding.Spec.State == placementv1beta1.BindingStateUnscheduled { 35 | currentAnnotation := binding.GetAnnotations() 36 | previousState, exist := currentAnnotation[placementv1beta1.PreviousBindingStateAnnotation] 37 | if exist && placementv1beta1.BindingState(previousState) == placementv1beta1.BindingStateBound { 38 | return true 39 | } 40 | } 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /pkg/utils/validator/clusterresourceplacementeviction.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package validator provides utils to validate ClusterResourcePlacementEviction resources. 18 | package validator 19 | 20 | import ( 21 | "fmt" 22 | 23 | "k8s.io/apimachinery/pkg/util/errors" 24 | 25 | fleetv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" 26 | ) 27 | 28 | // ValidateClusterResourcePlacementForEviction validates cluster resource placement fields for eviction and returns error. 29 | func ValidateClusterResourcePlacementForEviction(crp fleetv1beta1.ClusterResourcePlacement) error { 30 | allErr := make([]error, 0) 31 | 32 | // Check Cluster Resource Placement is not deleting 33 | if crp.DeletionTimestamp != nil { 34 | allErr = append(allErr, fmt.Errorf("cluster resource placement %s is being deleted", crp.Name)) 35 | return errors.NewAggregate(allErr) 36 | } 37 | // Check Cluster Resource Placement Policy 38 | if crp.Spec.Policy != nil { 39 | if crp.Spec.Policy.PlacementType == fleetv1beta1.PickFixedPlacementType { 40 | allErr = append(allErr, fmt.Errorf("cluster resource placement policy type %s is not supported", crp.Spec.Policy.PlacementType)) 41 | } 42 | } 43 | 44 | return errors.NewAggregate(allErr) 45 | } 46 | -------------------------------------------------------------------------------- /test/utils/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 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 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | // Package metrics provides utilities for metrics. 15 | package metrics 16 | 17 | import ( 18 | "github.com/google/go-cmp/cmp" 19 | "github.com/google/go-cmp/cmp/cmpopts" 20 | prometheusclientmodel "github.com/prometheus/client_model/go" 21 | ) 22 | 23 | var ( 24 | // MetricsCmpOptions defines comparison options for Prometheus metric structures. 25 | // - Sorting metric value and its labels for deterministic ordering, 26 | // - Comparing gauge values based on whether they were meaningfully set (i.e., > 0), 27 | // - Ignoring unexported fields to avoid false mismatches due to internal state. 28 | MetricsCmpOptions = []cmp.Option{ 29 | cmpopts.SortSlices(func(a, b *prometheusclientmodel.Metric) bool { 30 | return a.GetGauge().GetValue() < b.GetGauge().GetValue() // sort by time 31 | }), 32 | cmpopts.SortSlices(func(a, b *prometheusclientmodel.LabelPair) bool { 33 | return a.GetName() < b.GetName() // Sort by label 34 | }), 35 | cmp.Comparer(func(a, b *prometheusclientmodel.Gauge) bool { 36 | return (a.GetValue() > 0) == (b.GetValue() > 0) 37 | }), 38 | cmpopts.IgnoreUnexported(prometheusclientmodel.Metric{}, prometheusclientmodel.LabelPair{}, prometheusclientmodel.Gauge{}), 39 | } 40 | ) 41 | -------------------------------------------------------------------------------- /pkg/authtoken/token_writer_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package authtoken 17 | 18 | import ( 19 | "io" 20 | "strings" 21 | "testing" 22 | "time" 23 | 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | type BufferWriterFactory struct { 28 | buf *strings.Builder 29 | writeCount int 30 | } 31 | 32 | func NewBufferWriterFactory() *BufferWriterFactory { 33 | return &BufferWriterFactory{new(strings.Builder), 0} 34 | } 35 | 36 | func (f *BufferWriterFactory) Create() (io.WriteCloser, error) { 37 | return BufferWriter{f}, nil 38 | } 39 | 40 | type BufferWriter struct { 41 | factory *BufferWriterFactory 42 | } 43 | 44 | func (c BufferWriter) Write(p []byte) (int, error) { 45 | c.factory.writeCount++ 46 | return c.factory.buf.Write(p) 47 | } 48 | 49 | func (c BufferWriter) Close() error { 50 | // no op 51 | return nil 52 | } 53 | 54 | func TestWriteToken(t *testing.T) { 55 | token := AuthToken{ 56 | Token: "test token", 57 | ExpiresOn: time.Now(), 58 | } 59 | 60 | factory := NewBufferWriterFactory() 61 | bufferWriter := NewWriter(factory.Create) 62 | err := bufferWriter.WriteToken(token) 63 | 64 | assert.Equal(t, nil, err, "TestWriteToken") 65 | assert.Equal(t, token.Token, factory.buf.String(), "TestWriteToken") 66 | } 67 | -------------------------------------------------------------------------------- /pkg/authtoken/token_writer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package authtoken 17 | 18 | import ( 19 | "fmt" 20 | "io" 21 | "os" 22 | 23 | "k8s.io/klog/v2" 24 | ) 25 | 26 | type Factory struct { 27 | filePath string 28 | } 29 | 30 | func NewFactory(filePath string) Factory { 31 | return Factory{filePath: filePath} 32 | } 33 | 34 | func (w Factory) Create() (io.WriteCloser, error) { 35 | wc, err := os.Create(w.filePath) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return wc, nil 40 | } 41 | 42 | type TokenWriter struct { 43 | writerFactory func() (io.WriteCloser, error) 44 | } 45 | 46 | func NewWriter(factory func() (io.WriteCloser, error)) Writer { 47 | return &TokenWriter{ 48 | writerFactory: factory, 49 | } 50 | } 51 | 52 | func (w *TokenWriter) WriteToken(token AuthToken) error { 53 | writer, err := w.writerFactory() 54 | if err != nil { 55 | return err 56 | } 57 | defer func() { 58 | err := writer.Close() 59 | if err != nil { 60 | klog.ErrorS(err, "cannot close the token file") 61 | } 62 | }() 63 | _, err = io.WriteString(writer, token.Token) 64 | if err != nil { 65 | return fmt.Errorf("cannot write the refresh token: %w", err) 66 | } 67 | klog.V(2).InfoS("token has been saved to the file successfully") 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /pkg/webhook/add_handler.go: -------------------------------------------------------------------------------- 1 | package webhook 2 | 3 | import ( 4 | "github.com/kubefleet-dev/kubefleet/pkg/webhook/clusterresourceoverride" 5 | "github.com/kubefleet-dev/kubefleet/pkg/webhook/clusterresourceplacement" 6 | "github.com/kubefleet-dev/kubefleet/pkg/webhook/clusterresourceplacementdisruptionbudget" 7 | "github.com/kubefleet-dev/kubefleet/pkg/webhook/clusterresourceplacementeviction" 8 | "github.com/kubefleet-dev/kubefleet/pkg/webhook/fleetresourcehandler" 9 | "github.com/kubefleet-dev/kubefleet/pkg/webhook/membercluster" 10 | "github.com/kubefleet-dev/kubefleet/pkg/webhook/pod" 11 | "github.com/kubefleet-dev/kubefleet/pkg/webhook/replicaset" 12 | "github.com/kubefleet-dev/kubefleet/pkg/webhook/resourceoverride" 13 | "github.com/kubefleet-dev/kubefleet/pkg/webhook/resourceplacement" 14 | ) 15 | 16 | func init() { 17 | // AddToManagerFleetResourceValidator is a function to register fleet guard rail resource validator to the webhook server 18 | AddToManagerFleetResourceValidator = fleetresourcehandler.Add 19 | AddToManagerMemberclusterValidator = membercluster.Add 20 | // AddToManagerFuncs is a list of functions to register webhook validators and mutators to the webhook server 21 | AddToManagerFuncs = append(AddToManagerFuncs, clusterresourceplacement.AddMutating) 22 | AddToManagerFuncs = append(AddToManagerFuncs, clusterresourceplacement.Add) 23 | AddToManagerFuncs = append(AddToManagerFuncs, resourceplacement.Add) 24 | AddToManagerFuncs = append(AddToManagerFuncs, pod.Add) 25 | AddToManagerFuncs = append(AddToManagerFuncs, replicaset.Add) 26 | AddToManagerFuncs = append(AddToManagerFuncs, clusterresourceoverride.Add) 27 | AddToManagerFuncs = append(AddToManagerFuncs, resourceoverride.Add) 28 | AddToManagerFuncs = append(AddToManagerFuncs, clusterresourceplacementeviction.Add) 29 | AddToManagerFuncs = append(AddToManagerFuncs, clusterresourceplacementdisruptionbudget.Add) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/utils/binding/binding.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package binding 18 | 19 | import ( 20 | "k8s.io/klog/v2" 21 | 22 | placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" 23 | "github.com/kubefleet-dev/kubefleet/pkg/utils/condition" 24 | ) 25 | 26 | // HasBindingFailed checks if BindingObj has failed based on its conditions. 27 | func HasBindingFailed(binding placementv1beta1.BindingObj) bool { 28 | for i := condition.OverriddenCondition; i <= condition.AvailableCondition; i++ { 29 | if condition.IsConditionStatusFalse(binding.GetCondition(string(i.ResourceBindingConditionType())), binding.GetGeneration()) { 30 | // TODO: parse the reason of the condition to see if the failure is recoverable/retriable or not 31 | klog.V(2).Infof("binding %s has condition %s with status false", klog.KObj(binding), string(i.ResourceBindingConditionType())) 32 | return true 33 | } 34 | } 35 | return false 36 | } 37 | 38 | // IsBindingDiffReported checks if the binding is in diffReported state. 39 | func IsBindingDiffReported(binding placementv1beta1.BindingObj) bool { 40 | diffReportCondition := binding.GetCondition(string(placementv1beta1.ResourceBindingDiffReported)) 41 | return diffReportCondition != nil && diffReportCondition.ObservedGeneration == binding.GetGeneration() 42 | } 43 | -------------------------------------------------------------------------------- /pkg/utils/controller/strategy_resolver.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controller 18 | 19 | import ( 20 | "context" 21 | 22 | "k8s.io/apimachinery/pkg/types" 23 | "sigs.k8s.io/controller-runtime/pkg/client" 24 | 25 | placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" 26 | ) 27 | 28 | // FetchUpdateStrategyFromNamespacedName resolves a NamespacedName to a concrete staged update strategy object that implements UpdateStrategyObj. 29 | // If Namespace is empty, it fetches a ClusterStagedUpdateStrategy (cluster-scoped). 30 | // If Namespace is not empty, it fetches a StagedUpdateStrategy (namespace-scoped). 31 | func FetchUpdateStrategyFromNamespacedName(ctx context.Context, c client.Reader, strategyKey types.NamespacedName) (placementv1beta1.UpdateStrategyObj, error) { 32 | var strategy placementv1beta1.UpdateStrategyObj 33 | // If Namespace is empty, it's a cluster-scoped strategy (ClusterStagedUpdateStrategy) 34 | if strategyKey.Namespace == "" { 35 | strategy = &placementv1beta1.ClusterStagedUpdateStrategy{} 36 | } else { 37 | // Otherwise, it's a namespaced strategy (StagedUpdateStrategy) 38 | strategy = &placementv1beta1.StagedUpdateStrategy{} 39 | } 40 | err := c.Get(ctx, strategyKey, strategy) 41 | if err != nil { 42 | return nil, err 43 | } 44 | return strategy, nil 45 | } 46 | -------------------------------------------------------------------------------- /test/upgrade/after/utils_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package after 18 | 19 | import ( 20 | . "github.com/onsi/gomega" 21 | 22 | "github.com/kubefleet-dev/kubefleet/test/e2e/framework" 23 | ) 24 | 25 | // checkIfPlacedWorkResourcesOnAllMemberClustersConsistently verifies if the work resources have been placed on 26 | // all applicable member clusters. 27 | func checkIfPlacedWorkResourcesOnAllMemberClustersConsistently(workNamespaceName, appConfigMapName string) { 28 | checkIfPlacedWorkResourcesOnMemberClustersConsistently(allMemberClusters, workNamespaceName, appConfigMapName) 29 | } 30 | 31 | // checkIfPlacedWorkResourcesOnMemberClustersConsistently verifies if the work resources have been placed on 32 | // the specified set of member clusters. 33 | func checkIfPlacedWorkResourcesOnMemberClustersConsistently(clusters []*framework.Cluster, workNamespaceName, appConfigMapName string) { 34 | for idx := range clusters { 35 | memberCluster := clusters[idx] 36 | workResourcesPlacedActual := workNamespaceAndConfigMapPlacedOnClusterActual(memberCluster, workNamespaceName, appConfigMapName) 37 | // Give the system a bit more breathing room when process resource placement. 38 | Consistently(workResourcesPlacedActual, consistentlyDuration, consistentlyInterval).Should(Succeed(), "Failed to place work resources on member cluster %s", memberCluster.ClusterName) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pkg/metrics/shared/metrics.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package shared contains metrics emitted by both the hub-agent and member-agent. 18 | package shared 19 | 20 | import ( 21 | "github.com/prometheus/client_golang/prometheus" 22 | "sigs.k8s.io/controller-runtime/pkg/metrics" 23 | ) 24 | 25 | var ( 26 | JoinResultMetrics = prometheus.NewCounterVec(prometheus.CounterOpts{ 27 | Name: "join_result_counter", 28 | Help: "Number of successful Join operations", 29 | }, []string{"result"}) 30 | LeaveResultMetrics = prometheus.NewCounterVec(prometheus.CounterOpts{ 31 | Name: "leave_result_counter", 32 | Help: "Number of successful Leave operations", 33 | }, []string{"result"}) 34 | ) 35 | 36 | var ( 37 | ReportJoinResultMetric = func() { 38 | JoinResultMetrics.With(prometheus.Labels{ 39 | // Per team agreement, the failure result won't be reported from the agents as k8s controller would retry 40 | // failed reconciliations. 41 | "result": "success", 42 | }).Inc() 43 | } 44 | ReportLeaveResultMetric = func() { 45 | LeaveResultMetrics.With(prometheus.Labels{ 46 | // Per team agreement, the failure result won't be reported from the agents as k8s controller would retry 47 | // failed reconciliations. 48 | "result": "success", 49 | }).Inc() 50 | } 51 | ) 52 | 53 | func init() { 54 | metrics.Registry.MustRegister( 55 | JoinResultMetrics, 56 | LeaveResultMetrics, 57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /pkg/utils/controller/snapshot_builder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controller 18 | 19 | import ( 20 | fleetv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" 21 | ) 22 | 23 | // SplitSelectedResources splits selected resources into separate lists 24 | // so that the total size of each split list of selected Resources is within the size limit. 25 | func SplitSelectedResources(selectedResources []fleetv1beta1.ResourceContent, snapshotSizeLimit int) [][]fleetv1beta1.ResourceContent { 26 | var selectedResourcesList [][]fleetv1beta1.ResourceContent 27 | i := 0 28 | for i < len(selectedResources) { 29 | j := i 30 | currentSize := 0 31 | var snapshotResources []fleetv1beta1.ResourceContent 32 | for j < len(selectedResources) { 33 | currentSize += len(selectedResources[j].Raw) 34 | if currentSize > snapshotSizeLimit { 35 | break 36 | } 37 | snapshotResources = append(snapshotResources, selectedResources[j]) 38 | j++ 39 | } 40 | // Any selected resource will always be less than 1.5MB since that's the ETCD limit. In this case an individual 41 | // selected resource crosses the size limit. 42 | if len(snapshotResources) == 0 && len(selectedResources[j].Raw) > snapshotSizeLimit { 43 | snapshotResources = append(snapshotResources, selectedResources[j]) 44 | j++ 45 | } 46 | selectedResourcesList = append(selectedResourcesList, snapshotResources) 47 | i = j 48 | } 49 | return selectedResourcesList 50 | } 51 | -------------------------------------------------------------------------------- /pkg/scheduler/framework/profile_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package framework 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/google/go-cmp/cmp" 23 | ) 24 | 25 | const ( 26 | dummyProfileName = "dummyProfile" 27 | dummyPluginName = "dummyAllPurposePlugin" 28 | ) 29 | 30 | // TestProfile tests the basic ops of a Profile. 31 | func TestProfile(t *testing.T) { 32 | profile := NewProfile(dummyProfileName) 33 | 34 | dummyAllPurposePlugin := &DummyAllPurposePlugin{ 35 | name: dummyPluginName, 36 | } 37 | dummyPlugin := Plugin(dummyAllPurposePlugin) 38 | 39 | profile.WithPostBatchPlugin(dummyAllPurposePlugin) 40 | profile.WithPreFilterPlugin(dummyAllPurposePlugin) 41 | profile.WithFilterPlugin(dummyAllPurposePlugin) 42 | profile.WithPreScorePlugin(dummyAllPurposePlugin) 43 | profile.WithScorePlugin(dummyAllPurposePlugin) 44 | 45 | wantProfile := &Profile{ 46 | name: dummyProfileName, 47 | postBatchPlugins: []PostBatchPlugin{dummyAllPurposePlugin}, 48 | preFilterPlugins: []PreFilterPlugin{dummyAllPurposePlugin}, 49 | filterPlugins: []FilterPlugin{dummyAllPurposePlugin}, 50 | preScorePlugins: []PreScorePlugin{dummyAllPurposePlugin}, 51 | scorePlugins: []ScorePlugin{dummyAllPurposePlugin}, 52 | registeredPlugins: map[string]Plugin{ 53 | dummyPluginName: dummyPlugin, 54 | }, 55 | } 56 | 57 | if !cmp.Equal(profile, wantProfile, cmp.AllowUnexported(Profile{}, DummyAllPurposePlugin{})) { 58 | t.Fatalf("NewProfile() = %v, want %v", profile, wantProfile) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pkg/utils/validator/clusterresourceplacementdisruptionbudget.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 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 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | // Package validator provides utils to validate ClusterResourcePlacementDisruptionBudget resources. 15 | package validator 16 | 17 | import ( 18 | "fmt" 19 | 20 | "k8s.io/apimachinery/pkg/util/errors" 21 | "k8s.io/apimachinery/pkg/util/intstr" 22 | 23 | fleetv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" 24 | ) 25 | 26 | // ValidateClusterResourcePlacementDisruptionBudget validates cluster resource placement disruption budget fields based on crp placement type and returns error. 27 | func ValidateClusterResourcePlacementDisruptionBudget(db *fleetv1beta1.ClusterResourcePlacementDisruptionBudget, crp *fleetv1beta1.ClusterResourcePlacement) error { 28 | allErr := make([]error, 0) 29 | 30 | // Check ClusterResourcePlacementDisruptionBudget fields if CRP is PickAll placement type 31 | if crp.Spec.Policy == nil || crp.Spec.Policy.PlacementType == fleetv1beta1.PickAllPlacementType { 32 | if db.Spec.MaxUnavailable != nil { 33 | allErr = append(allErr, fmt.Errorf("cluster resource placement policy type PickAll is not supported with any specified max unavailable %v", db.Spec.MaxUnavailable)) 34 | } 35 | if db.Spec.MinAvailable != nil && db.Spec.MinAvailable.Type == intstr.String { 36 | allErr = append(allErr, fmt.Errorf("cluster resource placement policy type PickAll is not supported with min available as a percentage %v", db.Spec.MinAvailable)) 37 | } 38 | } 39 | 40 | return errors.NewAggregate(allErr) 41 | } 42 | -------------------------------------------------------------------------------- /hack/membership/cleanup.sh: -------------------------------------------------------------------------------- 1 | # This script should only be run after deleting the member cluster custom resources. 2 | # It cleans up the resources created during the join process. 3 | 4 | if [ "$#" -lt 2 ]; then 5 | echo "Usage: $0 [ ...]" 6 | exit 1 7 | fi 8 | 9 | export HUB_CLUSTER="$1" 10 | if [[ ! $(kubectl config view -o jsonpath="{.contexts[?(@.context.cluster==\"$HUB_CLUSTER\")]}") ]] > /dev/null 2>&1; then 11 | echo "The cluster named $HUB_CLUSTER does not exist." 12 | exit 1 13 | fi 14 | 15 | for MEMBER_CLUSTER in "${@:2}"; do 16 | if [[ ! $(kubectl config view -o jsonpath="{.contexts[?(@.context.cluster==\"$MEMBER_CLUSTER\")]}") ]] > /dev/null 2>&1; then 17 | echo "The cluster named $MEMBER_CLUSTER does not exist." 18 | exit 1 19 | fi 20 | done 21 | 22 | export HUB_CLUSTER_CONTEXT=$(kubectl config view -o jsonpath="{.contexts[?(@.context.cluster==\"$HUB_CLUSTER\")].name}") 23 | export HUB_CLUSTER_ADDRESS=$(kubectl config view -o jsonpath="{.clusters[?(@.name==\"$HUB_CLUSTER\")].cluster.server}") 24 | 25 | for MEMBER_CLUSTER in "${@:2}"; do 26 | export MEMBER_CLUSTER_CONTEXT=$(kubectl config view -o jsonpath="{.contexts[?(@.context.cluster==\"$MEMBER_CLUSTER\")].name}") 27 | 28 | kubectl config use-context $HUB_CLUSTER_CONTEXT 29 | kubectl delete secret $MEMBER_CLUSTER-hub-cluster-access-token -n connect-to-fleet 30 | kubectl delete serviceaccount $MEMBER_CLUSTER-hub-cluster-access -n connect-to-fleet 31 | kubectl config use-context $MEMBER_CLUSTER_CONTEXT 32 | helm uninstall member-agent 33 | helm uninstall member-net-controller-manager 34 | helm uninstall mcs-controller-manager 35 | kubectl delete crd endpointsliceexports.networking.fleet.azure.com 36 | kubectl delete crd endpointsliceimports.networking.fleet.azure.com 37 | kubectl delete crd internalserviceexports.networking.fleet.azure.com 38 | kubectl delete crd internalserviceimports.networking.fleet.azure.com 39 | kubectl delete crd multiclusterservices.networking.fleet.azure.com 40 | kubectl delete crd serviceexports.networking.fleet.azure.com 41 | kubectl delete crd serviceimports.networking.fleet.azure.com 42 | done 43 | -------------------------------------------------------------------------------- /pkg/controllers/clusterresourceplacementstatuswatcher/watcher_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 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 clusterresourceplacementstatuswatcher 16 | 17 | import ( 18 | "testing" 19 | 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | "sigs.k8s.io/controller-runtime/pkg/event" 22 | 23 | placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" 24 | ) 25 | 26 | func TestBuildDeleteEventPredicate(t *testing.T) { 27 | predicate := buildDeleteEventPredicate() 28 | 29 | tests := []struct { 30 | name string 31 | testFunc func() bool 32 | want bool 33 | }{ 34 | { 35 | name: "GenericEvent should return false", 36 | testFunc: func() bool { 37 | genericEvent := event.GenericEvent{ 38 | Object: &placementv1beta1.ClusterResourcePlacementStatus{ 39 | ObjectMeta: metav1.ObjectMeta{ 40 | Name: "test-crps", 41 | Namespace: "test-namespace", 42 | }, 43 | }, 44 | } 45 | return predicate.Generic(genericEvent) 46 | }, 47 | want: false, 48 | }, 49 | { 50 | name: "DeleteEvent with nil object should return false", 51 | testFunc: func() bool { 52 | deleteEvent := event.DeleteEvent{ 53 | Object: nil, 54 | DeleteStateUnknown: false, 55 | } 56 | return predicate.Delete(deleteEvent) 57 | }, 58 | want: false, 59 | }, 60 | } 61 | 62 | for _, tt := range tests { 63 | t.Run(tt.name, func(t *testing.T) { 64 | got := tt.testFunc() 65 | if got != tt.want { 66 | t.Errorf("want %v, but got %v", tt.want, got) 67 | } 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /pkg/utils/resource/resource.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package resource defines common utils for working with kubernetes resources. 18 | package resource 19 | 20 | import ( 21 | "crypto/sha256" 22 | "encoding/json" 23 | "fmt" 24 | 25 | "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | const ( 29 | // etcd has a 1.5 MiB limit for objects by default, and Kubernetes clients might 30 | // reject request entities too large (~2/~3 MiB, depending on the protocol in use). 31 | DefaultObjSizeLimitWithPaddingBytes = 1415578 // 1.35 MiB, or ~1.42 MB. 32 | ) 33 | 34 | // HashOf returns the hash of the resource. 35 | func HashOf(resource any) (string, error) { 36 | jsonBytes, err := json.Marshal(resource) 37 | if err != nil { 38 | return "", err 39 | } 40 | return fmt.Sprintf("%x", sha256.Sum256(jsonBytes)), nil 41 | } 42 | 43 | // CalculateSizeDeltaOverLimitFor calculates the size delta in bytes of a given object 44 | // over a specified size limit. It returns a positive value if the object size exceeds 45 | // the limit or a negative value if the object size is below the limit. 46 | // 47 | // This utility is useful in cases where KubeFleet needs to check if it can create/update 48 | // an object with additional information. 49 | func CalculateSizeDeltaOverLimitFor(obj runtime.Object, sizeLimitBytes int) (int, error) { 50 | jsonBytes, err := json.Marshal(obj) 51 | if err != nil { 52 | return 0, fmt.Errorf("cannot determine object size: %w", err) 53 | } 54 | if sizeLimitBytes < 0 { 55 | return 0, fmt.Errorf("size limit must be non-negative") 56 | } 57 | return len(jsonBytes) - sizeLimitBytes, nil 58 | } 59 | -------------------------------------------------------------------------------- /test/apis/v1alpha1/testresource_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // TestResourceSpec defines the desired state of TestResource 24 | type TestResourceSpec struct { 25 | // +optional 26 | Foo string `json:"foo,omitempty"` 27 | 28 | // +optional 29 | Bar string `json:"bar,omitempty"` 30 | 31 | // +optional 32 | Items []string `json:"items,omitempty"` 33 | 34 | // +optional 35 | LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"` 36 | } 37 | 38 | // TestResourceStatus defines the observed state of TestResource 39 | type TestResourceStatus struct { 40 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 41 | // Important: Run "make" to regenerate code after modifying this file 42 | } 43 | 44 | // +kubebuilder:object:root=true 45 | // +kubebuilder:subresource:status 46 | 47 | // TestResource is the Schema for the testresources API 48 | type TestResource struct { 49 | metav1.TypeMeta `json:",inline"` 50 | metav1.ObjectMeta `json:"metadata,omitempty"` 51 | 52 | // +required 53 | Spec TestResourceSpec `json:"spec"` 54 | // +optional 55 | Status TestResourceStatus `json:"status,omitempty"` 56 | } 57 | 58 | // +kubebuilder:object:root=true 59 | 60 | // TestResourceList contains a list of TestResource 61 | type TestResourceList struct { 62 | metav1.TypeMeta `json:",inline"` 63 | metav1.ListMeta `json:"metadata,omitempty"` 64 | Items []TestResource `json:"items"` 65 | } 66 | 67 | func init() { 68 | SchemeBuilder.Register(&TestResource{}, &TestResourceList{}) 69 | } 70 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # KubeFleet Roadmap 2 | 3 | ## Project Website 4 | - Setup the project website 5 | 6 | ## Support more cluster properties so that user can pick the right cluster for their workload 7 | - Support node level SKU as properties, e.g. CPU, GPU, Memory, etc 8 | - The application admin can choose clusters that have nodes with H100 GPU. 9 | - The application admin can choose clusters that have nodes with 128GB memory. 10 | - Support network topology 11 | - The application admin can choose the clusters with requires infiniband, or 100Gbps network. 12 | 13 | ## Support scheduling for namespaced resources (heterogeneous namespace) 14 | - Support independent scheduling policy for namespaced resources 15 | - e.g. The application admin can pick one workload in a namespace to cluster A while the other workload in the same namespace to cluster B. 16 | 17 | ## Support Job dispatching 18 | - Support the use case to use the fleet as a super computer to run hyper scale applications 19 | 20 | ## Dynamic scheduling 21 | - De-scheduler for the fleet 22 | - The de-scheduler would move the workload to the right cluster if the cluster is not the best fit for the workload anymore. 23 | - Rebalance the workload 24 | - The application admin can rebalance the workload to make sure the workload is spread evenly across the clusters. 25 | 26 | ## Support anti-affinity for workload 27 | - Support affinity/anti-affinity for their workload. 28 | - The application admin can specify that their workload A needs to be placed on the same clusters that workload B runs. 29 | - The application admin can specify that their workload A cannot be placed on the same clusters that workload B runs. 30 | 31 | ## Support Customized health check for workload 32 | - Support user specified health check for their workload. 33 | - The application admin can provide a customized health check for their workload. 34 | 35 | ## Support Spread mode for workload 36 | - The application admin can specify a spread mode for their workload. 37 | - The move between clusters would follow the max-unavailable/min-available pods rule. 38 | 39 | ## Support identity federation 40 | - The member agent can assume the identity of the operator on the hub cluster and not using admin privilege when applying the resources. 41 | -------------------------------------------------------------------------------- /test/upgrade/before/resources_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package before 18 | 19 | import ( 20 | corev1 "k8s.io/api/core/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | 23 | placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" 24 | ) 25 | 26 | const ( 27 | workNamespaceLabelName = "target-test-spec" 28 | ) 29 | 30 | func workResourceSelector(workNamespaceName string) []placementv1beta1.ResourceSelectorTerm { 31 | return []placementv1beta1.ResourceSelectorTerm{ 32 | { 33 | Group: "", 34 | Kind: "Namespace", 35 | Version: "v1", 36 | Name: workNamespaceName, 37 | }, 38 | } 39 | } 40 | 41 | func appNamespace(workNamespaceName string, crpName string) corev1.Namespace { 42 | return corev1.Namespace{ 43 | ObjectMeta: metav1.ObjectMeta{ 44 | Name: workNamespaceName, 45 | Labels: map[string]string{ 46 | workNamespaceLabelName: crpName, 47 | }, 48 | }, 49 | } 50 | } 51 | 52 | func appConfigMap(workNamespaceName, appConfigMapName string) corev1.ConfigMap { 53 | return corev1.ConfigMap{ 54 | ObjectMeta: metav1.ObjectMeta{ 55 | Name: appConfigMapName, 56 | Namespace: workNamespaceName, 57 | }, 58 | Data: map[string]string{ 59 | "data": "test", 60 | }, 61 | } 62 | } 63 | 64 | func workResourceIdentifiers(workNamespaceName, appConfigMapName string) []placementv1beta1.ResourceIdentifier { 65 | return []placementv1beta1.ResourceIdentifier{ 66 | { 67 | Kind: "Namespace", 68 | Name: workNamespaceName, 69 | Version: "v1", 70 | }, 71 | { 72 | Kind: "ConfigMap", 73 | Name: appConfigMapName, 74 | Version: "v1", 75 | Namespace: workNamespaceName, 76 | }, 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /config/crd/bases/placement.kubernetes-fleet.io_resourceenvelopes.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.16.0 7 | name: resourceenvelopes.placement.kubernetes-fleet.io 8 | spec: 9 | group: placement.kubernetes-fleet.io 10 | names: 11 | categories: 12 | - fleet 13 | - fleet-placement 14 | kind: ResourceEnvelope 15 | listKind: ResourceEnvelopeList 16 | plural: resourceenvelopes 17 | singular: resourceenvelope 18 | scope: Namespaced 19 | versions: 20 | - name: v1beta1 21 | schema: 22 | openAPIV3Schema: 23 | description: ResourceEnvelope wraps namespaced resources for placement. 24 | properties: 25 | apiVersion: 26 | description: |- 27 | APIVersion defines the versioned schema of this representation of an object. 28 | Servers should convert recognized schemas to the latest internal value, and 29 | may reject unrecognized values. 30 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 31 | type: string 32 | data: 33 | additionalProperties: 34 | type: object 35 | x-kubernetes-preserve-unknown-fields: true 36 | description: |- 37 | The manifests wrapped in this envelope. 38 | 39 | Each manifest is uniquely identified by a string key, typically a filename that represents 40 | the manifest. The value is the manifest object itself. 41 | maxProperties: 50 42 | minProperties: 1 43 | type: object 44 | kind: 45 | description: |- 46 | Kind is a string value representing the REST resource this object represents. 47 | Servers may infer this from the endpoint the client submits requests to. 48 | Cannot be updated. 49 | In CamelCase. 50 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 51 | type: string 52 | metadata: 53 | type: object 54 | required: 55 | - data 56 | type: object 57 | served: true 58 | storage: true 59 | -------------------------------------------------------------------------------- /config/crd/bases/placement.kubernetes-fleet.io_clusterresourceenvelopes.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.16.0 7 | name: clusterresourceenvelopes.placement.kubernetes-fleet.io 8 | spec: 9 | group: placement.kubernetes-fleet.io 10 | names: 11 | categories: 12 | - fleet 13 | - fleet-placement 14 | kind: ClusterResourceEnvelope 15 | listKind: ClusterResourceEnvelopeList 16 | plural: clusterresourceenvelopes 17 | singular: clusterresourceenvelope 18 | scope: Cluster 19 | versions: 20 | - name: v1beta1 21 | schema: 22 | openAPIV3Schema: 23 | description: ClusterResourceEnvelope wraps cluster-scoped resources for placement. 24 | properties: 25 | apiVersion: 26 | description: |- 27 | APIVersion defines the versioned schema of this representation of an object. 28 | Servers should convert recognized schemas to the latest internal value, and 29 | may reject unrecognized values. 30 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 31 | type: string 32 | data: 33 | additionalProperties: 34 | type: object 35 | x-kubernetes-preserve-unknown-fields: true 36 | description: |- 37 | The manifests wrapped in this envelope. 38 | 39 | Each manifest is uniquely identified by a string key, typically a filename that represents 40 | the manifest. The value is the manifest object itself. 41 | maxProperties: 50 42 | minProperties: 1 43 | type: object 44 | kind: 45 | description: |- 46 | Kind is a string value representing the REST resource this object represents. 47 | Servers may infer this from the endpoint the client submits requests to. 48 | Cannot be updated. 49 | In CamelCase. 50 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 51 | type: string 52 | metadata: 53 | type: object 54 | required: 55 | - data 56 | type: object 57 | served: true 58 | storage: true 59 | -------------------------------------------------------------------------------- /pkg/utils/informer/readiness/readiness.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package readiness 18 | 19 | import ( 20 | "fmt" 21 | "net/http" 22 | 23 | "github.com/kubefleet-dev/kubefleet/pkg/utils/informer" 24 | "k8s.io/apimachinery/pkg/runtime/schema" 25 | "k8s.io/klog/v2" 26 | ) 27 | 28 | // InformerReadinessChecker creates a readiness check function that verifies 29 | // all resource informer caches are synced before marking the pod as ready. 30 | // This prevents components from processing requests before the discovery cache is populated. 31 | func InformerReadinessChecker(resourceInformer informer.Manager) func(*http.Request) error { 32 | return func(_ *http.Request) error { 33 | if resourceInformer == nil { 34 | return fmt.Errorf("resource informer not initialized") 35 | } 36 | 37 | // Require ALL informer caches to be synced before marking ready 38 | allResources := resourceInformer.GetAllResources() 39 | if len(allResources) == 0 { 40 | // This can happen during startup when the ResourceInformer is created but the ChangeDetector 41 | // hasn't discovered and registered any resources yet via AddDynamicResources(). 42 | return fmt.Errorf("resource informer not ready: no resources registered") 43 | } 44 | 45 | // Check that ALL informers have synced 46 | unsyncedResources := []schema.GroupVersionResource{} 47 | for _, gvr := range allResources { 48 | if !resourceInformer.IsInformerSynced(gvr) { 49 | unsyncedResources = append(unsyncedResources, gvr) 50 | } 51 | } 52 | 53 | if len(unsyncedResources) > 0 { 54 | return fmt.Errorf("resource informer not ready: %d/%d informers not synced yet", len(unsyncedResources), len(allResources)) 55 | } 56 | 57 | klog.V(5).InfoS("All resource informers synced", "totalInformers", len(allResources)) 58 | return nil 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pkg/scheduler/framework/plugins/tainttoleration/filtering.go: -------------------------------------------------------------------------------- 1 | package tainttoleration 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | corev1 "k8s.io/api/core/v1" 8 | "k8s.io/klog/v2" 9 | 10 | clusterv1beta1 "github.com/kubefleet-dev/kubefleet/apis/cluster/v1beta1" 11 | placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" 12 | "github.com/kubefleet-dev/kubefleet/pkg/scheduler/framework" 13 | ) 14 | 15 | var ( 16 | reasonFmt = "taint %+v cannot be tolerated" 17 | ) 18 | 19 | // Filter allows the plugin to connect to the Filter extension point in the scheduling framework. 20 | func (p *Plugin) Filter( 21 | _ context.Context, 22 | _ framework.CycleStatePluginReadWriter, 23 | policy placementv1beta1.PolicySnapshotObj, 24 | cluster *clusterv1beta1.MemberCluster, 25 | ) (status *framework.Status) { 26 | taint, isUntolerated := findUntoleratedTaint(cluster.Spec.Taints, policy.GetPolicySnapshotSpec().Tolerations()) 27 | if !isUntolerated { 28 | return nil 29 | } 30 | policyRef := klog.KObj(policy) 31 | klog.V(2).InfoS("Cluster is unschedulable, because taint cannot be tolerated", "clusterSchedulingPolicySnapshot", policyRef, "taint", taint) 32 | return framework.NewNonErrorStatus(framework.ClusterUnschedulable, p.Name(), fmt.Sprintf(reasonFmt, taint)) 33 | } 34 | 35 | func findUntoleratedTaint(taints []clusterv1beta1.Taint, tolerations []placementv1beta1.Toleration) (*clusterv1beta1.Taint, bool) { 36 | for _, taint := range taints { 37 | if !tolerationsTolerateTaint(taint, tolerations) { 38 | return &taint, true 39 | } 40 | } 41 | return nil, false 42 | } 43 | 44 | func tolerationsTolerateTaint(taint clusterv1beta1.Taint, tolerations []placementv1beta1.Toleration) bool { 45 | for _, toleration := range tolerations { 46 | if canTolerationTolerateTaint(taint, toleration) { 47 | return true 48 | } 49 | } 50 | return false 51 | } 52 | 53 | func canTolerationTolerateTaint(taint clusterv1beta1.Taint, toleration placementv1beta1.Toleration) bool { 54 | if toleration.Operator == corev1.TolerationOpExists { 55 | if toleration.Key == "" || toleration.Key == taint.Key { 56 | return toleration.Effect == taint.Effect || toleration.Effect == "" 57 | } 58 | } 59 | if toleration.Operator == corev1.TolerationOpEqual { 60 | if toleration.Key == taint.Key && toleration.Value == taint.Value { 61 | return toleration.Effect == taint.Effect || toleration.Effect == "" 62 | } 63 | } 64 | return false 65 | } 66 | -------------------------------------------------------------------------------- /test/utils/controller/controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package controller provides a fake controller for testing. 18 | package controller 19 | 20 | import ( 21 | "context" 22 | "sort" 23 | "sync" 24 | 25 | "github.com/google/go-cmp/cmp" 26 | "github.com/google/go-cmp/cmp/cmpopts" 27 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | ) 29 | 30 | // FakeController is a fake controller which only stores one key. 31 | type FakeController struct { 32 | key string 33 | mu sync.RWMutex 34 | } 35 | 36 | // ResetQueue resets the value in the queue. 37 | func (f *FakeController) ResetQueue() { 38 | f.mu.Lock() 39 | defer f.mu.Unlock() 40 | f.key = "" 41 | } 42 | 43 | // Enqueue enqueues a string type key. 44 | func (f *FakeController) Enqueue(obj interface{}) { 45 | key, ok := obj.(string) 46 | if !ok { 47 | return 48 | } 49 | f.mu.Lock() 50 | f.key = key 51 | f.mu.Unlock() 52 | } 53 | 54 | // Run does nothing. 55 | func (f *FakeController) Run(_ context.Context, _ int) error { 56 | return nil 57 | } 58 | 59 | // Key returns the key stored in the queue. 60 | func (f *FakeController) Key() string { 61 | f.mu.RLock() 62 | defer f.mu.RUnlock() 63 | return f.key 64 | } 65 | 66 | // CompareConditions compares two condition slices and returns a string with the differences. 67 | func CompareConditions(wantConditions, gotConditions []v1.Condition) string { 68 | ignoreOption := cmpopts.IgnoreFields(v1.Condition{}, "LastTransitionTime", "ObservedGeneration", "Message") 69 | // we need to sort each condition slice by type before comparing 70 | sort.SliceStable(wantConditions, func(i, j int) bool { 71 | return wantConditions[i].Type < wantConditions[j].Type 72 | }) 73 | sort.SliceStable(gotConditions, func(i, j int) bool { 74 | return gotConditions[i].Type < gotConditions[j].Type 75 | }) 76 | return cmp.Diff(wantConditions, gotConditions, ignoreOption) 77 | } 78 | -------------------------------------------------------------------------------- /pkg/utils/labels/labels.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2025 The KubeFleet Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package labels provides utils related to object labels. 18 | package labels 19 | 20 | import ( 21 | "fmt" 22 | "strconv" 23 | 24 | "sigs.k8s.io/controller-runtime/pkg/client" 25 | 26 | fleetv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" 27 | ) 28 | 29 | // ExtractResourceIndexFromResourceSnapshot extracts the resource index from the label of a type of resourceSnapshot. 30 | func ExtractResourceIndexFromResourceSnapshot(snapshot client.Object) (int, error) { 31 | return ExtractIndex(snapshot, fleetv1beta1.ResourceIndexLabel) 32 | } 33 | 34 | // ExtractResourceSnapshotIndexFromWork extracts the resource snapshot index from the work. 35 | func ExtractResourceSnapshotIndexFromWork(work client.Object) (int, error) { 36 | return ExtractIndex(work, fleetv1beta1.ParentResourceSnapshotIndexLabel) 37 | } 38 | 39 | // ExtractIndex extracts the numeric index from the a label with labelKey. 40 | func ExtractIndex(object client.Object, labelKey string) (int, error) { 41 | indexStr := object.GetLabels()[labelKey] 42 | v, err := strconv.Atoi(indexStr) 43 | if err != nil || v < 0 { 44 | return -1, fmt.Errorf("invalid resource index %q, error: %w", indexStr, err) 45 | } 46 | return v, nil 47 | } 48 | 49 | // ParsePolicyIndexFromLabel extracts and validates the policy index from a ClusterSchedulingPolicySnapshot label. 50 | // Works with both ClusterSchedulingPolicySnapshot and PolicySnapshot interfaces. 51 | func ParsePolicyIndexFromLabel(policySnapshot client.Object) (int, error) { 52 | labels := policySnapshot.GetLabels() 53 | if labels == nil { 54 | return -1, fmt.Errorf("no labels found on policy snapshot") 55 | } 56 | 57 | indexLabel := labels[fleetv1beta1.PolicyIndexLabel] 58 | v, err := strconv.Atoi(indexLabel) 59 | if err != nil || v < 0 { 60 | return -1, fmt.Errorf("invalid policy index %q, error: %w", indexLabel, err) 61 | } 62 | return v, nil 63 | } 64 | --------------------------------------------------------------------------------