Showing preview only (515K chars total). Download the full file or copy to clipboard to get everything.
Repository: kubernetes-sigs/mcs-api
Branch: master
Commit: 4546fabf7251
Files: 138
Total size: 476.5 KB
Directory structure:
gitextract_e338xpw6/
├── .github/
│ └── workflows/
│ └── auto-label.yml
├── .gitignore
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── Makefile
├── OWNERS
├── README.md
├── RELEASE.md
├── SECURITY.md
├── SECURITY_CONTACTS
├── code-of-conduct.md
├── config/
│ ├── crd/
│ │ ├── embed.go
│ │ ├── multicluster.x-k8s.io_serviceexports.yaml
│ │ └── multicluster.x-k8s.io_serviceimports.yaml
│ ├── crd-base/
│ │ ├── multicluster.x-k8s.io_serviceexports.yaml
│ │ └── multicluster.x-k8s.io_serviceimports.yaml
│ └── rbac/
│ └── role.yaml
├── conformance/
│ ├── clusterip_service_dns.go
│ ├── conformance_suite.go
│ ├── conformance_suite_test.go
│ ├── connectivity.go
│ ├── endpoint_slice.go
│ ├── framework.go
│ ├── go.mod
│ ├── go.sum
│ ├── headless_service_dns.go
│ ├── k8s_objects.go
│ ├── report.go
│ ├── report_template.gohtml
│ └── service_import.go
├── controllers/
│ ├── cmd/
│ │ └── servicecontroller/
│ │ └── servicecontroller.go
│ ├── common.go
│ ├── controllers_suite_test.go
│ ├── endpointslice.go
│ ├── endpointslice_test.go
│ ├── go.mod
│ ├── go.sum
│ ├── service.go
│ ├── serviceimport.go
│ └── serviceimport_test.go
├── demo/
│ ├── .gitignore
│ ├── demo.sh
│ ├── edit-meta
│ ├── reset.sh
│ ├── udemo.sh
│ └── yaml/
│ ├── dep1.yaml
│ ├── dep2.yaml
│ ├── serviceimport-with-vip.yaml
│ ├── serviceimport.yaml
│ └── svc.yaml
├── e2e/
│ ├── connectivity_test.go
│ ├── e2e_suite_test.go
│ ├── go.mod
│ ├── go.sum
│ └── localserviceimpact_test.go
├── go.mod
├── go.sum
├── hack/
│ ├── boilerplate/
│ │ ├── boilerplate.go.txt
│ │ ├── boilerplate.py
│ │ ├── boilerplate.py.txt
│ │ └── boilerplate.sh.txt
│ ├── boilerplate.go.txt
│ ├── kube-env.sh
│ ├── update-codegen.sh
│ ├── update-k8s.sh
│ ├── verify-all.sh
│ ├── verify-boilerplate.sh
│ ├── verify-codegen.sh
│ ├── verify-crd-bump-revision.sh
│ ├── verify-crds.sh
│ ├── verify-gofmt.sh
│ └── verify-golint.sh
├── pkg/
│ ├── apis/
│ │ ├── v1alpha1/
│ │ │ ├── BUILD
│ │ │ ├── doc.go
│ │ │ ├── serviceexport.go
│ │ │ ├── serviceimport.go
│ │ │ ├── well_known_labels.go
│ │ │ ├── zz_generated.deepcopy.go
│ │ │ └── zz_generated.register.go
│ │ └── v1beta1/
│ │ ├── BUILD
│ │ ├── doc.go
│ │ ├── serviceexport.go
│ │ ├── serviceimport.go
│ │ ├── well_known_labels.go
│ │ ├── zz_generated.deepcopy.go
│ │ └── zz_generated.register.go
│ └── client/
│ ├── clientset/
│ │ └── versioned/
│ │ ├── clientset.go
│ │ ├── fake/
│ │ │ ├── clientset_generated.go
│ │ │ ├── doc.go
│ │ │ └── register.go
│ │ ├── scheme/
│ │ │ ├── doc.go
│ │ │ └── register.go
│ │ └── typed/
│ │ └── apis/
│ │ ├── v1alpha1/
│ │ │ ├── apis_client.go
│ │ │ ├── doc.go
│ │ │ ├── fake/
│ │ │ │ ├── doc.go
│ │ │ │ ├── fake_apis_client.go
│ │ │ │ ├── fake_serviceexport.go
│ │ │ │ └── fake_serviceimport.go
│ │ │ ├── generated_expansion.go
│ │ │ ├── serviceexport.go
│ │ │ └── serviceimport.go
│ │ └── v1beta1/
│ │ ├── apis_client.go
│ │ ├── doc.go
│ │ ├── fake/
│ │ │ ├── doc.go
│ │ │ ├── fake_apis_client.go
│ │ │ ├── fake_serviceexport.go
│ │ │ └── fake_serviceimport.go
│ │ ├── generated_expansion.go
│ │ ├── serviceexport.go
│ │ └── serviceimport.go
│ ├── informers/
│ │ └── externalversions/
│ │ ├── apis/
│ │ │ ├── interface.go
│ │ │ ├── v1alpha1/
│ │ │ │ ├── interface.go
│ │ │ │ ├── serviceexport.go
│ │ │ │ └── serviceimport.go
│ │ │ └── v1beta1/
│ │ │ ├── interface.go
│ │ │ ├── serviceexport.go
│ │ │ └── serviceimport.go
│ │ ├── factory.go
│ │ ├── generic.go
│ │ └── internalinterfaces/
│ │ └── factory_interfaces.go
│ └── listers/
│ └── apis/
│ ├── v1alpha1/
│ │ ├── expansion_generated.go
│ │ ├── serviceexport.go
│ │ └── serviceimport.go
│ └── v1beta1/
│ ├── expansion_generated.go
│ ├── serviceexport.go
│ └── serviceimport.go
├── scripts/
│ ├── .gitignore
│ ├── c1.yaml
│ ├── c2.yaml
│ ├── coredns-rbac.json
│ ├── down.sh
│ ├── e2e-test.sh
│ ├── up.sh
│ └── util.sh
└── tools/
├── go.mod
├── go.sum
└── tools.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/auto-label.yml
================================================
---
name: Label issues
on:
issues:
types:
- opened
- reopened
jobs:
label_issues:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- run: gh issue edit "$NUMBER" --add-label "$LABELS"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
NUMBER: ${{ github.event.issue.number }}
LABELS: sig/multicluster
================================================
FILE: .gitignore
================================================
*~
.\#*
._*
\#*\#
/_artifacts/
/bazel-*
bin
.classpath
/cluster
/.config/gcloud*/
*.dll
/doc_tmp/
!\.drone\.sec
.DS_Store
*.dylib
.envrc
*.exe
*.exe~
/_gopath/
/.gsutil/
/hack/.test-cmd-auth
**/.hg*
.idea/
*.iml
/junit*.xml
/.make/
.netrwhist
network_closure.sh
*.out
/_output*/
/output*/
.project
*.pyc
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
Session.vim
.settings/**
*.so
*.swo
*.swp
.tags*
*.test
/third_party/pkg
.*.timestamp
/_tmp/
*.un~
.vagrant
!vendor/**/zz_generated.*
.vscode
/www/test_out
report.html
report.yaml
.*.timestamp
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing Guidelines
Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://git.k8s.io/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt:
_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._
## Getting Started
We have full documentation on how to get started contributing here:
<!---
If your repo has certain guidelines for contribution, put them here ahead of the general k8s resources
-->
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
- [Kubernetes Contributor Guide](https://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](https://git.k8s.io/community/contributors/guide#contributing)
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet) - Common resources for existing developers
## Mentorship
- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
## Contact Information
- [Slack](https://kubernetes.slack.com/messages/sig-service-catalog)
- [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-service-catalog)
================================================
FILE: Dockerfile
================================================
# Build the manager binary
FROM golang:1.23 as builder
WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
RUN go mod download
# Copy the go source
COPY controllers/ controllers/
RUN go -C controllers mod download
COPY pkg/ pkg/
# Build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go -C controllers build -a -o /workspace/controller cmd/servicecontroller/servicecontroller.go
# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/controller .
USER nonroot:nonroot
ENTRYPOINT ["/controller"]
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Makefile
================================================
# Copyright 2019 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
DOCKER ?= docker
# TOP is the current directory where this Makefile lives.
TOP := $(dir $(firstword $(MAKEFILE_LIST)))
# ROOT is the root of the mkdocs tree.
ROOT := $(abspath $(TOP))
# Image URL to use all building/pushing image targets
IMG ?= mcs-api-controller:latest
# Need v1 to support defaults in CRDs, unfortunately limiting us to k8s 1.16+
CRD_OPTIONS ?= "crd:crdVersions=v1"
CONTROLLER_GEN=go -C tools run sigs.k8s.io/controller-tools/cmd/controller-gen
# enable Go modules
export GO111MODULE=on
.PHONY: all
all: generate manifests controller verify
.PHONY: e2e-test
e2e-test: export MCS_CONTROLLER_IMAGE := $(IMG)
e2e-test: docker-build
./scripts/e2e-test.sh
.PHONY: demo
demo: export MCS_CONTROLLER_IMAGE := $(IMG)
demo: docker-build
./scripts/up.sh
./demo/demo.sh
./scripts/down.sh
# Build controller binary
.PHONY: controller
controller: generate fmt vet
go -C controllers build -o $(ROOT)/bin/manager cmd/servicecontroller/servicecontroller.go
# Run go fmt against code
.PHONY: fmt
fmt:
for m in . conformance controllers e2e; do go -C $$m fmt ./...; done
# Run go vet against code
.PHONY: vet
vet:
for m in . conformance controllers e2e; do go -C $$m vet ./...; done
# Run generators for Deepcopy funcs and CRDs
.PHONY: generate
generate:
./hack/update-codegen.sh
$(CONTROLLER_GEN) object:headerFile=$(ROOT)/hack/boilerplate.go.txt paths="$(ROOT)/..."
# Generate manifests e.g. CRD, RBAC etc.
.PHONY: manifests
manifests:
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=mcs-derived-service-manager output:rbac:dir="$(ROOT)/config/rbac" webhook schemapatch:manifests="$(ROOT)/config/crd-base" paths="$(ROOT)/..." output:crd:none output:schemapatch:dir="$(ROOT)/config/crd"
# Run tests
.PHONY: test
test: generate fmt vet manifests
for m in . controllers; do go -C $$m test ./... -coverprofile cover.out; done
# Install CRD's and example resources to a pre-existing cluster.
.PHONY: install
install: manifests crd
# Remove installed CRD's and CR's.
.PHONY: uninstall
uninstall:
./hack/delete-crds.sh
# Run static analysis.
.PHONY: verify
verify:
./hack/verify-all.sh -v
# Build docker containers
.PHONY: docker-build
docker-build: generate fmt vet manifests
docker build . -t ${IMG}
# Push the docker image
.PHONY: docker-push
docker-push: docker-build
docker push ${IMG}
# Run against the configured Kubernetes cluster in ~/.kube/config
run: generate fmt vet manifests
go run ./cmd/servicecontroller/servicecontroller.go
================================================
FILE: OWNERS
================================================
# See the OWNERS docs at https://go.k8s.io/owners
reviewers:
- jeremyot
- lauralorenz
- skitt
- MrFreezeex
- munnerz
- RainbowMango
- ryanzhang-oss
# Pending org membership
# - jnpacker
approvers:
- JeremyOT
- lauralorenz
- skitt
emeritus_approvers:
- pmorie
labels:
- sig/multicluster
================================================
FILE: README.md
================================================
# Multi-cluster Service APIs
This repository hosts the Multi-Cluster Service APIs. Providers can import packages in this repo to ensure their multi-cluster service controller implementations will be compatible with MCS data planes.
This repo contains the initial implementation according to [KEP-1645][kep].
[kep]: https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api
## Try it out
To see the API in action, run `make demo` to build and run a local demo against
a pair of kind clusters. Alternatively, you can take a self guided tour. Use:
- `./scripts/up.sh` to create a pair of clusters with mutually connected networks
and install the `mcs-api-controller`.
_This will use a pre-existing controller image if available, it's recommended
to run `make docker-build` first._
- `./demo/demo.sh` to run the same demo as above against your newly created
clusters (must run `./scripts/up.sh` first).
- `./scripts/down.sh` to tear down your clusters.
## Community, discussion, contribution, and support
Learn how to engage with the Kubernetes community on the [community page](http://kubernetes.io/community/).
You can reach the maintainers of this project at:
- [Slack](https://kubernetes.slack.com/messages/sig-multicluster)
- [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-multicluster)
[Our meeting schedule is here]( https://github.com/kubernetes/community/tree/master/sig-multicluster#meetings)
## Technical Leads
- @pmorie
- @jeremyot
### Code of conduct
Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md).
================================================
FILE: RELEASE.md
================================================
# Release Process
The Kubernetes Template Project is released on an as-needed basis. The process is as follows:
1. An issue is proposing a new release with a changelog since the last release
1. All [OWNERS](OWNERS) must LGTM this release
1. An OWNER runs `git tag -s $VERSION` and inserts the changelog and pushes the tag with `git push $VERSION`
1. The release issue is closed
1. An announcement email is sent to `kubernetes-dev@googlegroups.com` with the subject `[ANNOUNCE] kubernetes-template-project $VERSION is released`
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Security Announcements
Join the [kubernetes-security-announce] group for security and vulnerability announcements.
You can also subscribe to an RSS feed of the above using [this link][kubernetes-security-announce-rss].
## Reporting a Vulnerability
Instructions for reporting a vulnerability can be found on the
[Kubernetes Security and Disclosure Information] page.
## Supported Versions
Information about supported Kubernetes versions can be found on the
[Kubernetes version and version skew support policy] page on the Kubernetes website.
[kubernetes-security-announce]: https://groups.google.com/forum/#!forum/kubernetes-security-announce
[kubernetes-security-announce-rss]: https://groups.google.com/forum/feed/kubernetes-security-announce/msgs/rss_v2_0.xml?num=50
[Kubernetes version and version skew support policy]: https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-versions
[Kubernetes Security and Disclosure Information]: https://kubernetes.io/docs/reference/issues-security/security/#report-a-vulnerability
================================================
FILE: SECURITY_CONTACTS
================================================
# Defined below are the security contacts for this repo.
#
# They are the contact point for the Product Security Committee to reach out
# to for triaging and handling of incoming issues.
#
# The below names agree to abide by the
# [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy)
# and will be removed and replaced if they violate that agreement.
#
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
# INSTRUCTIONS AT https://kubernetes.io/security/
pmorie
JeremyOT
================================================
FILE: code-of-conduct.md
================================================
# Kubernetes Community Code of Conduct
Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)
================================================
FILE: config/crd/embed.go
================================================
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package crd
import _ "embed" // import embed to be able to use go:embed
var (
// ServiceExportCRD is the embedded YAML for the ServiceExport CRD
//go:embed multicluster.x-k8s.io_serviceexports.yaml
ServiceExportCRD []byte
// ServiceImportCRD is the embedded YAML for the ServiceImport CRD
//go:embed multicluster.x-k8s.io_serviceimports.yaml
ServiceImportCRD []byte
)
const (
// ReleaseVersionLabel is the label which indicate the release version
ReleaseVersionLabel = "multicluster.x-k8s.io/release-version"
// CustomResourceDefinitionSchemaRevisionLabel is the label which holds the CRD schema revision
CustomResourceDefinitionSchemaRevisionLabel = "multicluster.x-k8s.io/crd-schema-revision"
)
================================================
FILE: config/crd/multicluster.x-k8s.io_serviceexports.yaml
================================================
# Copyright 2020 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: serviceexports.multicluster.x-k8s.io
labels:
multicluster.x-k8s.io/release-version: "v0.5.0"
# The revision is updated on each CRD change and reset back to 0 on every new version.
# It can be used together with the version label when installing those CRDs
# and prevent any downgrades.
multicluster.x-k8s.io/crd-schema-revision: "0"
spec:
group: multicluster.x-k8s.io
scope: Namespaced
names:
plural: serviceexports
singular: serviceexport
kind: ServiceExport
shortNames:
- svcex
- svcexport
versions:
- name: v1alpha1
served: true
storage: false
subresources:
status: {}
additionalPrinterColumns:
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
"schema":
"openAPIV3Schema":
description: |-
ServiceExport declares that the Service with the same name and namespace
as this export should be consumable from other clusters.
type: object
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: spec defines the behavior of a ServiceExport.
type: object
properties:
exportedAnnotations:
description: exportedAnnotations describes the annotations exported. It is optional for implementation.
type: object
additionalProperties:
type: string
exportedLabels:
description: exportedLabels describes the labels exported. It is optional for implementation.
type: object
additionalProperties:
type: string
status:
description: |-
status describes the current state of an exported service.
Service configuration comes from the Service that had the same
name and namespace as this ServiceExport.
Populated by the multi-cluster service implementation's controller.
type: object
properties:
conditions:
type: array
items:
description: Condition contains details for one aspect of the current state of this API Resource.
type: object
required:
- lastTransitionTime
- message
- reason
- status
- type
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
type: string
format: date-time
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
type: string
maxLength: 32768
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
type: integer
format: int64
minimum: 0
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
type: string
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
status:
description: status of the condition, one of True, False, Unknown.
type: string
enum:
- "True"
- "False"
- Unknown
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
type: string
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
- name: v1beta1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
"schema":
"openAPIV3Schema":
description: |-
ServiceExport declares that the Service with the same name and namespace
as this export should be consumable from other clusters.
type: object
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: spec defines the behavior of a ServiceExport.
type: object
properties:
exportedAnnotations:
description: exportedAnnotations describes the annotations exported. It is optional for implementation.
type: object
additionalProperties:
type: string
exportedLabels:
description: exportedLabels describes the labels exported. It is optional for implementation.
type: object
additionalProperties:
type: string
status:
description: |-
status describes the current state of an exported service.
Service configuration comes from the Service that had the same
name and namespace as this ServiceExport.
Populated by the multi-cluster service implementation's controller.
type: object
properties:
conditions:
type: array
items:
description: Condition contains details for one aspect of the current state of this API Resource.
type: object
required:
- lastTransitionTime
- message
- reason
- status
- type
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
type: string
format: date-time
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
type: string
maxLength: 32768
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
type: integer
format: int64
minimum: 0
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
type: string
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
status:
description: status of the condition, one of True, False, Unknown.
type: string
enum:
- "True"
- "False"
- Unknown
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
type: string
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
================================================
FILE: config/crd/multicluster.x-k8s.io_serviceimports.yaml
================================================
# Copyright 2020 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: serviceimports.multicluster.x-k8s.io
labels:
multicluster.x-k8s.io/release-version: "v0.5.0"
# The revision is updated on each CRD change and reset back to 0 on every new version.
# It can be used together with the version label when installing those CRDs
# and prevent any downgrades.
multicluster.x-k8s.io/crd-schema-revision: "0"
spec:
group: multicluster.x-k8s.io
scope: Namespaced
names:
plural: serviceimports
singular: serviceimport
kind: ServiceImport
shortNames:
- svcim
- svcimport
versions:
- name: v1alpha1
served: true
storage: false
subresources:
status: {}
additionalPrinterColumns:
- name: Type
type: string
description: The type of this ServiceImport
jsonPath: .spec.type
- name: IP
type: string
description: The VIP for this ServiceImport
jsonPath: .spec.ips
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
"schema":
"openAPIV3Schema":
description: ServiceImport describes a service imported from clusters in a ClusterSet.
type: object
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: spec defines the behavior of a ServiceImport.
type: object
required:
- ports
- type
properties:
internalTrafficPolicy:
description: |-
InternalTrafficPolicy describes how nodes distribute service traffic they
receive on the ClusterIP. If set to "Local", the proxy will assume that pods
only want to talk to endpoints of the service on the same node as the pod,
dropping the traffic if there are no local endpoints. The default value,
"Cluster", uses the standard behavior of routing to all endpoints evenly
(possibly modified by topology and other features).
type: string
ipFamilies:
description: IPFamilies identifies all the IPFamilies assigned for this ServiceImport.
type: array
maxItems: 2
items:
description: |-
IPFamily represents the IP Family (IPv4 or IPv6). This type is used
to express the family of an IP expressed by a type (e.g. service.spec.ipFamilies).
type: string
ips:
description: ip will be used as the VIP for this service when type is ClusterSetIP.
type: array
maxItems: 2
items:
type: string
ports:
type: array
items:
description: ServicePort represents the port on which the service is exposed
type: object
required:
- port
properties:
appProtocol:
description: |-
The application protocol for this port.
This is used as a hint for implementations to offer richer behavior for protocols that they understand.
This field follows standard Kubernetes label syntax.
Valid values are either:
* Un-prefixed protocol names - reserved for IANA standard service names (as per
RFC-6335 and https://www.iana.org/assignments/service-names).
* Kubernetes-defined prefixed names:
* 'kubernetes.io/h2c' - HTTP/2 over cleartext as described in https://www.rfc-editor.org/rfc/rfc7540
* Other protocols should use implementation-defined prefixed names such as
mycompany.com/my-custom-protocol.
Field can be enabled with ServiceAppProtocol feature gate.
type: string
name:
description: |-
The name of this port within the service. This must be a DNS_LABEL.
All ports within a ServiceSpec must have unique names. When considering
the endpoints for a Service, this must match the 'name' field in the
EndpointPort.
Optional if only one ServicePort is defined on this service.
type: string
port:
description: The port that will be exposed by this service.
type: integer
format: int32
protocol:
description: |-
The IP protocol for this port. Supports "TCP", "UDP", and "SCTP".
Default is TCP.
type: string
x-kubernetes-list-type: atomic
sessionAffinity:
description: |-
Supports "ClientIP" and "None". Used to maintain session affinity.
Enable client IP based session affinity.
Must be ClientIP or None.
Defaults to None.
Ignored when type is Headless
More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies
type: string
sessionAffinityConfig:
description: sessionAffinityConfig contains session affinity configuration.
type: object
properties:
clientIP:
description: clientIP contains the configurations of Client IP based session affinity.
type: object
properties:
timeoutSeconds:
description: |-
timeoutSeconds specifies the seconds of ClientIP type session sticky time.
The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP".
Default value is 10800(for 3 hours).
type: integer
format: int32
trafficDistribution:
description: |-
TrafficDistribution offers a way to express preferences for how traffic
is distributed to Service endpoints. Implementations can use this field
as a hint, but are not required to guarantee strict adherence. If the
field is not set, the implementation will apply its default routing
strategy. If set to "PreferClose", implementations should prioritize
endpoints that are in the same zone.
type: string
type:
description: |-
type defines the type of this service.
Must be ClusterSetIP or Headless.
type: string
enum:
- ClusterSetIP
- Headless
status:
description: |-
status contains information about the exported services that form
the multi-cluster service referenced by this ServiceImport.
type: object
properties:
clusters:
description: |-
clusters is the list of exporting clusters from which this service
was derived.
type: array
items:
description: ClusterStatus contains service configuration mapped to a specific source cluster
type: object
required:
- cluster
properties:
cluster:
description: |-
cluster is the name of the exporting cluster. Must be a valid RFC-1123 DNS
label.
type: string
x-kubernetes-list-map-keys:
- cluster
x-kubernetes-list-type: map
conditions:
type: array
items:
description: Condition contains details for one aspect of the current state of this API Resource.
type: object
required:
- lastTransitionTime
- message
- reason
- status
- type
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
type: string
format: date-time
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
type: string
maxLength: 32768
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
type: integer
format: int64
minimum: 0
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
type: string
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
status:
description: status of the condition, one of True, False, Unknown.
type: string
enum:
- "True"
- "False"
- Unknown
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
type: string
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
- name: v1beta1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Type
type: string
description: The type of this ServiceImport
jsonPath: .spec.type
- name: IP
type: string
description: The VIP for this ServiceImport
jsonPath: .spec.ips
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
"schema":
"openAPIV3Schema":
description: ServiceImport describes a service imported from clusters in a ClusterSet.
type: object
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: spec defines the behavior of a ServiceImport.
type: object
required:
- ports
- type
properties:
internalTrafficPolicy:
description: |-
InternalTrafficPolicy describes how nodes distribute service traffic they
receive on the ClusterIP. If set to "Local", the proxy will assume that pods
only want to talk to endpoints of the service on the same node as the pod,
dropping the traffic if there are no local endpoints. The default value,
"Cluster", uses the standard behavior of routing to all endpoints evenly
(possibly modified by topology and other features).
type: string
ipFamilies:
description: IPFamilies identifies all the IPFamilies assigned for this ServiceImport.
type: array
maxItems: 2
items:
description: |-
IPFamily represents the IP Family (IPv4 or IPv6). This type is used
to express the family of an IP expressed by a type (e.g. service.spec.ipFamilies).
type: string
ips:
description: ip will be used as the VIP for this service when type is ClusterSetIP.
type: array
maxItems: 2
items:
type: string
ports:
type: array
items:
description: ServicePort represents the port on which the service is exposed
type: object
required:
- port
properties:
appProtocol:
description: |-
The application protocol for this port.
This is used as a hint for implementations to offer richer behavior for protocols that they understand.
This field follows standard Kubernetes label syntax.
Valid values are either:
* Un-prefixed protocol names - reserved for IANA standard service names (as per
RFC-6335 and https://www.iana.org/assignments/service-names).
* Kubernetes-defined prefixed names:
* 'kubernetes.io/h2c' - HTTP/2 over cleartext as described in https://www.rfc-editor.org/rfc/rfc7540
* Other protocols should use implementation-defined prefixed names such as
mycompany.com/my-custom-protocol.
Field can be enabled with ServiceAppProtocol feature gate.
type: string
name:
description: |-
The name of this port within the service. This must be a DNS_LABEL.
All ports within a ServiceSpec must have unique names. When considering
the endpoints for a Service, this must match the 'name' field in the
EndpointPort.
Optional if only one ServicePort is defined on this service.
type: string
port:
description: The port that will be exposed by this service.
type: integer
format: int32
protocol:
description: |-
The IP protocol for this port. Supports "TCP", "UDP", and "SCTP".
Default is TCP.
type: string
x-kubernetes-list-type: atomic
sessionAffinity:
description: |-
Supports "ClientIP" and "None". Used to maintain session affinity.
Enable client IP based session affinity.
Must be ClientIP or None.
Defaults to None.
Ignored when type is Headless
More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies
type: string
sessionAffinityConfig:
description: sessionAffinityConfig contains session affinity configuration.
type: object
properties:
clientIP:
description: clientIP contains the configurations of Client IP based session affinity.
type: object
properties:
timeoutSeconds:
description: |-
timeoutSeconds specifies the seconds of ClientIP type session sticky time.
The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP".
Default value is 10800(for 3 hours).
type: integer
format: int32
trafficDistribution:
description: |-
TrafficDistribution offers a way to express preferences for how traffic
is distributed to Service endpoints. Implementations can use this field
as a hint, but are not required to guarantee strict adherence. If the
field is not set, the implementation will apply its default routing
strategy. If set to "PreferClose", implementations should prioritize
endpoints that are in the same zone.
type: string
type:
description: |-
type defines the type of this service.
Must be ClusterSetIP or Headless.
type: string
enum:
- ClusterSetIP
- Headless
status:
description: |-
status contains information about the exported services that form
the multi-cluster service referenced by this ServiceImport.
type: object
properties:
clusters:
description: |-
clusters is the list of exporting clusters from which this service
was derived.
type: array
items:
description: ClusterStatus contains service configuration mapped to a specific source cluster
type: object
required:
- cluster
properties:
cluster:
description: |-
cluster is the name of the exporting cluster. Must be a valid RFC-1123 DNS
label.
type: string
x-kubernetes-list-map-keys:
- cluster
x-kubernetes-list-type: map
conditions:
type: array
items:
description: Condition contains details for one aspect of the current state of this API Resource.
type: object
required:
- lastTransitionTime
- message
- reason
- status
- type
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
type: string
format: date-time
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
type: string
maxLength: 32768
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
type: integer
format: int64
minimum: 0
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
type: string
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
status:
description: status of the condition, one of True, False, Unknown.
type: string
enum:
- "True"
- "False"
- Unknown
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
type: string
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
================================================
FILE: config/crd-base/multicluster.x-k8s.io_serviceexports.yaml
================================================
# Copyright 2020 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: serviceexports.multicluster.x-k8s.io
labels:
multicluster.x-k8s.io/release-version: "v0.5.0"
# The revision is updated on each CRD change and reset back to 0 on every new version.
# It can be used together with the version label when installing those CRDs
# and prevent any downgrades.
multicluster.x-k8s.io/crd-schema-revision: "0"
spec:
group: multicluster.x-k8s.io
scope: Namespaced
names:
plural: serviceexports
singular: serviceexport
kind: ServiceExport
shortNames:
- svcex
- svcexport
versions:
- name: v1alpha1
served: true
storage: false
subresources:
status: {}
additionalPrinterColumns:
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
- name: v1beta1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
================================================
FILE: config/crd-base/multicluster.x-k8s.io_serviceimports.yaml
================================================
# Copyright 2020 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: serviceimports.multicluster.x-k8s.io
labels:
multicluster.x-k8s.io/release-version: "v0.5.0"
# The revision is updated on each CRD change and reset back to 0 on every new version.
# It can be used together with the version label when installing those CRDs
# and prevent any downgrades.
multicluster.x-k8s.io/crd-schema-revision: "0"
spec:
group: multicluster.x-k8s.io
scope: Namespaced
names:
plural: serviceimports
singular: serviceimport
kind: ServiceImport
shortNames:
- svcim
- svcimport
versions:
- name: v1alpha1
served: true
storage: false
subresources:
status: {}
additionalPrinterColumns:
- name: Type
type: string
description: The type of this ServiceImport
jsonPath: .spec.type
- name: IP
type: string
description: The VIP for this ServiceImport
jsonPath: .spec.ips
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
- name: v1beta1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Type
type: string
description: The type of this ServiceImport
jsonPath: .spec.type
- name: IP
type: string
description: The VIP for this ServiceImport
jsonPath: .spec.ips
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
================================================
FILE: config/rbac/role.yaml
================================================
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: mcs-derived-service-manager
rules:
- apiGroups:
- ""
resources:
- services
- services/status
verbs:
- create
- get
- list
- patch
- update
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- get
- list
- patch
- update
- watch
- apiGroups:
- multicluster.x-k8s.io
resources:
- serviceimports
verbs:
- get
- list
- patch
- update
- watch
================================================
FILE: conformance/clusterip_service_dns.go
================================================
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conformance
import (
"context"
"fmt"
"regexp"
"strconv"
"strings"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/mcs-api/pkg/apis/v1beta1"
)
var _ = Describe("", Label(OptionalLabel, DNSLabel, ClusterIPLabel), func() {
t := newTestDriver()
Specify("A DNS lookup of the <service>.<ns>.svc."+dnsDomain+" domain for a ClusterIP service should resolve to the "+
"clusterset IP", func(ctx context.Context) {
AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#dns")
for _, client := range clients {
serviceImport := t.awaitServiceImport(ctx, &client, t.helloService.Name, false,
func(g Gomega, serviceImport *v1beta1.ServiceImport) {
g.Expect(serviceImport.Spec.IPs).ToNot(BeEmpty(), "ServiceImport on cluster %q does not contain an IP", client.name)
})
By(fmt.Sprintf("Found ServiceImport on cluster %q with clusterset IPs %v",
client.name, strings.Join(serviceImport.Spec.IPs, ",")))
for _, clusterSetIP := range serviceImport.Spec.IPs {
command := []string{"sh", "-c", fmt.Sprintf("nslookup -type=%s %s.%s.svc.%s.",
dnsRecordTypeOf(ipFamilyOf(clusterSetIP)), t.helloService.Name, t.namespace, dnsDomain)}
By(fmt.Sprintf("Executing %s command %q on cluster %q", ipFamilyOf(clusterSetIP),
strings.Join(command, " "), client.name))
t.awaitCmdOutputMatches(&client, command, clusterSetIP, 1, reportNonConformant(""))
}
}
})
Specify("A DNS SRV query of the <service>.<ns>.svc."+dnsDomain+" domain for a ClusterIP service should return valid SRV "+
"records", func() {
AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#dns")
domainName := fmt.Sprintf("%s.%s.svc.%s", t.helloService.Name, t.namespace, dnsDomain)
for _, client := range clients {
expSRVRecs := []srvRecord{{
port: t.helloService.Spec.Ports[0].Port,
domainName: domainName,
}}
srvRecs := t.expectSRVRecords(&client, domainName, len(expSRVRecs))
Expect(srvRecs).To(Equal(expSRVRecs), reportNonConformant(
fmt.Sprintf("Received SRV records %v do not match the expected records %v", srvRecs, expSRVRecs)))
}
})
Specify("DNS lookups of the <service>.<ns>.svc.cluster.local domain for a ClusterIP service should only resolve "+
"local services", func(ctx context.Context) {
AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#dns")
By(fmt.Sprintf("Retrieving local Service on cluster %q", clients[0].name))
var resolvedIP string
Eventually(func(ctx context.Context) string {
svc, err := clients[0].k8s.CoreV1().Services(t.namespace).Get(ctx, t.helloService.Name, metav1.GetOptions{})
Expect(err).ToNot(HaveOccurred(), "Error retrieving the local Service")
resolvedIP = svc.Spec.ClusterIP
return resolvedIP
}).WithContext(ctx).Within(20*time.Second).ProbeEvery(1*time.Second).ShouldNot(BeEmpty(), "The service was not assigned a cluster IP")
By(fmt.Sprintf("Found local Service cluster IP %q", resolvedIP))
// Add trailing dot to prevent search domain from being appended
command := []string{"sh", "-c", fmt.Sprintf("nslookup -type=%s %s.%s.svc.cluster.local.",
dnsRecordTypeOf(ipFamilyOf(resolvedIP)), t.helloService.Name, t.namespace)}
By(fmt.Sprintf("Executing command %q on cluster %q", strings.Join(command, " "), clients[0].name))
t.awaitCmdOutputMatches(&clients[0], command, resolvedIP, 1, reportNonConformant(""))
})
})
func (t *testDriver) expectSRVRecords(c *clusterClients, domainName string, expectedCount int) []srvRecord {
// Add trailing dot to prevent search domain from being appended
command := []string{"sh", "-c", "nslookup -type=SRV " + domainName + "."}
By(fmt.Sprintf("Executing command %q on cluster %q", strings.Join(command, " "), c.name))
var srvRecs []srvRecord
Eventually(func(g Gomega) {
srvRecs = parseSRVRecords(t.execCmdOnRequestPod(c, command))
g.Expect(srvRecs).To(HaveLen(expectedCount),
fmt.Sprintf("Expected %d SRV records but got %d: %v", expectedCount, len(srvRecs), srvRecs))
}, 20, 1).Should(Succeed(), reportNonConformant(""))
return srvRecs
}
// Match SRV records from nslookup of the form:
//
// hello.mcs-conformance-1686874467.svc.clusterset.local service = 0 50 42 hello.mcs-conformance-1686874467.svc.clusterset.local
//
// to extract the port and target domain name (the last two tokens)
var srvRecordRegEx = regexp.MustCompile(`.*=\s*\d*\s*\d*\s*(\d*)\s*([a-zA-Z0-9-.]*)`)
type srvRecord struct {
port int32
domainName string
}
func (s srvRecord) String() string {
return fmt.Sprintf("port:%d, domainName:%q", s.port, s.domainName)
}
func parseSRVRecords(str string) []srvRecord {
var recs []srvRecord
matches := srvRecordRegEx.FindAllStringSubmatch(str, -1)
for i := range matches {
// First match at index 0 is the full text that was matched; index 1 is the port and index 2 is the domain name.
port, _ := strconv.ParseInt(matches[i][1], 10, 32)
domainName := matches[i][2]
// Strip trailing period from FQDN (some nslookup versions include it)
domainName = strings.TrimSuffix(domainName, ".")
recs = append(recs, srvRecord{
port: int32(port),
domainName: domainName,
})
}
return recs
}
================================================
FILE: conformance/conformance_suite.go
================================================
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conformance
import (
"cmp"
"context"
"errors"
"flag"
"fmt"
"math/rand"
"slices"
"strings"
"testing"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/types"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
rest "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
k8snet "k8s.io/utils/net"
"sigs.k8s.io/mcs-api/pkg/apis/v1beta1"
mcsclient "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned"
)
type clusterClients struct {
name string
k8s kubernetes.Interface
mcs mcsclient.Interface
rest *rest.Config
}
var (
contexts string
clients []clusterClients
loadingRules *clientcmd.ClientConfigLoadingRules
skipVerifyEndpointSliceManagedBy bool
dnsDomain string
organization string
project string
version string
url string
)
// TestConformance runs the conformance test.
func TestConformance(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Conformance Suite")
}
func init() {
loadingRules = clientcmd.NewDefaultClientConfigLoadingRules()
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
flag.StringVar(&loadingRules.ExplicitPath, "kubeconfig", "", "absolute path(s) to the kubeconfig file(s)")
flag.StringVar(&contexts, "contexts", "", "comma-separated list of contexts to use")
flag.BoolVar(&skipVerifyEndpointSliceManagedBy, "skip-verify-eps-managed-by", false,
fmt.Sprintf("The MSC spec states that any EndpointSlice created by an mcs-controller must be marked as managed by "+
"the mcs-controller. By default, the conformance test verifies that the %q label on MCS EndpointSlices is not equal to %q. "+
"However with some implementations, MCS EndpointSlices may be created and managed by K8s. If this flag is set to true, "+
"the test only verifies the presence of the label.",
discoveryv1.LabelManagedBy, K8sEndpointSliceManagedByName))
flag.StringVar(&dnsDomain, "dns-domain", "clusterset.local", "The DNS domain suffix used for multi-cluster services. "+
"The default is \"clusterset.local\" as specified by the MCS spec, but some implementations may use a custom domain.")
flag.StringVar(&organization, "organization", "", "Name of the organization responsible for the MCS implementation")
flag.StringVar(&project, "project", "", "Name of the MCS implementation project being tested")
flag.StringVar(&version, "version", "", "Version of the MCS implementation being tested")
flag.StringVar(&url, "url", "", "A URL pointing to the MCS implementation project or documentation")
}
var _ = BeforeSuite(func(ctx context.Context) {
Expect(setupClients(ctx)).To(Succeed(), "Test suite set up failed")
})
func setupClients(ctx context.Context) error {
splitContexts := strings.Split(contexts, ",")
clients = make([]clusterClients, len(splitContexts))
accumulatedErrors := []error{}
for i, kubeContext := range splitContexts {
err := func() error {
overrides := clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults}
overrides.CurrentContext = kubeContext
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &overrides)
rawConfig, err := clientConfig.RawConfig()
if err != nil {
return fmt.Errorf("error setting up a Kubernetes API client on context %s: %w", kubeContext, err)
}
name := kubeContext
if name == "" {
name = rawConfig.CurrentContext
}
configContext, ok := rawConfig.Contexts[name]
if ok {
name = configContext.Cluster
}
restConfig, err := clientConfig.ClientConfig()
if err != nil {
return fmt.Errorf("error setting up a Kubernetes API client on context %s: %w", name, err)
}
k8sClient, err := kubernetes.NewForConfig(restConfig)
if err != nil {
return fmt.Errorf("error setting up a Kubernetes API client on context %s: %w", name, err)
}
mcsClient, err := mcsclient.NewForConfig(restConfig)
if err != nil {
return fmt.Errorf("error setting up an MCS API client on context %s: %w", name, err)
}
if _, err := mcsClient.MulticlusterV1beta1().ServiceExports("").List(ctx, metav1.ListOptions{}); err != nil {
return fmt.Errorf("error listing ServiceExports on context %s: %w. Is the MCS API installed?", name, err)
}
if _, err := mcsClient.MulticlusterV1beta1().ServiceImports("").List(ctx, metav1.ListOptions{}); err != nil {
return fmt.Errorf("error listing ServiceImports on context %s: %w. Is the MCS API installed?", name, err)
}
clients[i] = clusterClients{name: name, k8s: k8sClient, mcs: mcsClient, rest: restConfig}
return nil
}()
accumulatedErrors = append(accumulatedErrors, err)
}
return errors.Join(accumulatedErrors...)
}
type testDriver struct {
namespace string
helloService *corev1.Service
helloServiceExport *v1beta1.ServiceExport
helloDeployment *appsv1.Deployment
requestPod *corev1.Pod
autoExportService bool
}
func newTestDriver() *testDriver {
t := &testDriver{}
BeforeEach(func() {
t.namespace = fmt.Sprintf("mcs-conformance-%v", rand.Uint32())
t.helloService = newHelloService()
t.helloServiceExport = newHelloServiceExport()
t.helloDeployment = newHelloDeployment()
t.requestPod = newRequestPod()
t.autoExportService = true
})
JustBeforeEach(func(ctx context.Context) {
Expect(clients).ToNot(BeEmpty())
// Set up the shared namespace
for _, client := range clients {
_, err := client.k8s.CoreV1().Namespaces().Create(ctx, &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: t.namespace},
}, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())
}
// Set up the remote service (the first cluster is considered to be the remote)
t.helloService = t.deployHelloService(ctx, &clients[0], t.helloService)
// Start the request pod on all clusters
for _, client := range clients {
t.startRequestPod(ctx, client)
}
if t.autoExportService {
t.createServiceExport(ctx, &clients[0], t.helloServiceExport)
}
})
AfterEach(func(ctx context.Context) {
// Clean up the shared namespace
for _, client := range clients {
err := client.k8s.CoreV1().Namespaces().Delete(ctx, t.namespace, metav1.DeleteOptions{})
if !apierrors.IsNotFound(err) {
Expect(err).ToNot(HaveOccurred())
}
}
})
return t
}
func (t *testDriver) createServiceExport(ctx context.Context, c *clusterClients, serviceExport *v1beta1.ServiceExport) {
_, err := c.mcs.MulticlusterV1beta1().ServiceExports(t.namespace).Create(
ctx, serviceExport, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())
By(fmt.Sprintf("Service \"%s/%s\" exported on cluster %q", t.namespace, helloServiceName, c.name))
}
func (t *testDriver) deleteServiceExport(ctx context.Context, c *clusterClients) {
Expect(c.mcs.MulticlusterV1beta1().ServiceExports(t.namespace).Delete(ctx, helloServiceName,
metav1.DeleteOptions{})).ToNot(HaveOccurred())
By(fmt.Sprintf("Service \"%s/%s\" unexported on cluster %q", t.namespace, helloServiceName, c.name))
}
func (t *testDriver) deployHelloService(ctx context.Context, c *clusterClients, service *corev1.Service) *corev1.Service {
if t.helloDeployment != nil {
_, err := c.k8s.AppsV1().Deployments(t.namespace).Create(ctx, t.helloDeployment, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())
}
deployed, err := c.k8s.CoreV1().Services(t.namespace).Create(ctx, service, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())
By(fmt.Sprintf("Service \"%s/%s\" with IP families %v deployed on cluster %q", deployed.Namespace, deployed.Name,
deployed.Spec.IPFamilies, c.name))
return deployed
}
func (t *testDriver) getServiceImport(ctx context.Context, c *clusterClients, name string) *v1beta1.ServiceImport {
si, err := c.mcs.MulticlusterV1beta1().ServiceImports(t.namespace).Get(ctx, name, metav1.GetOptions{})
if apierrors.IsNotFound(err) || errors.Is(err, context.DeadlineExceeded) ||
(err != nil && strings.Contains(err.Error(), "rate limiter")) {
return nil
}
Expect(err).ToNot(HaveOccurred(), "Error retrieving ServiceImport")
return si
}
func (t *testDriver) awaitServiceImport(ctx context.Context, c *clusterClients, name string, reportNonConformanceOnMissing bool,
verify func(Gomega, *v1beta1.ServiceImport)) *v1beta1.ServiceImport {
var serviceImport *v1beta1.ServiceImport
By(fmt.Sprintf("Retrieving ServiceImport for %q on cluster %q", name, c.name))
Eventually(func(g Gomega, ctx context.Context) {
si := t.getServiceImport(ctx, c, name)
missingMsg := fmt.Sprintf("ServiceImport was not found on cluster %q", c.name)
var missing any = missingMsg
if reportNonConformanceOnMissing {
missing = reportNonConformant(missingMsg)
}
g.Expect(si).NotTo(BeNil(), missing)
serviceImport = si
if verify != nil {
verify(g, serviceImport)
}
// The final run succeeded so cancel any prior non-conformance reported.
cancelNonConformanceReport()
}).WithContext(ctx).Within(20 * time.Second).WithPolling(100 * time.Millisecond).Should(Succeed())
return serviceImport
}
func (t *testDriver) awaitServiceImportIPFamilies(ctx context.Context, c *clusterClients) []corev1.IPFamily {
serviceImport := t.awaitServiceImport(ctx, c, t.helloService.Name, false,
func(g Gomega, serviceImport *v1beta1.ServiceImport) {
g.Expect(serviceImport.Spec.IPFamilies).NotTo(BeEmpty(),
"ServiceImport on cluster %q does not contain an IP family", c.name)
})
return serviceImport.Spec.IPFamilies
}
func (t *testDriver) awaitNoServiceImport(ctx context.Context, c *clusterClients, name, nonConformanceMsg string) {
Eventually(func() bool {
_, err := c.mcs.MulticlusterV1beta1().ServiceImports(t.namespace).Get(ctx, name, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
return true
}
Expect(err).ToNot(HaveOccurred())
return false
}, 20*time.Second, 100*time.Millisecond).Should(BeTrue(), reportNonConformant(nonConformanceMsg))
}
func (t *testDriver) ensureServiceImport(ctx context.Context, c *clusterClients, name, nonConformanceMsg string) {
Consistently(func() error {
_, err := c.mcs.MulticlusterV1beta1().ServiceImports(t.namespace).Get(ctx, name, metav1.GetOptions{})
return err
}, 5*time.Second, 100*time.Millisecond).ShouldNot(HaveOccurred(), reportNonConformant(nonConformanceMsg))
}
func (t *testDriver) ensureNoServiceImport(ctx context.Context, c *clusterClients, name, nonConformanceMsg string) {
Consistently(func() bool {
_, err := c.mcs.MulticlusterV1beta1().ServiceImports(t.namespace).Get(ctx, name, metav1.GetOptions{})
return apierrors.IsNotFound(err)
}, 5*time.Second, 100*time.Millisecond).Should(BeTrue(), reportNonConformant(nonConformanceMsg))
}
func (t *testDriver) awaitServiceExportCondition(ctx context.Context, c *clusterClients, condType v1beta1.ServiceExportConditionType,
wantStatus metav1.ConditionStatus) {
Eventually(func() bool {
se, err := c.mcs.MulticlusterV1beta1().ServiceExports(t.namespace).Get(ctx, helloServiceName, metav1.GetOptions{})
Expect(err).ToNot(HaveOccurred())
cond := meta.FindStatusCondition(se.Status.Conditions, string(condType))
return cond != nil && cond.Status == wantStatus
}, 20*time.Second, 100*time.Millisecond).Should(BeTrue(),
reportNonConformant(fmt.Sprintf("The %s condition was not set to %s", condType, wantStatus)))
}
func (t *testDriver) startRequestPod(ctx context.Context, client clusterClients) {
_, err := client.k8s.CoreV1().Pods(t.namespace).Create(ctx, t.requestPod, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())
Eventually(func() error {
pod, err := client.k8s.CoreV1().Pods(t.namespace).Get(ctx, t.requestPod.Name, metav1.GetOptions{})
if err != nil {
return err
}
if pod.Status.Phase != corev1.PodRunning {
return fmt.Errorf("pod is not running yet, current status %v", pod.Status.Phase)
}
return nil
}, 20, 1).Should(Succeed())
}
func (t *testDriver) execCmdOnRequestPod(c *clusterClients, command []string) string {
stdout, _, _ := execCmd(c.k8s, c.rest, t.requestPod.Name, t.namespace, command)
return string(stdout)
}
func (t *testDriver) awaitCmdOutputMatches(c *clusterClients, command []string, expected any, nIter int, msg func() string) {
var matcher types.GomegaMatcher
switch v := expected.(type) {
case string:
matcher = ContainSubstring(v)
case types.GomegaMatcher:
matcher = v
}
Eventually(func(g Gomega) {
output := t.execCmdOnRequestPod(c, command)
g.Expect(output).To(matcher, "Command output")
}).Within(time.Duration(20*int64(nIter))*time.Second).ProbeEvery(time.Second).MustPassRepeatedly(nIter).Should(Succeed(), msg)
}
func (t *testDriver) awaitServicePodIP(ctx context.Context, c *clusterClients) string {
By(fmt.Sprintf("Awaiting service deployment pod IP on cluster %q", c.name))
servicePodIP := ""
Eventually(func(g Gomega, ctx context.Context) {
pods, err := c.k8s.CoreV1().Pods(t.namespace).List(ctx, metav1.ListOptions{
LabelSelector: metav1.FormatLabelSelector(newHelloDeployment().Spec.Selector),
})
g.Expect(err).NotTo(HaveOccurred())
g.Expect(pods.Items).NotTo(BeEmpty())
servicePodIP = pods.Items[0].Status.PodIP
g.Expect(servicePodIP).NotTo(BeEmpty(), "Service deployment pod was not allocated an IP")
}).WithContext(ctx).Within(20 * time.Second).WithPolling(100 * time.Millisecond).Should(Succeed())
By(fmt.Sprintf("Retrieved service deployment pod IP %q", servicePodIP))
return servicePodIP
}
func (t *testDriver) execPortConnectivityCommand(ctx context.Context, port int, matchStr string, nIter int) {
for _, client := range clients {
for _, ipFamily := range t.awaitServiceImportIPFamilies(ctx, &client) {
serviceFQDN := fmt.Sprintf("%s.%s.svc.%s", t.helloService.Name, t.namespace, dnsDomain)
command := []string{"sh", "-c", ncCommand(ipFamily, serviceFQDN, port)}
By(fmt.Sprintf("Executing %s command %q on cluster %q", ipFamily, strings.Join(command, " "), client.name))
t.awaitCmdOutputMatches(&client, command, matchStr, nIter, reportNonConformant(""))
}
}
}
type twoClusterTestDriver struct {
*testDriver
helloService2 *corev1.Service
helloServiceExport2 *v1beta1.ServiceExport
}
func newTwoClusterTestDriver(t *testDriver) *twoClusterTestDriver {
tt := &twoClusterTestDriver{testDriver: t}
BeforeEach(func() {
requireTwoClusters()
tt.helloService2 = newHelloService()
tt.helloServiceExport2 = newHelloServiceExport()
t.autoExportService = false
})
JustBeforeEach(func(ctx context.Context) {
t.createServiceExport(ctx, &clients[0], t.helloServiceExport)
// The conflict resolution policy in the MCS spec (KEP 1645) allows an implementation to favor maintaining
// service continuity and avoiding potentially disruptive changes, as such, an implementation may choose the
// first observed exported service when resolving conflicts. To support this, verify the ServiceImport is
// created on the first cluster prior to deploying on the second cluster.
t.awaitServiceImport(ctx, &clients[0], helloServiceName, false, nil)
// Delay a little before deploying on the second cluster to ensure the first cluster's ServiceExport timestamp
// is older so conflict checking is deterministic for implementations that use the timestamp when resolving conflicts.
// Make the delay at least 1 sec as creation timestamps have seconds granularity.
time.Sleep(1100 * time.Millisecond)
t.deployHelloService(ctx, &clients[1], tt.helloService2)
t.createServiceExport(ctx, &clients[1], tt.helloServiceExport2)
})
return tt
}
func toMCSPorts(from []corev1.ServicePort) []v1beta1.ServicePort {
var mcsPorts []v1beta1.ServicePort
for _, port := range from {
mcsPorts = append(mcsPorts, v1beta1.ServicePort{
Name: port.Name,
Protocol: port.Protocol,
Port: port.Port,
AppProtocol: port.AppProtocol,
})
}
return sortMCSPorts(mcsPorts)
}
func sortMCSPorts(p []v1beta1.ServicePort) []v1beta1.ServicePort {
slices.SortFunc(p, func(a, b v1beta1.ServicePort) int {
return cmp.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name))
})
return p
}
func requireTwoClusters() {
if len(clients) < 2 {
Skip("This test requires at least 2 clusters - skipping")
}
}
func addressTypeOf(f corev1.IPFamily) discoveryv1.AddressType {
if f == corev1.IPv4Protocol {
return discoveryv1.AddressTypeIPv4
}
return discoveryv1.AddressTypeIPv6
}
func dnsRecordTypeOf(f corev1.IPFamily) string {
if f == corev1.IPv4Protocol {
return "A"
}
return "AAAA"
}
func ipFamilyOf(ip string) corev1.IPFamily {
f := k8snet.IPFamilyOfString(ip)
Expect(f).NotTo(Equal(k8snet.IPFamilyUnknown))
if f == k8snet.IPv4 {
return corev1.IPv4Protocol
}
return corev1.IPv6Protocol
}
// ncCommand creates a shell command that connects to a service using nc with the specified IP family.
// The netshoot image provides nc which supports -4/-6 flags for forcing IP family.
func ncCommand(ipFamily corev1.IPFamily, serviceFQDN string, port int) string {
ipFlag := "-4"
if ipFamily == corev1.IPv6Protocol {
ipFlag = "-6"
}
// For IPv6, nc -6 with hostname seems to hang in IPv6-only environments
// Fall back to explicit DNS resolution and pass IP directly to nc
if ipFamily == corev1.IPv6Protocol {
fqdnWithDot := serviceFQDN + "."
return fmt.Sprintf(
"addr=$(nslookup -type=AAAA %s 2>&1 | grep '^Address:' | grep -v '#' | tail -1 | awk '{print $2}'); "+
"if [ -z \"$addr\" ]; then echo 'No IPv6 address found'; exit 1; fi; "+
"echo hi | nc -v -w 2 $addr %d 2>&1",
fqdnWithDot, port)
}
return fmt.Sprintf("echo hi | nc -v -w 2 %s %s %d 2>&1", ipFlag, serviceFQDN, port)
}
================================================
FILE: conformance/conformance_suite_test.go
================================================
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conformance_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestConformance(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Conformance Suite")
}
================================================
FILE: conformance/connectivity.go
================================================
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conformance
import (
"context"
"fmt"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
var _ = Describe("", func() {
t := newTestDriver()
BeforeEach(func() {
t.autoExportService = false
})
Context("Connectivity to a service that is not exported", func() {
It("should be inaccessible", Label(RequiredLabel), func() {
AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#exporting-services")
By("attempting to access the remote service", func() {
By("issuing a request from all clusters", func() {
serviceFQDN := fmt.Sprintf("%s.%s.svc.%s", t.helloService.Name, t.namespace, dnsDomain)
// Test all IP families for completeness
for _, ipFamily := range t.helloService.Spec.IPFamilies {
command := []string{"sh", "-c", ncCommand(ipFamily, serviceFQDN, 42)}
// Run on all clusters
for _, client := range clients {
// Repeat multiple times
for i := 0; i < 20; i++ {
Expect(t.execCmdOnRequestPod(&client, command)).NotTo(ContainSubstring("pod ip"), reportNonConformant(""))
}
}
}
})
})
})
})
Context("Connectivity to an exported ClusterIP service", func() {
It("should be accessible through DNS", Label(OptionalLabel, ConnectivityLabel, ClusterIPLabel), func(ctx context.Context) {
AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#dns")
By("Exporting the service", func() {
// On the "remote" cluster
t.createServiceExport(ctx, &clients[0], newHelloServiceExport())
})
By("Issuing a request from all clusters", func() {
t.execPortConnectivityCommand(ctx, 42, "pod ip", 1)
})
})
})
Context("Connectivity to a ClusterIP service existing in two clusters but exported from one", func() {
BeforeEach(func() {
requireTwoClusters()
})
JustBeforeEach(func(ctx context.Context) {
t.deployHelloService(ctx, &clients[1], newHelloService())
})
It("should only access the exporting cluster", Label(OptionalLabel, ConnectivityLabel, ClusterIPLabel), func(ctx context.Context) {
AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/blob/master/keps/sig-multicluster/1645-multi-cluster-services-api/README.md#exporting-services")
By(fmt.Sprintf("Exporting the service on cluster %q", clients[0].name))
t.createServiceExport(ctx, &clients[0], newHelloServiceExport())
servicePodIP := t.awaitServicePodIP(ctx, &clients[0])
t.execPortConnectivityCommand(ctx, 42, servicePodIP, 10)
})
})
Context("Connectivity to exported services with same port name but different port numbers on each cluster", func() {
tt := newTwoClusterTestDriver(t)
BeforeEach(func() {
tt.helloService2.Spec.Ports = []corev1.ServicePort{
{Name: "tcp", Port: 4242, Protocol: corev1.ProtocolTCP, TargetPort: intstr.FromInt32(42)},
}
})
It("should only route traffic to the cluster that exposes the port", Label(OptionalLabel, ConnectivityLabel, ClusterIPLabel, StrictPortConflictLabel), func(ctx context.Context) {
AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#service-port")
servicePodIP := t.awaitServicePodIP(ctx, &clients[0])
t.execPortConnectivityCommand(ctx, 42, servicePodIP, 10)
})
})
Context("Connectivity to exported services with different port name on each cluster", func() {
tt := newTwoClusterTestDriver(t)
BeforeEach(func() {
tt.helloService2.Spec.Ports = []corev1.ServicePort{
{Name: "tcp2", Port: 4242, Protocol: corev1.ProtocolTCP, TargetPort: intstr.FromInt32(42)},
}
})
It("should route traffic to each port only to the cluster that exposes it", Label(OptionalLabel, ConnectivityLabel, ClusterIPLabel, StrictPortConflictLabel), func(ctx context.Context) {
AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#service-port")
cluster1PodIP := t.awaitServicePodIP(ctx, &clients[0])
cluster2PodIP := t.awaitServicePodIP(ctx, &clients[1])
t.execPortConnectivityCommand(ctx, 42, cluster1PodIP, 10)
t.execPortConnectivityCommand(ctx, 4242, cluster2PodIP, 10)
})
})
})
================================================
FILE: conformance/endpoint_slice.go
================================================
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conformance
import (
"context"
"fmt"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
discoveryv1 "k8s.io/api/discovery/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/mcs-api/pkg/apis/v1beta1"
)
// K8sEndpointSliceManagedByName is the name used for endpoint slices managed by the Kubernetes controller
const K8sEndpointSliceManagedByName = "endpointslice-controller.k8s.io"
var _ = Describe("", Label(OptionalLabel, EndpointSliceLabel), func() {
t := newTestDriver()
SpecifyWithSpecRef("Exporting a service should create an MCS EndpointSlice in the service's namespace in each cluster with the "+
"required MCS labels. Unexporting should delete the EndpointSlice.",
"https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#using-endpointslice-objects-to-track-endpoints",
func(ctx context.Context) {
endpointSliceNames := make([][]string, len(clients))
for i, client := range clients {
for _, ipFamily := range t.awaitServiceImportIPFamilies(ctx, &client) {
eps := t.awaitMCSEndpointSlice(ctx, &client, addressTypeOf(ipFamily), nil, reportNonConformant(fmt.Sprintf(
"an MCS EndpointSlice was not found on cluster %q. An MCS EndpointSlice is identified by the presence "+
"of the required MCS labels (%q and %q). "+
"If the MCS implementation does not use MCS EndpointSlices, you can specify a Ginkgo label filter using "+
"the %q label where appropriate to skip this test.",
client.name, v1beta1.LabelServiceName, v1beta1.LabelSourceCluster, EndpointSliceLabel)))
endpointSliceNames[i] = append(endpointSliceNames[i], eps.Name)
Expect(eps.Labels).To(HaveKeyWithValue(v1beta1.LabelServiceName, t.helloService.Name),
reportNonConformant(fmt.Sprintf("the MCS EndpointSlice %q does not contain the %q label referencing the service name",
eps.Name, v1beta1.LabelServiceName)))
Expect(eps.Labels).To(HaveKey(discoveryv1.LabelManagedBy),
reportNonConformant(fmt.Sprintf("the MCS EndpointSlice %q does not contain the %q label",
eps.Name, discoveryv1.LabelManagedBy)))
if !skipVerifyEndpointSliceManagedBy {
Expect(eps.Labels[discoveryv1.LabelManagedBy]).ToNot(Equal(K8sEndpointSliceManagedByName),
reportNonConformant(fmt.Sprintf("the MCS EndpointSlice's %q label must not reference %q",
discoveryv1.LabelManagedBy, K8sEndpointSliceManagedByName)))
}
}
}
t.deleteServiceExport(ctx, &clients[0])
for i, client := range clients {
for _, name := range endpointSliceNames[i] {
Eventually(func(ctx context.Context) bool {
_, err := client.k8s.DiscoveryV1().EndpointSlices(t.namespace).Get(ctx, name, metav1.GetOptions{})
return apierrors.IsNotFound(err)
}).WithContext(ctx).Within(20*time.Second).ProbeEvery(100*time.Millisecond).Should(BeTrue(),
reportNonConformant(fmt.Sprintf("the EndpointSlice %q was not deleted on unexport from cluster %q",
name, client.name)))
}
}
})
})
func (t *testDriver) awaitMCSEndpointSlice(ctx context.Context, c *clusterClients, addressType discoveryv1.AddressType,
verify func(Gomega, *discoveryv1.EndpointSlice), desc ...any) *discoveryv1.EndpointSlice {
By(fmt.Sprintf("Retrieving %s MCS EndpointSlice for the service on cluster %q", addressType, c.name))
var endpointSlice *discoveryv1.EndpointSlice
hasLabel := func(eps *discoveryv1.EndpointSlice, label string) bool {
_, exists := eps.Labels[label]
return exists
}
Eventually(func(g Gomega, ctx context.Context) {
list, err := c.k8s.DiscoveryV1().EndpointSlices(t.namespace).List(ctx, metav1.ListOptions{})
g.Expect(err).ToNot(HaveOccurred(), "Error retrieving EndpointSlices")
endpointSlice = nil
for i := range list.Items {
eps := &list.Items[i]
if hasLabel(eps, v1beta1.LabelServiceName) && hasLabel(eps, v1beta1.LabelSourceCluster) && eps.AddressType == addressType && len(eps.Endpoints) > 0 {
endpointSlice = eps
if verify != nil {
verify(g, endpointSlice)
}
}
}
g.Expect(endpointSlice).ToNot(BeNil(), desc...)
// The final run succeeded so cancel any prior non-conformance reported.
cancelNonConformanceReport()
}).WithContext(ctx).Within(20 * time.Second).ProbeEvery(100 * time.Millisecond).Should(Succeed())
return endpointSlice
}
================================================
FILE: conformance/framework.go
================================================
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conformance
import (
"bytes"
"context"
"time"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
rest "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
)
func execCmd(k8s kubernetes.Interface, config *rest.Config, podName string, podNamespace string, command []string) ([]byte, []byte, error) {
req := k8s.CoreV1().RESTClient().Post().Resource("pods").Name(podName).Namespace(podNamespace).SubResource("exec")
req.VersionedParams(&v1.PodExecOptions{
Command: command,
Stdin: false,
Stdout: true,
Stderr: true,
TTY: true,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
if err != nil {
return []byte{}, []byte{}, err
}
var stdout, stderr bytes.Buffer
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
defer cancel()
err = exec.StreamWithContext(ctx, remotecommand.StreamOptions{
Stdin: nil,
Stdout: &stdout,
Stderr: &stderr,
})
if err != nil {
return []byte{}, []byte{}, err
}
return stdout.Bytes(), stderr.Bytes(), nil
}
================================================
FILE: conformance/go.mod
================================================
module sigs.k8s.io/mcs-api/conformance
go 1.23.0
require (
github.com/onsi/ginkgo/v2 v2.21.0
github.com/onsi/gomega v1.35.1
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.32.5
k8s.io/apimachinery v0.32.5
k8s.io/client-go v0.32.5
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
sigs.k8s.io/mcs-api v0.3.0
)
replace sigs.k8s.io/mcs-api => ..
require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/moby/spdystream v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/term v0.25.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.26.0 // indirect
google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
================================================
FILE: conformance/go.sum
================================================
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.32.5 h1:uqjjsYo1kTJr5NIcoIaP9F+TgXgADH7nKQx91FDAhtk=
k8s.io/api v0.32.5/go.mod h1:bXXFU3fGCZ/eFMZvfHZC69PeGbXEL4zzjuPVzOxHF64=
k8s.io/apimachinery v0.32.5 h1:6We3aJ6crC0ap8EhsEXcgX3LpI6SEjubpiOMXLROwPM=
k8s.io/apimachinery v0.32.5/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/client-go v0.32.5 h1:huFmQMzgWu0z4kbWsuZci+Gt4Fo72I4CcrvhToZ/Qp0=
k8s.io/client-go v0.32.5/go.mod h1:Qchw6f9WIVrur7DKojAHpRgGLcANT0RLIvF39Jz58xA=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
================================================
FILE: conformance/headless_service_dns.go
================================================
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conformance
import (
"context"
"fmt"
"regexp"
"slices"
"strings"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/types"
corev1 "k8s.io/api/core/v1"
discovery "k8s.io/api/discovery/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/utils/ptr"
"sigs.k8s.io/mcs-api/pkg/apis/v1beta1"
)
var _ = Describe("", Label(OptionalLabel, DNSLabel, HeadlessLabel), func() {
const replicas = 2
t := newTestDriver()
BeforeEach(func() {
t.helloService.Spec.ClusterIP = corev1.ClusterIPNone
t.helloDeployment.Spec.Replicas = ptr.To(int32(replicas))
})
Specify("A DNS query of the <service>.<ns>.svc."+dnsDomain+" domain for a headless service should return the "+
"ready endpoint addresses of all the backing pods", func(ctx context.Context) {
AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#dns")
for _, client := range clients {
for _, ipFamily := range t.awaitServiceImportIPFamilies(ctx, &client) {
command := []string{"sh", "-c", fmt.Sprintf("nslookup -type=%s %s.%s.svc.%s.", dnsRecordTypeOf(ipFamily),
t.helloService.Name, t.namespace, dnsDomain)}
endpoints := t.awaitK8sEndpoints(ctx, &clients[0], addressTypeOf(ipFamily))
var addresses []string
for _, ep := range endpoints {
addresses = append(addresses, ep.address)
}
By(fmt.Sprintf("Executing %s command %q on cluster %q", ipFamily, strings.Join(command, " "), client.name))
t.awaitCmdOutputMatches(&client, command, HaveAddresses(addresses), 1, reportNonConformant(""))
}
}
})
Context("", func() {
BeforeEach(func() {
t.helloDeployment = nil
})
JustBeforeEach(func(ctx context.Context) {
_, err := clients[0].k8s.AppsV1().StatefulSets(t.namespace).Create(ctx, newStatefulSet(replicas), metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())
})
Specify("A DNS query of the <hostname>.<clusterid>.<service>.<ns>.svc."+dnsDomain+" domain for a headless StatefulSet "+
"service should return the requested pod's endpoint address", Label(EndpointSliceLabel), func(ctx context.Context) {
AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#dns")
for _, client := range clients {
for _, ipFamily := range t.awaitServiceImportIPFamilies(ctx, &client) {
eps := t.awaitMCSEndpointSlice(ctx, &client, addressTypeOf(ipFamily), func(g Gomega, eps *discovery.EndpointSlice) {
g.Expect(eps.Endpoints).To(HaveLen(replicas),
"the MCS EndpointSlice %q does not contain the expected number of endpoints %d",
eps.Name, replicas)
for i := range eps.Endpoints {
ep := eps.Endpoints[i]
g.Expect(ptr.Deref(ep.Conditions.Ready, true)).To(BeTrue(),
"the endpoint address %s in the MCS EndpointSlice %q is not ready",
strings.Join(ep.Addresses, ","), eps.Name)
g.Expect(ptr.Deref(ep.Hostname, "")).ToNot(BeEmpty(),
"the hostname field for endpoint address %s in the MCS EndpointSlice %q is not set",
strings.Join(ep.Addresses, ","), eps.Name)
}
}, "an MCS EndpointSlice was not found on cluster %q", client.name)
clusterID := eps.Labels[v1beta1.LabelSourceCluster]
for i := range eps.Endpoints {
ep := &eps.Endpoints[i]
// Add trailing dot to prevent search domain from being appended
command := []string{"sh", "-c", fmt.Sprintf("nslookup -type=%s %s.%s.%s.%s.svc.%s.",
dnsRecordTypeOf(ipFamily), ptr.Deref(ep.Hostname, ""), clusterID, t.helloService.Name,
t.namespace, dnsDomain)}
By(fmt.Sprintf("Executing command %q on cluster %q", strings.Join(command, " "), client.name))
t.awaitCmdOutputMatches(&client, command, HaveAddresses(ep.Addresses), 1, reportNonConformant(""))
}
}
}
})
})
Specify("A DNS SRV query of the <service>.<ns>.svc."+dnsDomain+" domain for a headless service should return valid SRV "+
"records", func(ctx context.Context) {
AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#dns")
// Collect endpoints for all IP families in the service (for dual-stack support)
var allEndpoints []endpointInfo
for _, ipFamily := range t.helloService.Spec.IPFamilies {
endpoints := t.awaitK8sEndpoints(ctx, &clients[0], addressTypeOf(ipFamily))
allEndpoints = append(allEndpoints, endpoints...)
}
domainName := fmt.Sprintf("%s.%s.svc.%s", t.helloService.Name, t.namespace, dnsDomain)
for _, client := range clients {
srvRecs := t.expectSRVRecords(&client, domainName, len(allEndpoints))
// Verify each endpoint has a corresponding SRV record
for _, ep := range allEndpoints {
index := slices.IndexFunc(srvRecs, func(r srvRecord) bool {
return strings.HasPrefix(r.domainName, ep.hostName)
})
Expect(index).To(BeNumerically(">=", 0), reportNonConformant(
fmt.Sprintf("SRV record for endpoint host name %q not received. Actual records received: %v",
ep.hostName, srvRecs)))
Expect(srvRecs[index].port).To(Equal(t.helloService.Spec.Ports[0].Port))
}
}
})
})
type endpointInfo struct {
address string
hostName string
}
func (e endpointInfo) String() string {
return fmt.Sprintf("address:%q, hostName:%q", e.address, e.hostName)
}
func (t *testDriver) awaitK8sEndpoints(ctx context.Context, c *clusterClients, addressType discovery.AddressType) []endpointInfo {
By(fmt.Sprintf("Retrieving %s K8s endpoint addresses for the service on cluster %q", addressType, c.name))
var endpoints []endpointInfo
Eventually(func(g Gomega, ctx context.Context) {
epsList, err := c.k8s.DiscoveryV1().EndpointSlices(t.namespace).List(ctx, metav1.ListOptions{
LabelSelector: labels.SelectorFromSet(map[string]string{
discovery.LabelServiceName: t.helloService.Name,
}).String(),
})
g.Expect(err).ToNot(HaveOccurred())
endpoints = nil
for i := range epsList.Items {
eps := &epsList.Items[i]
if eps.AddressType != addressType {
continue
}
for j := range epsList.Items[i].Endpoints {
ep := &epsList.Items[i].Endpoints[j]
g.Expect(ptr.Deref(ep.Conditions.Ready, true)).To(BeTrue(),
"the endpoint address %s in the K8s EndpointSlice %q is not ready",
strings.Join(ep.Addresses, ","), eps.Name)
for _, addr := range ep.Addresses {
epi := endpointInfo{address: addr}
switch {
case ptr.Deref(ep.Hostname, "") != "":
epi.hostName = *ep.Hostname
case strings.Contains(addr, "."):
epi.hostName = strings.ReplaceAll(addr, ".", "-")
case strings.Contains(addr, ":"):
epi.hostName = strings.ReplaceAll(addr, ":", "-")
}
endpoints = append(endpoints, epi)
}
}
}
expCount := int(ptr.Deref(t.helloDeployment.Spec.Replicas, 1))
g.Expect(endpoints).To(HaveLen(expCount),
"the K8s EndpointSlice does not contain the expected number of ready endpoints %d", expCount)
// The final run succeeded so cancel any prior non-conformance reported.
cancelNonConformanceReport()
}).WithContext(ctx).Within(20 * time.Second).ProbeEvery(100 * time.Millisecond).Should(Succeed())
By(fmt.Sprintf("Found endpoints %v", endpoints))
return endpoints
}
// Match DNS records of type A from nslookup output of the form:
//
// Server: 10.96.0.10
// Address: 10.96.0.10:53
//
// Name: hello.mcs-conformance-2021198391.svc.clusterset.local
// Address: 10.244.0.52
// Name: hello.mcs-conformance-2021198391.svc.clusterset.local
// Address: 10.244.0.51
//
// to extract the domain addresses (in this case "10.244.0.52" and "10.244.0.51")
var addressesRegEx = regexp.MustCompile(`Name:.*\s*Address:\s*(.*)`)
type haveAddressesMatcher struct {
expected []string
}
func (m *haveAddressesMatcher) Match(v interface{}) (bool, error) {
matches := addressesRegEx.FindAllStringSubmatch(v.(string), -1)
var actual []string
for i := range matches {
actual = append(actual, strings.TrimSpace(matches[i][1]))
}
slices.Sort(actual)
return slices.Equal(actual, m.expected), nil
}
func (m *haveAddressesMatcher) FailureMessage(actual interface{}) string {
return format.Message(actual, "to have addresses", m.expected)
}
func (m *haveAddressesMatcher) NegatedFailureMessage(actual interface{}) string {
return format.Message(actual, "to not have addresses", m.expected)
}
func HaveAddresses(expected []string) types.GomegaMatcher {
slices.Sort(expected)
return &haveAddressesMatcher{
expected: expected,
}
}
================================================
FILE: conformance/k8s_objects.go
================================================
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conformance
import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
"sigs.k8s.io/mcs-api/pkg/apis/v1beta1"
)
const helloServiceName = "hello"
func newHelloService() *corev1.Service {
return &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: helloServiceName,
Annotations: map[string]string{"dummy-svc": "dummy"},
Labels: map[string]string{"dummy-svc": "dummy"},
},
Spec: corev1.ServiceSpec{
Selector: map[string]string{
"app": helloServiceName,
},
Ports: []corev1.ServicePort{
{
Name: "tcp",
Port: 42,
Protocol: corev1.ProtocolTCP,
},
{
Name: "udp",
Port: 42,
Protocol: corev1.ProtocolUDP,
},
},
SessionAffinity: corev1.ServiceAffinityClientIP,
SessionAffinityConfig: &corev1.SessionAffinityConfig{
ClientIP: &corev1.ClientIPConfig{TimeoutSeconds: ptr.To(int32(10))},
},
IPFamilyPolicy: ptr.To(corev1.IPFamilyPolicyPreferDualStack),
},
}
}
func newHelloServiceExport() *v1beta1.ServiceExport {
return &v1beta1.ServiceExport{
ObjectMeta: metav1.ObjectMeta{
Name: helloServiceName,
Annotations: map[string]string{"dummy-svcexport": "dummy"},
Labels: map[string]string{"dummy-svcexport": "dummy"},
},
}
}
// socatListenerScript generates a shell script that detects the pod's IP family configuration
// and starts the appropriate socat listener (IPv4, IPv6, or dual-stack).
func socatListenerScript(protocol string) string {
return `# Detect IP families available on the pod from podIPs
# The downward API provides podIPs as a plain string (single IP or space/comma-separated list)
has_ipv4=false
has_ipv6=false
# Check for IPv4 pattern (dotted decimal notation)
if echo "$MY_POD_IPS" | grep -qE '([^0-9]|^)[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}([^0-9]|$)'; then has_ipv4=true; fi
# Check for IPv6 pattern (hex digits with at least two colons)
if echo "$MY_POD_IPS" | grep -qE '[0-9a-fA-F]*:[0-9a-fA-F]*:[0-9a-fA-F:]*'; then has_ipv6=true; fi
# Debug: log what we detected
echo "DEBUG: MY_POD_IPS=$MY_POD_IPS" >&2
echo "DEBUG: has_ipv4=$has_ipv4, has_ipv6=$has_ipv6" >&2
# Extract first IP address (may be space or comma separated)
first_ip=$(echo "$MY_POD_IPS" | tr ',' ' ' | awk '{print $1}')
echo "DEBUG: first_ip=$first_ip" >&2
# Choose socat listener based on available IP families
# Export first_ip so it's available to SYSTEM subprocesses
export first_ip
if $has_ipv6 && $has_ipv4; then
# Dual-stack: use IPv6 socket that accepts both
echo "DEBUG: Starting dual-stack listener" >&2
socat -v -v ` + protocol + `6-LISTEN:42,crlf,reuseaddr,fork,ipv6only=0 SYSTEM:'echo "pod ip $first_ip"'
elif $has_ipv6; then
# IPv6 only
echo "DEBUG: Starting IPv6-only listener" >&2
socat -v -v ` + protocol + `6-LISTEN:42,crlf,reuseaddr,fork SYSTEM:'echo "pod ip $first_ip"'
else
# IPv4 only
echo "DEBUG: Starting IPv4-only listener" >&2
socat -v -v ` + protocol + `-LISTEN:42,crlf,reuseaddr,fork SYSTEM:'echo "pod ip $first_ip"'
fi`
}
func podContainers() []corev1.Container {
return []corev1.Container{
{
Name: "hello-tcp",
Image: "alpine/socat:1.7.4.4",
// Detect if pod has IPv4, IPv6, or both and use appropriate socat listener
// - IPv4 only: use TCP-LISTEN
// - IPv6 only: use TCP6-LISTEN (no ipv6only flag needed)
// - Dual-stack: use TCP6-LISTEN with ipv6only=0 to handle both
Command: []string{"/bin/sh"},
Args: []string{"-c", socatListenerScript("TCP")},
Env: []corev1.EnvVar{
{
Name: "MY_POD_IPS",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "status.podIPs",
},
},
},
},
},
{
Name: "hello-udp",
Image: "alpine/socat:1.7.4.4",
// Detect if pod has IPv4, IPv6, or both and use appropriate socat listener
// - IPv4 only: use UDP-LISTEN
// - IPv6 only: use UDP6-LISTEN (no ipv6only flag needed)
// - Dual-stack: use UDP6-LISTEN with ipv6only=0 to handle both
Command: []string{"/bin/sh"},
Args: []string{"-c", socatListenerScript("UDP")},
Env: []corev1.EnvVar{
{
Name: "MY_POD_IPS",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "status.podIPs",
},
},
},
},
},
}
}
func newHelloDeployment() *appsv1.Deployment {
return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: helloServiceName,
},
Spec: appsv1.DeploymentSpec{
Replicas: ptr.To(int32(1)),
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": helloServiceName,
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"app": helloServiceName},
},
Spec: corev1.PodSpec{
Containers: podContainers(),
},
},
},
}
}
func newStatefulSet(replicas int) *appsv1.StatefulSet {
return &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: helloServiceName + "-ss",
},
Spec: appsv1.StatefulSetSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": helloServiceName,
},
},
ServiceName: helloServiceName,
Replicas: ptr.To(int32(replicas)),
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": helloServiceName,
},
},
Spec: corev1.PodSpec{
Containers: podContainers(),
RestartPolicy: corev1.RestartPolicyAlways,
},
},
},
}
}
func newRequestPod() *corev1.Pod {
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "request",
Labels: map[string]string{"app": "request"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "request",
Image: "nicolaka/netshoot:v0.15",
Args: []string{"/bin/sh", "-ec", "while :; do echo '.'; sleep 5 ; done"},
},
},
},
}
}
================================================
FILE: conformance/report.go
================================================
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conformance
import (
_ "embed" // Needed for go:embed
"errors"
"fmt"
"html/template"
"os"
"regexp"
"slices"
"strings"
"sync/atomic"
. "github.com/onsi/ginkgo/v2"
"github.com/onsi/ginkgo/v2/types"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/matchers"
"gopkg.in/yaml.v3"
)
const (
OptionalLabel = "Optional"
RequiredLabel = "Required"
DNSLabel = "DNS"
ConnectivityLabel = "Connectivity"
ClusterIPLabel = "ClusterIP"
HeadlessLabel = "Headless"
ExternalNameLabel = "ExternalName"
EndpointSliceLabel = "EndpointSlice"
ExportedLabelsLabel = "ExportedLabels"
StrictPortConflictLabel = "StrictPortConflict"
SpecRefReportEntry = "spec-ref"
NonConformantReportEntry = "non-conformant"
)
var reportingLabels = []string{
RequiredLabel,
OptionalLabel,
}
//go:embed report_template.gohtml
var reportHTML string
type testInfo struct {
Desc string
Ref string
Labels []string
Passed bool
Failed bool
Skipped bool
Conformant bool
Message string
}
type testGrouping struct {
Name string
Tests []testInfo
}
type implementationInfo struct {
Organization string
Project string
Version string
URL string
}
var (
errorRegEx *regexp.Regexp
// currentSpecNonConformanceMsg holds the last non-conformance message emitted by the current spec. Using
// Eventually with a function that takes a Gomega, different assertions could report non-conformance on
// successive retries, so we want to just report the last one. This also allows the last message to be cleared
// (via cancelNonConformanceReport()) if it eventually succeeded.
currentSpecNonConformanceMsg atomic.Value
// specRefRegistry maps test description substrings to the KEP spec reference
specRefRegistry = map[string]string{}
)
// SpecifyWithSpecRef exist to be able to register a spec to the KEP alongside
// a Specify. This is important to be able to correctly attach this spec ref
// even if a test is skipped
func SpecifyWithSpecRef(text string, specRef string, args ...interface{}) bool {
specRefRegistry[text] = specRef
return Specify(text, args...)
}
func lookupSpecRef(fullText string) string {
for desc, ref := range specRefRegistry {
if strings.Contains(fullText, desc) {
return ref
}
}
return ""
}
func init() {
dummyErr := errors.New("dummy")
// Matches gomega output for an error rendered by either the HaveOccurred or Success matchers, eg
//
// "Error retrieving resource
// Unexpected error:
// <*errors.StatusError | 0x400024e820>:
// "foo" not found
// {
// ...
// }
// occurred"
//
// In this case, we want to match and extract: "Error retrieving resource" and ""foo" not found".
errorRegEx = regexp.MustCompile(fmt.Sprintf(`\s*(.*)\s*(?:%s|%s|The function passed to)\s*.*\s*(.*)`,
firstLine((&matchers.HaveOccurredMatcher{}).NegatedFailureMessage(dummyErr)),
firstLine((&matchers.SucceedMatcher{}).FailureMessage(dummyErr))))
}
var _ = ReportBeforeEach(func(specReport SpecReport) {
cancelNonConformanceReport()
if ref := lookupSpecRef(specReport.FullText()); ref != "" {
AddReportEntry(SpecRefReportEntry, ref)
}
})
var _ = ReportAfterEach(func(specReport SpecReport) {
if specReport.LeafNodeType != types.NodeTypeIt || specReport.State == types.SpecStatePending ||
specReport.State == types.SpecStateSkipped {
return
}
msg := currentSpecNonConformanceMsg.Swap("")
if msg != "" {
AddReportEntry(NonConformantReportEntry, msg, types.ReportEntryVisibilityNever)
}
})
var _ = ReportAfterSuite("MCS conformance report", func(report Report) {
testGroupMap := map[string]*testGrouping{}
suiteFailure := ""
for _, specReport := range report.SpecReports {
if specReport.LeafNodeType == types.NodeTypeBeforeSuite && specReport.State == types.SpecStateFailed {
suiteFailure = parseFailureMessage(specReport.FailureMessage())
continue
}
if specReport.LeafNodeType != types.NodeTypeIt || specReport.State == types.SpecStatePending {
continue
}
for _, label := range reportingLabels {
if !slices.Contains(specReport.Labels(), label) {
continue
}
if testGroupMap[label] == nil {
testGroupMap[label] = &testGrouping{
Name: label,
}
}
info := testInfo{
Desc: strings.TrimSpace(specReport.FullText()),
Conformant: true,
}
for _, currLabel := range specReport.Labels() {
if currLabel != label {
info.Labels = append(info.Labels, currLabel)
}
}
slices.Sort(info.Labels)
for i := range specReport.ReportEntries {
switch specReport.ReportEntries[i].Name {
case SpecRefReportEntry:
info.Ref = specReport.ReportEntries[i].GetRawValue().(string)
case NonConformantReportEntry:
// An assertion reporting non-conformance may have failed initially but eventually succeeded after retries
// so only report non-conformance if the spec actually failed.
if specReport.State != types.SpecStatePassed {
info.Conformant = false
info.Message = specReport.ReportEntries[i].GetRawValue().(string)
}
}
}
if specReport.State == types.SpecStateSkipped {
info.Skipped = true
info.Message = parseFailureMessage(specReport.FailureMessage())
} else if specReport.State != types.SpecStatePassed && info.Conformant {
// If the spec failed (ie didn't pass) not due to non-conformance then we assume it encountered
// an unexpected error preventing conformance from being determined, and thus we'll report the
// conformance status as unknown.
info.Failed = true
info.Message = parseFailureMessage(specReport.FailureMessage())
}
info.Passed = !info.Failed && !info.Skipped && info.Conformant
if info.Message != "" {
info.Message = " - " + info.Message
}
testGroupMap[label].Tests = append(testGroupMap[label].Tests, info)
}
}
testGroups := []testGrouping{}
for _, l := range reportingLabels {
if testGroupMap[l] != nil {
slices.SortFunc(testGroupMap[l].Tests, func(a, b testInfo) int {
if cmp := slices.Compare(a.Labels, b.Labels); cmp != 0 {
return cmp
}
return strings.Compare(strings.TrimSpace(a.Desc), strings.TrimSpace(b.Desc))
})
testGroups = append(testGroups, *testGroupMap[l])
}
}
totalTests := 0
passedTests := 0
for _, g := range testGroups {
for _, t := range g.Tests {
totalTests++
if t.Passed {
passedTests++
}
}
}
data := struct {
Groups []testGrouping
SuiteFailure string
DNSDomain string
Passed int
Total int
Implementation implementationInfo
}{
Groups: testGroups,
SuiteFailure: suiteFailure,
DNSDomain: dnsDomain,
Passed: passedTests,
Total: totalTests,
Implementation: implementationInfo{
Organization: organization,
Project: project,
Version: version,
URL: url,
},
}
out, err := os.Create("report.html")
Expect(err).To(Succeed())
tmpl, err := template.New("report").Parse(reportHTML)
Expect(err).To(Succeed())
err = tmpl.Execute(out, data)
Expect(err).To(Succeed())
yamlOut, err := os.Create("report.yaml")
Expect(err).To(Succeed())
err = yaml.NewEncoder(yamlOut).Encode(data)
Expect(err).To(Succeed())
})
func parseFailureMessage(s string) string {
// First see if the message represents an error formatted by a gomega matcher - we're interested in
// extracting the optional user description passed to the gomega assertion and the actual error
// string as these are the useful parts conducive for formatting in the report table.
matches := errorRegEx.FindStringSubmatch(s)
if len(matches) > 0 {
// First match at index 0 is the full text that was matched; index 1 will be the user description
// and index 2 the error string. We concatenate the latter two.
msg := strings.TrimSpace(matches[1])
if msg == "" {
msg = strings.TrimSpace(matches[2])
} else {
msg = strings.TrimSuffix(msg, ".") + ": " + strings.TrimSpace(matches[2])
}
return msg
}
// Fallback - just take the first line in the message.
return firstLine(s)
}
func firstLine(s string) string {
first, _, _ := strings.Cut(s, "\n")
return strings.TrimSpace(first)
}
// reportNonConformant is intended for use as an optional description in a gomega assertion. It returns
// a function that is lazily evaluated by the assertion only if a failure occurs. We take advantage of
// that to add a report entry indicating non-conformance.
func reportNonConformant(msg string) func() string {
return func() string {
currentSpecNonConformanceMsg.Store(msg)
return msg
}
}
func cancelNonConformanceReport() {
currentSpecNonConformanceMsg.Store("")
}
================================================
FILE: conformance/report_template.gohtml
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>MCS API conformance report</title>
<style>
table, th, td {
border: 1px solid black;
}
td {
padding: 2px;
}
</style>
</head>
<body>
<h2>MCS Conformance Report</h2>
<p><strong>{{.Passed}}</strong> of <strong>{{.Total}}</strong> tests passed</p>
{{if and .DNSDomain (ne .DNSDomain "clusterset.local")}}
<p>DNS domain suffix: <strong>{{.DNSDomain}}</strong></p>
{{end}}
{{if .SuiteFailure }}
<p style="color: red">{{.SuiteFailure}}</p>
{{end}}
{{range .Groups}}
<h3>{{.Name}}</h3>
<table>
<thead>
<tr>
<th>Conformant</th>
<th>Labels</th>
<th>Description</th>
</tr>
</thead>
{{range .Tests}}
<tr>
{{ if .Skipped }}
<td style="color:gray">Skipped{{.Message}}</td>
{{ else if .Failed }}
<td style="color:orange">Unknown{{.Message}}</td>
{{ else if .Conformant }}
<td style="color:green">Yes{{.Message}}</td>
{{ else }}
<td style="color:red">No{{.Message}}</td>
{{end}}
<td>{{range $i, $l := .Labels}}{{if $i}}, {{end}}{{$l}}{{end}}</td>
<td><a href="{{.Ref}}">{{.Desc}}</a></td>
</tr>
{{end}}
</table>
{{end}}
</body>
</html>
================================================
FILE: conformance/service_import.go
================================================
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conformance
import (
"context"
"fmt"
"net"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
"sigs.k8s.io/mcs-api/pkg/apis/v1beta1"
)
var (
_ = Describe("", testGeneralServiceImport)
_ = Describe("", Label(ClusterIPLabel), testClusterIPServiceImport)
_ = Describe("", Label(HeadlessLabel), testHeadlessServiceImport)
_ = Describe("", Label(ExternalNameLabel), testExternalNameService)
_ = Describe("", testServiceTypeConflict)
)
func testGeneralServiceImport() {
t := newTestDriver()
assertHasKeyValues := func(g Gomega, actual, expected map[string]string) {
for k, v := range expected {
g.Expect(actual).To(HaveKeyWithValue(k, v), reportNonConformant(""))
}
}
assertNotHasKeyValues := func(g Gomega, actual, expected map[string]string) {
for k, v := range expected {
g.Expect(actual).ToNot(HaveKeyWithValue(k, v), reportNonConformant(""))
}
}
// Other tests also unexport the service and verifies the ServiceImport is deleted, but it doesn't require more than one
// cluster, so it provides basic coverage in a simple single cluster environment. The following test is more comprehensive in that it
// exports a service from two clusters and ensures proper behavior when unexported from both.
SpecifyWithSpecRef("A ServiceImport should only exist as long as there's at least one exporting cluster",
"https://github.com/kubernetes/enhancements/blob/master/keps/sig-multicluster/1645-multi-cluster-services-api/README.md#importing-services",
Label(RequiredLabel), func(ctx context.Context) {
requireTwoClusters()
t.awaitServiceImport(ctx, &clients[0], t.helloService.Name, false, nil)
By(fmt.Sprintf("Exporting the service on the second cluster %q", clients[1].name))
t.deployHelloService(ctx, &clients[1], newHelloService())
t.createServiceExport(ctx, &clients[1], newHelloServiceExport())
// Sanity check and to also wait a bit for the second cluster to export. There's no deterministic way to tell if/when
// the second cluster has finished exporting other than utilizing different service ports in each cluster but service
// port merging behavior is already covered in another test case, so it's ideal not to rely on behavior that could
// cause orthogonal failures and to avoid duplicate testing.
t.ensureServiceImport(ctx, &clients[0], t.helloService.Name, fmt.Sprintf(
"the ServiceImport no longer exists after exporting on cluster %q", clients[1].name))
t.deleteServiceExport(ctx, &clients[0])
t.ensureServiceImport(ctx, &clients[0], t.helloService.Name, fmt.Sprintf(
"the ServiceImport no longer exists after unexporting the service on cluster %q while still exported on cluster %q",
clients[0].name, clients[1].name))
t.deleteServiceExport(ctx, &clients[1])
t.awaitNoServiceImport(ctx, &clients[0], helloServiceName,
"the ServiceImport still exists after unexporting the service on all clusters")
})
Context("", func() {
BeforeEach(func() {
t.helloServiceExport.Spec.ExportedAnnotations = map[string]string{"dummy-annotation": "true"}
t.helloServiceExport.Spec.ExportedLabels = map[string]string{"dummy-label": "true"}
})
SpecifyWithSpecRef("Only labels and annotations specified as exported in the ServiceExport should be propagated to the ServiceImport",
"https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#labels-and-annotations",
Label(OptionalLabel), Label(ExportedLabelsLabel), func(ctx context.Context) {
t.awaitServiceImport(ctx, &clients[0], helloServiceName, false,
func(g Gomega, serviceImport *v1beta1.ServiceImport) {
assertHasKeyValues(g, serviceImport.Annotations, t.helloServiceExport.Spec.ExportedAnnotations)
assertNotHasKeyValues(g, serviceImport.Annotations, t.helloService.Annotations)
assertHasKeyValues(g, serviceImport.Labels, t.helloServiceExport.Spec.ExportedLabels)
assertNotHasKeyValues(g, serviceImport.Labels, t.helloService.Labels)
})
})
})
Context("A service exported on two clusters", func() {
tt := newTwoClusterTestDriver(t)
Context("with conflicting annotations and labels", func() {
BeforeEach(func() {
t.helloServiceExport.Spec.ExportedAnnotations = map[string]string{"dummy-annotation": "true"}
t.helloServiceExport.Spec.ExportedLabels = map[string]string{"dummy-label": "true"}
tt.helloServiceExport2.Spec.ExportedAnnotations = map[string]string{"dummy-annotation2": "true"}
tt.helloServiceExport2.Spec.ExportedLabels = map[string]string{"dummy-label2": "true"}
})
SpecifyWithSpecRef("should apply the conflict resolution policy and report a Conflict condition on each ServiceExport",
"https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#labels-and-annotations",
Label(OptionalLabel), Label(ExportedLabelsLabel), func(ctx context.Context) {
t.awaitServiceExportCondition(ctx, &clients[0], v1beta1.ServiceExportConditionConflict, metav1.ConditionTrue)
t.awaitServiceExportCondition(ctx, &clients[1], v1beta1.ServiceExportConditionConflict, metav1.ConditionTrue)
t.awaitServiceImport(ctx, &clients[0], t.helloService.Name, false,
func(g Gomega, serviceImport *v1beta1.ServiceImport) {
assertHasKeyValues(g, serviceImport.Annotations, t.helloServiceExport.Spec.ExportedAnnotations)
assertNotHasKeyValues(g, serviceImport.Annotations, tt.helloServiceExport2.Spec.ExportedAnnotations)
assertHasKeyValues(g, serviceImport.Labels, t.helloServiceExport.Spec.ExportedLabels)
assertNotHasKeyValues(g, serviceImport.Labels, tt.helloServiceExport2.Spec.ExportedLabels)
})
})
})
})
}
func testClusterIPServiceImport() {
t := newTestDriver()
SpecifyWithSpecRef("Exporting a ClusterIP service should create a ServiceImport of type ClusterSetIP in the service's namespace in each cluster. "+
"Unexporting should delete the ServiceImport",
"https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#importing-services",
Label(RequiredLabel), func(ctx context.Context) {
t.awaitServiceExportCondition(ctx, &clients[0], v1beta1.ServiceExportConditionValid, metav1.ConditionTrue)
for i := range clients {
serviceImport := t.awaitServiceImport(ctx, &clients[i], helloServiceName, true, nil)
Expect(serviceImport.Spec.Type).To(Equal(v1beta1.ClusterSetIP), reportNonConformant(
fmt.Sprintf("ServiceImport on cluster %q has type %q", clients[i].name, serviceImport.Spec.Type)))
}
t.deleteServiceExport(ctx, &clients[0])
for i := range clients {
t.awaitNoServiceImport(ctx, &clients[i], helloServiceName, fmt.Sprintf(
"the ServiceImport still exists on cluster %q after unexporting the service", clients[i].name))
}
})
SpecifyWithSpecRef("The SessionAffinity for a ClusterSetIP ServiceImport should match the exported service's SessionAffinity",
"https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#session-affinity",
Label(RequiredLabel), func(ctx context.Context) {
t.awaitServiceImport(ctx, &clients[0], helloServiceName, false, func(g Gomega, serviceImport *v1beta1.ServiceImport) {
g.Expect(serviceImport.Spec.SessionAffinity).To(Equal(t.helloService.Spec.SessionAffinity), reportNonConformant(""))
g.Expect(serviceImport.Spec.SessionAffinityConfig).To(Equal(t.helloService.Spec.SessionAffinityConfig), reportNonConformant(
"The SessionAffinityConfig of the ServiceImport does not match the exported Service's SessionAffinityConfig"))
})
})
Context("", func() {
BeforeEach(func() {
t.helloService.Spec.InternalTrafficPolicy = ptr.To(corev1.ServiceInternalTrafficPolicyCluster)
})
SpecifyWithSpecRef("The InternalTrafficPolicy for a ClusterSetIP ServiceImport should match the exported service's InternalTrafficPolicy",
"https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#internal-traffic-policy",
Label(RequiredLabel), func(ctx context.Context) {
t.awaitServiceImport(ctx, &clients[0], helloServiceName, false, func(g Gomega, serviceImport *v1beta1.ServiceImport) {
g.Expect(serviceImport.Spec.InternalTrafficPolicy).To(Equal(t.helloService.Spec.InternalTrafficPolicy), reportNonConformant(
"The InternalTrafficPolicy of the ServiceImport does not match the exported Service's InternalTrafficPolicy"))
})
},
)
})
Context("", func() {
BeforeEach(func() {
t.helloService.Spec.TrafficDistribution = ptr.To(corev1.ServiceTrafficDistributionPreferClose)
})
SpecifyWithSpecRef("The TrafficDistribution for a ClusterSetIP ServiceImport should match the exported service's TrafficDistribution",
"https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#traffic-distribution",
Label(RequiredLabel), func(ctx context.Context) {
t.awaitServiceImport(ctx, &clients[0], helloServiceName, false, func(g Gomega, serviceImport *v1beta1.ServiceImport) {
g.Expect(serviceImport.Spec.TrafficDistribution).To(Equal(t.helloService.Spec.TrafficDistribution), reportNonConformant(
"The TrafficDistribution of the ServiceImport does not match the exported Service's TrafficDistribution"))
})
},
)
})
SpecifyWithSpecRef("An IP should be allocated for a ClusterSetIP ServiceImport for each IP family",
"https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#clustersetip",
Label(RequiredLabel), func(ctx context.Context) {
serviceImport := t.awaitServiceImport(ctx, &clients[0], t.helloService.Name, false,
func(g Gomega, serviceImport *v1beta1.ServiceImport) {
g.Expect(serviceImport.Spec.IPs).ToNot(BeEmpty(), reportNonConformant(""))
})
Expect(serviceImport.Spec.IPs).To(HaveLen(len(serviceImport.Spec.IPFamilies)), reportNonConformant(
"The ServiceImport IPs field must have the same number of IPs as ServiceImport IPFamilies"))
// Verify the IPs are valid and are in the same order as IPFamilies.
for i := range serviceImport.Spec.IPs {
Expect(net.ParseIP(serviceImport.Spec.IPs[i])).ToNot(BeNil(),
reportNonConformant(fmt.Sprintf("The value %q is not a valid IP", serviceImport.Spec.IPs[i])))
Expect(ipFamilyOf(serviceImport.Spec.IPs[i])).To(Equal(serviceImport.Spec.IPFamilies[i]),
reportNonConformant(fmt.Sprintf(
"The IP family of ServiceImport.Spec.IPs[%d] (%q) must match ServiceImport.Spec.IPFamilies[%d] (%q)",
i, serviceImport.Spec.IPs[i], i, serviceImport.Spec.IPFamilies[i])))
}
})
SpecifyWithSpecRef("The ports for a ClusterSetIP ServiceImport should match those of the exported service",
"https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#service-port",
Label(RequiredLabel), func(ctx context.Context) {
t.awaitServiceImport(ctx, &clients[0], helloServiceName, false, func(g Gomega, serviceImport *v1beta1.ServiceImport) {
g.Expect(sortMCSPorts(serviceImport.Spec.Ports)).To(Equal(toMCSPorts(t.helloService.Spec.Ports)), reportNonConformant(""))
})
})
Context("A ClusterIP service exported on two clusters", func() {
tt := newTwoClusterTestDriver(t)
Context("", func() {
BeforeEach(func() {
tt.helloService2.Spec.Ports = []corev1.ServicePort{
t.helloService.Spec.Ports[0],
{
Name: "stcp",
Port: 142,
Protocol: corev1.ProtocolSCTP,
},
}
})
SpecifyWithSpecRef("should expose the union of the constituent service ports and raise a conflict",
"https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#service-port",
Label(RequiredLabel), func(ctx context.Context) {
t.awaitServiceExportCondition(ctx, &clients[0], v1beta1.ServiceExportConditionConflict, metav1.ConditionTrue)
t.awaitServiceExportCondition(ctx, &clients[1], v1beta1.ServiceExportConditionConflict, metav1.ConditionTrue)
t.awaitServiceImport(ctx, &clients[0], t.helloService.Name, false,
func(g Gomega, serviceImport *v1beta1.ServiceImport) {
g.Expect(sortMCSPorts(serviceImport.Spec.Ports)).To(Equal(toMCSPorts(
append(t.helloService.Spec.Ports, tt.helloService2.Spec.Ports[1]))), reportNonConformant(""))
})
})
})
Context("with conflicting ports", Label(RequiredLabel), func() {
BeforeEach(func() {
tt.helloService2.Spec.Ports = []corev1.ServicePort{t.helloService.Spec.Ports[0]}
tt.helloService2.Spec.Ports[0].Port = t.helloService.Spec.Ports[0].Port + 1
})
SpecifyWithSpecRef("should apply the conflict resolution policy and report a Conflict condition on each ServiceExport",
"https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#service-port",
func(ctx context.Context) {
t.awaitServiceExportCondition(ctx, &clients[0], v1beta1.ServiceExportConditionConflict, metav1.ConditionTrue)
t.awaitServiceExportCondition(ctx, &clients[1], v1beta1.ServiceExportConditionConflict, metav1.ConditionTrue)
t.awaitServiceImport(ctx, &clients[0], t.helloService.Name, false,
func(g Gomega, serviceImport *v1beta1.ServiceImport) {
g.Expect(sortMCSPorts(serviceImport.Spec.Ports)).To(Equal(toMCSPorts(t.helloService.Spec.Ports)),
reportNonConformant("The service ports were not resolved correctly"))
})
})
})
})
}
func testHeadlessServiceImport() {
t := newTestDriver()
BeforeEach(func() {
t.helloService.Spec.ClusterIP = corev1.ClusterIPNone
})
SpecifyWithSpecRef("Exporting a headless service should create a ServiceImport of type Headless in the service's namespace in each cluster. "+
"Unexporting should delete the ServiceImport",
"https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#service-types",
Label(RequiredLabel), func(ctx context.Context) {
for i := range clients {
serviceImport := t.awaitServiceImport(ctx, &clients[i], helloServiceName, true, nil)
Expect(serviceImport.Spec.Type).To(Equal(v1beta1.Headless), reportNonConformant(
fmt.Sprintf("ServiceImport on cluster %q has type %q", clients[i].name, serviceImport.Spec.Type)))
}
t.deleteServiceExport(ctx, &clients[0])
for i := range clients {
t.awaitNoServiceImport(ctx, &clients[i], helloServiceName, fmt.Sprintf(
"the ServiceImport still exists on cluster %q after unexporting the service", clients[i].name))
}
})
SpecifyWithSpecRef("No clusterset IP should be allocated for a Headless ServiceImport",
"https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#clustersetip",
Label(RequiredLabel), func(ctx context.Context) {
t.awaitServiceImport(ctx, &clients[0], t.helloService.Name, false, nil)
Consistently(func() []string {
return t.getServiceImport(ctx, &clients[0], t.helloService.Name).Spec.IPs
}).Within(5*time.Second).ProbeEvery(time.Second).Should(BeEmpty(), reportNonConformant(""))
})
}
func testExternalNameService() {
t := newTestDriver()
BeforeEach(func() {
t.helloService.Spec.Type = corev1.ServiceTypeExternalName
t.helloService.Spec.ExternalName = "example.com"
t.helloService.Spec.IPFamilyPolicy = nil
})
SpecifyWithSpecRef("Exporting an ExternalName service should set ServiceExport Valid condition to False",
"https://github.com/kubernetes/enhancements/blob/master/keps/sig-multicluster/1645-multi-cluster-services-api/README.md#service-types",
Label(RequiredLabel), func(ctx context.Context) {
t.awaitServiceExportCondition(ctx, &clients[0], v1beta1.ServiceExportConditionValid, metav1.ConditionFalse)
t.ensureNoServiceImport(ctx, &clients[0], helloServiceName,
"the ServiceImport should not exist for an ExternalName service")
})
}
func testServiceTypeConflict() {
t := newTwoClusterTestDriver(newTestDriver())
BeforeEach(func() {
t.helloService2.Spec.ClusterIP = corev1.ClusterIPNone
})
SpecifyWithSpecRef("A service exported on two clusters with conflicting headlessness should apply the conflict resolution policy and "+
"report a Conflict condition on the ServiceExport",
"https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#headlessness",
Label(RequiredLabel), func(ctx context.Context) {
t.awaitServiceExportCondition(ctx, &clients[0], v1beta1.ServiceExportConditionConflict, metav1.ConditionTrue)
t.awaitServiceExportCondition(ctx, &clients[1], v1beta1.ServiceExportConditionConflict, metav1.ConditionTrue)
for i := range clients {
serviceImport := t.awaitServiceImport(ctx, &clients[i], helloServiceName, true, nil)
Expect(serviceImport.Spec.Type).To(Equal(v1beta1.ClusterSetIP), reportNonConformant(
fmt.Sprintf("ServiceImport on cluster %q has type %q", clients[i].name, serviceImport.Spec.Type)))
}
})
}
================================================
FILE: controllers/cmd/servicecontroller/servicecontroller.go
================================================
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"flag"
"os"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/mcs-api/controllers"
"sigs.k8s.io/mcs-api/pkg/apis/v1beta1"
)
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
func init() {
clientgoscheme.AddToScheme(scheme)
v1beta1.AddToScheme(scheme)
}
func main() {
var metricsAddr string
var enableLeaderElection bool
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
flag.Parse()
opts := ctrl.Options{
Scheme: scheme,
Metrics: server.Options{
BindAddress: metricsAddr,
},
LeaderElection: enableLeaderElection,
WebhookServer: webhook.NewServer(webhook.Options{
Port: 9443,
}),
}
ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
if err := controllers.Start(ctrl.SetupSignalHandler(), ctrl.GetConfigOrDie(), setupLog, opts); err != nil {
setupLog.Error(err, "problem running controllers")
os.Exit(1)
}
}
================================================
FILE: controllers/common.go
================================================
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controllers
import (
"context"
"crypto/sha256"
"encoding/base32"
"os"
"strings"
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
)
const (
// DerivedServiceAnnotation is set on a ServiceImport to reference the
// derived Service that represents the imported service for kube-proxy.
DerivedServiceAnnotation = "multicluster.kubernetes.io/derived-service"
serviceImportKind = "ServiceImport"
)
func derivedName(name types.NamespacedName) string {
hash := sha256.New()
hash.Write([]byte(name.String()))
return "derived-" + strings.ToLower(base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString(hash.Sum(nil)))[:10]
}
// Start the controllers with the supplied config
func Start(ctx context.Context, cfg *rest.Config, setupLog logr.Logger, opts ctrl.Options) error {
mgr, err := ctrl.NewManager(cfg, opts)
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
if err = (&ServiceImportReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("ServiceImport"),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ServiceImport")
return err
}
if err = (&ServiceReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Service"),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Service")
return err
}
if err = (&EndpointSliceReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("EndpointSlice"),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "EndpointSlice")
return err
}
setupLog.Info("starting manager")
if err := mgr.Start(ctx); err != nil {
setupLog.Error(err, "problem running manager")
return err
}
return nil
}
================================================
FILE: controllers/controllers_suite_test.go
================================================
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controllers
import (
"context"
"fmt"
"math/rand"
"path/filepath"
"testing"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/kind/pkg/cluster"
"sigs.k8s.io/mcs-api/pkg/apis/v1beta1"
)
const (
clusterName = "test-cluster"
)
var (
cfg *rest.Config
k8s client.Client
env *envtest.Environment
clusterProvider *cluster.Provider
testNS string
)
var _ = BeforeSuite(func(done Done) {
rand.Seed(GinkgoRandomSeed())
log.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)))
// Use Kind for a more up-to-date K8s
clusterProvider = cluster.NewProvider()
Expect(clusterProvider.Create(clusterName)).To(Succeed())
kubeconfig, err := clusterProvider.KubeConfig(clusterName, false)
Expect(err).ToNot(HaveOccurred())
cfg, err := clientcmd.RESTConfigFromKubeConfig([]byte(kubeconfig))
Expect(err).ToNot(HaveOccurred())
scheme := runtime.NewScheme()
Expect(clientgoscheme.AddToScheme(scheme)).To(Succeed())
Expect(v1beta1.AddToScheme(scheme)).To(Succeed())
Expect(err).ToNot(HaveOccurred())
existingCluster := true
env = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd")},
UseExistingCluster: &existingCluster,
Config: cfg,
}
cfg, err = env.Start()
Expect(err).ToNot(HaveOccurred())
Expect(cfg).ToNot(BeNil())
k8s, err = client.New(cfg, client.Options{Scheme: scheme})
Expect(err).ToNot(HaveOccurred())
Expect(k8s).ToNot(BeNil())
testNS = fmt.Sprintf("test-%v", time.Now().Unix())
Expect(k8s.Create(context.Background(), &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: testNS,
},
})).To(Succeed())
opts := ctrl.Options{
Scheme: scheme,
}
go Start(context.TODO(), cfg, log.Log, opts)
close(done)
})
var _ = AfterSuite(func() {
Expect(clusterProvider.Delete(clusterName, "")).To(Succeed())
err := env.Stop()
Expect(err).ToNot(HaveOccurred())
})
func TestControllers(t *testing.T) {
RegisterFailHandler(Fail)
}
================================================
FILE: controllers/endpointslice.go
================================================
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controllers
import (
"context"
"github.com/go-logr/logr"
discoveryv1 "k8s.io/api/discovery/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/mcs-api/pkg/apis/v1beta1"
)
// EndpointSliceReconciler reconciles a EndpointSlice object
type EndpointSliceReconciler struct {
client.Client
Log logr.Logger
}
// +kubebuilder:rbac:groups=discovery.k8s.io,resources=endpointslices,verbs=get;list;watch;update;patch
func shouldIgnoreEndpointSlice(epSlice *discoveryv1.EndpointSlice) bool {
if epSlice.DeletionTimestamp != nil {
return true
}
if epSlice.Labels[v1beta1.LabelServiceName] == "" {
return true
}
return false
}
// Reconcile the changes.
func (r *EndpointSliceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := r.Log.WithValues("endpointslice", req.NamespacedName)
var epSlice discoveryv1.EndpointSlice
if err := r.Client.Get(ctx, req.NamespacedName, &epSlice); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if shouldIgnoreEndpointSlice(&epSlice) {
return ctrl.Result{}, nil
}
// Ensure the EndpointSlice is labelled to match the ServiceImport's derived
// Service.
serviceName := derivedName(types.NamespacedName{Namespace: epSlice.Namespace, Name: epSlice.Labels[v1beta1.LabelServiceName]})
if epSlice.Labels[discoveryv1.LabelServiceName] == serviceName {
return ctrl.Result{}, nil
}
epSlice.Labels[discoveryv1.LabelServiceName] = serviceName
if err := r.Client.Update(ctx, &epSlice); err != nil {
return ctrl.Result{}, err
}
log.Info("added label", discoveryv1.LabelServiceName, serviceName)
return ctrl.Result{}, nil
}
// SetupWithManager wires up the controller.
func (r *EndpointSliceReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).For(&discoveryv1.EndpointSlice{}).Complete(r)
}
================================================
FILE: controllers/endpointslice_test.go
================================================
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controllers
import (
"context"
"fmt"
"math/rand"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
discoveryv1 "k8s.io/api/discovery/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/mcs-api/pkg/apis/v1beta1"
)
var _ = Describe("EndpointSlice", func() {
ctx := context.Background()
Context("should be ignored", func() {
Specify("when not multi-cluster", func() {
Expect(shouldIgnoreEndpointSlice(&discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNS,
Name: "no-mc-service",
},
AddressType: discoveryv1.AddressTypeIPv4,
})).To(BeTrue())
})
Specify("when deleted", func() {
Expect(shouldIgnoreEndpointSlice(&discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNS,
Name: "deleted",
DeletionTimestamp: &metav1.Time{Time: time.Now()},
},
AddressType: discoveryv1.AddressTypeIPv4,
})).To(BeTrue())
})
})
Context("created with mc label", func() {
var (
serviceName types.NamespacedName
derivedServiceName types.NamespacedName
sliceName types.NamespacedName
epSlice discoveryv1.EndpointSlice
)
BeforeEach(func() {
serviceName = types.NamespacedName{Namespace: testNS, Name: fmt.Sprintf("svc-%v", rand.Uint64())}
derivedServiceName = types.NamespacedName{Namespace: testNS, Name: derivedName(serviceName)}
sliceName = types.NamespacedName{Namespace: testNS, Name: fmt.Sprintf("slice-%v", rand.Uint64())}
epSlice = discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNS,
Name: sliceName.Name,
Labels: map[string]string{
v1beta1.LabelServiceName: serviceName.Name,
},
},
AddressType: discoveryv1.AddressTypeIPv4,
}
Expect(k8s.Create(ctx, &epSlice)).To(Succeed())
})
It("has correct label", func() {
Eventually(func() string {
var eps discoveryv1.EndpointSlice
Expect(k8s.Get(ctx, sliceName, &eps)).Should(Succeed())
return eps.Labels[discoveryv1.LabelServiceName]
}).Should(Equal(derivedServiceName.Name))
})
})
Context("created with wrong label", func() {
var (
serviceName types.NamespacedName
derivedServiceName types.NamespacedName
sliceName types.NamespacedName
epSlice discoveryv1.EndpointSlice
)
BeforeEach(func() {
serviceName = types.NamespacedName{Namespace: testNS, Name: fmt.Sprintf("svc-%v", rand.Uint64())}
derivedServiceName = types.NamespacedName{Namespace: testNS, Name: derivedName(serviceName)}
sliceName = types.NamespacedName{Namespace: testNS, Name: fmt.Sprintf("slice-%v", rand.Uint64())}
epSlice = discoveryv1.EndpointSlice{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNS,
Name: sliceName.Name,
Labels: map[string]string{
v1beta1.LabelServiceName: serviceName.Name,
discoveryv1.LabelServiceName: serviceName.Name,
},
},
AddressType: discoveryv1.AddressTypeIPv4,
}
Expect(k8s.Create(ctx, &epSlice)).To(Succeed())
})
It("has correct label", func() {
Eventually(func() string {
var eps discoveryv1.EndpointSlice
Expect(k8s.Get(ctx, sliceName, &eps)).Should(Succeed())
return eps.Labels[discoveryv1.LabelServiceName]
}).Should(Equal(derivedServiceName.Name))
})
})
})
================================================
FILE: controllers/go.mod
================================================
module sigs.k8s.io/mcs-api/controllers
go 1.23.0
require (
github.com/go-logr/logr v1.4.2
github.com/onsi/ginkgo/v2 v2.22.0
github.com/onsi/gomega v1.36.1
k8s.io/api v0.32.5
k8s.io/apimachinery v0.32.5
k8s.io/client-go v0.32.5
sigs.k8s.io/controller-runtime v0.20.4
sigs.k8s.io/kind v0.23.0
sigs.k8s.io/mcs-api v0.3.0
)
replace sigs.k8s.io/mcs-api => ..
require (
github.com/BurntSushi/toml v1.0.0 // indirect
github.com/alessio/shellescape v1.4.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/term v0.25.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.26.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.32.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
================================================
FILE: controllers/go.sum
================================================
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 h1:SJ+NtwL6QaZ21U+IrK7d0gGgpjGGvd2kz+FzTHVzdqI=
github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2/go.mod h1:Tv1PlzqC9t8wNnpPdctvtSUOPUUg4SHeE6vR1Ir2hmg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE
gitextract_e338xpw6/
├── .github/
│ └── workflows/
│ └── auto-label.yml
├── .gitignore
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── Makefile
├── OWNERS
├── README.md
├── RELEASE.md
├── SECURITY.md
├── SECURITY_CONTACTS
├── code-of-conduct.md
├── config/
│ ├── crd/
│ │ ├── embed.go
│ │ ├── multicluster.x-k8s.io_serviceexports.yaml
│ │ └── multicluster.x-k8s.io_serviceimports.yaml
│ ├── crd-base/
│ │ ├── multicluster.x-k8s.io_serviceexports.yaml
│ │ └── multicluster.x-k8s.io_serviceimports.yaml
│ └── rbac/
│ └── role.yaml
├── conformance/
│ ├── clusterip_service_dns.go
│ ├── conformance_suite.go
│ ├── conformance_suite_test.go
│ ├── connectivity.go
│ ├── endpoint_slice.go
│ ├── framework.go
│ ├── go.mod
│ ├── go.sum
│ ├── headless_service_dns.go
│ ├── k8s_objects.go
│ ├── report.go
│ ├── report_template.gohtml
│ └── service_import.go
├── controllers/
│ ├── cmd/
│ │ └── servicecontroller/
│ │ └── servicecontroller.go
│ ├── common.go
│ ├── controllers_suite_test.go
│ ├── endpointslice.go
│ ├── endpointslice_test.go
│ ├── go.mod
│ ├── go.sum
│ ├── service.go
│ ├── serviceimport.go
│ └── serviceimport_test.go
├── demo/
│ ├── .gitignore
│ ├── demo.sh
│ ├── edit-meta
│ ├── reset.sh
│ ├── udemo.sh
│ └── yaml/
│ ├── dep1.yaml
│ ├── dep2.yaml
│ ├── serviceimport-with-vip.yaml
│ ├── serviceimport.yaml
│ └── svc.yaml
├── e2e/
│ ├── connectivity_test.go
│ ├── e2e_suite_test.go
│ ├── go.mod
│ ├── go.sum
│ └── localserviceimpact_test.go
├── go.mod
├── go.sum
├── hack/
│ ├── boilerplate/
│ │ ├── boilerplate.go.txt
│ │ ├── boilerplate.py
│ │ ├── boilerplate.py.txt
│ │ └── boilerplate.sh.txt
│ ├── boilerplate.go.txt
│ ├── kube-env.sh
│ ├── update-codegen.sh
│ ├── update-k8s.sh
│ ├── verify-all.sh
│ ├── verify-boilerplate.sh
│ ├── verify-codegen.sh
│ ├── verify-crd-bump-revision.sh
│ ├── verify-crds.sh
│ ├── verify-gofmt.sh
│ └── verify-golint.sh
├── pkg/
│ ├── apis/
│ │ ├── v1alpha1/
│ │ │ ├── BUILD
│ │ │ ├── doc.go
│ │ │ ├── serviceexport.go
│ │ │ ├── serviceimport.go
│ │ │ ├── well_known_labels.go
│ │ │ ├── zz_generated.deepcopy.go
│ │ │ └── zz_generated.register.go
│ │ └── v1beta1/
│ │ ├── BUILD
│ │ ├── doc.go
│ │ ├── serviceexport.go
│ │ ├── serviceimport.go
│ │ ├── well_known_labels.go
│ │ ├── zz_generated.deepcopy.go
│ │ └── zz_generated.register.go
│ └── client/
│ ├── clientset/
│ │ └── versioned/
│ │ ├── clientset.go
│ │ ├── fake/
│ │ │ ├── clientset_generated.go
│ │ │ ├── doc.go
│ │ │ └── register.go
│ │ ├── scheme/
│ │ │ ├── doc.go
│ │ │ └── register.go
│ │ └── typed/
│ │ └── apis/
│ │ ├── v1alpha1/
│ │ │ ├── apis_client.go
│ │ │ ├── doc.go
│ │ │ ├── fake/
│ │ │ │ ├── doc.go
│ │ │ │ ├── fake_apis_client.go
│ │ │ │ ├── fake_serviceexport.go
│ │ │ │ └── fake_serviceimport.go
│ │ │ ├── generated_expansion.go
│ │ │ ├── serviceexport.go
│ │ │ └── serviceimport.go
│ │ └── v1beta1/
│ │ ├── apis_client.go
│ │ ├── doc.go
│ │ ├── fake/
│ │ │ ├── doc.go
│ │ │ ├── fake_apis_client.go
│ │ │ ├── fake_serviceexport.go
│ │ │ └── fake_serviceimport.go
│ │ ├── generated_expansion.go
│ │ ├── serviceexport.go
│ │ └── serviceimport.go
│ ├── informers/
│ │ └── externalversions/
│ │ ├── apis/
│ │ │ ├── interface.go
│ │ │ ├── v1alpha1/
│ │ │ │ ├── interface.go
│ │ │ │ ├── serviceexport.go
│ │ │ │ └── serviceimport.go
│ │ │ └── v1beta1/
│ │ │ ├── interface.go
│ │ │ ├── serviceexport.go
│ │ │ └── serviceimport.go
│ │ ├── factory.go
│ │ ├── generic.go
│ │ └── internalinterfaces/
│ │ └── factory_interfaces.go
│ └── listers/
│ └── apis/
│ ├── v1alpha1/
│ │ ├── expansion_generated.go
│ │ ├── serviceexport.go
│ │ └── serviceimport.go
│ └── v1beta1/
│ ├── expansion_generated.go
│ ├── serviceexport.go
│ └── serviceimport.go
├── scripts/
│ ├── .gitignore
│ ├── c1.yaml
│ ├── c2.yaml
│ ├── coredns-rbac.json
│ ├── down.sh
│ ├── e2e-test.sh
│ ├── up.sh
│ └── util.sh
└── tools/
├── go.mod
├── go.sum
└── tools.go
SYMBOL INDEX (447 symbols across 62 files)
FILE: config/crd/embed.go
constant ReleaseVersionLabel (line 32) | ReleaseVersionLabel = "multicluster.x-k8s.io/release-version"
constant CustomResourceDefinitionSchemaRevisionLabel (line 34) | CustomResourceDefinitionSchemaRevisionLabel = "multicluster.x-k8s.io/crd...
FILE: conformance/clusterip_service_dns.go
method expectSRVRecords (line 109) | func (t *testDriver) expectSRVRecords(c *clusterClients, domainName stri...
type srvRecord (line 133) | type srvRecord struct
method String (line 138) | func (s srvRecord) String() string {
function parseSRVRecords (line 142) | func parseSRVRecords(str string) []srvRecord {
FILE: conformance/conformance_suite.go
type clusterClients (line 48) | type clusterClients struct
function TestConformance (line 68) | func TestConformance(t *testing.T) {
function init (line 73) | func init() {
function setupClients (line 96) | func setupClients(ctx context.Context) error {
type testDriver (line 157) | type testDriver struct
method createServiceExport (line 215) | func (t *testDriver) createServiceExport(ctx context.Context, c *clust...
method deleteServiceExport (line 223) | func (t *testDriver) deleteServiceExport(ctx context.Context, c *clust...
method deployHelloService (line 230) | func (t *testDriver) deployHelloService(ctx context.Context, c *cluste...
method getServiceImport (line 245) | func (t *testDriver) getServiceImport(ctx context.Context, c *clusterC...
method awaitServiceImport (line 257) | func (t *testDriver) awaitServiceImport(ctx context.Context, c *cluste...
method awaitServiceImportIPFamilies (line 288) | func (t *testDriver) awaitServiceImportIPFamilies(ctx context.Context,...
method awaitNoServiceImport (line 298) | func (t *testDriver) awaitNoServiceImport(ctx context.Context, c *clus...
method ensureServiceImport (line 311) | func (t *testDriver) ensureServiceImport(ctx context.Context, c *clust...
method ensureNoServiceImport (line 318) | func (t *testDriver) ensureNoServiceImport(ctx context.Context, c *clu...
method awaitServiceExportCondition (line 325) | func (t *testDriver) awaitServiceExportCondition(ctx context.Context, ...
method startRequestPod (line 337) | func (t *testDriver) startRequestPod(ctx context.Context, client clust...
method execCmdOnRequestPod (line 355) | func (t *testDriver) execCmdOnRequestPod(c *clusterClients, command []...
method awaitCmdOutputMatches (line 360) | func (t *testDriver) awaitCmdOutputMatches(c *clusterClients, command ...
method awaitServicePodIP (line 376) | func (t *testDriver) awaitServicePodIP(ctx context.Context, c *cluster...
method execPortConnectivityCommand (line 398) | func (t *testDriver) execPortConnectivityCommand(ctx context.Context, ...
function newTestDriver (line 166) | func newTestDriver() *testDriver {
type twoClusterTestDriver (line 411) | type twoClusterTestDriver struct
function newTwoClusterTestDriver (line 417) | func newTwoClusterTestDriver(t *testDriver) *twoClusterTestDriver {
function toMCSPorts (line 449) | func toMCSPorts(from []corev1.ServicePort) []v1beta1.ServicePort {
function sortMCSPorts (line 464) | func sortMCSPorts(p []v1beta1.ServicePort) []v1beta1.ServicePort {
function requireTwoClusters (line 472) | func requireTwoClusters() {
function addressTypeOf (line 478) | func addressTypeOf(f corev1.IPFamily) discoveryv1.AddressType {
function dnsRecordTypeOf (line 486) | func dnsRecordTypeOf(f corev1.IPFamily) string {
function ipFamilyOf (line 494) | func ipFamilyOf(ip string) corev1.IPFamily {
function ncCommand (line 507) | func ncCommand(ipFamily corev1.IPFamily, serviceFQDN string, port int) s...
FILE: conformance/conformance_suite_test.go
function TestConformance (line 26) | func TestConformance(t *testing.T) {
FILE: conformance/endpoint_slice.go
constant K8sEndpointSliceManagedByName (line 33) | K8sEndpointSliceManagedByName = "endpointslice-controller.k8s.io"
method awaitMCSEndpointSlice (line 86) | func (t *testDriver) awaitMCSEndpointSlice(ctx context.Context, c *clust...
FILE: conformance/framework.go
function execCmd (line 31) | func execCmd(k8s kubernetes.Interface, config *rest.Config, podName stri...
FILE: conformance/headless_service_dns.go
type endpointInfo (line 157) | type endpointInfo struct
method String (line 162) | func (e endpointInfo) String() string {
method awaitK8sEndpoints (line 166) | func (t *testDriver) awaitK8sEndpoints(ctx context.Context, c *clusterCl...
type haveAddressesMatcher (line 239) | type haveAddressesMatcher struct
method Match (line 243) | func (m *haveAddressesMatcher) Match(v interface{}) (bool, error) {
method FailureMessage (line 257) | func (m *haveAddressesMatcher) FailureMessage(actual interface{}) stri...
method NegatedFailureMessage (line 261) | func (m *haveAddressesMatcher) NegatedFailureMessage(actual interface{...
function HaveAddresses (line 265) | func HaveAddresses(expected []string) types.GomegaMatcher {
FILE: conformance/k8s_objects.go
constant helloServiceName (line 27) | helloServiceName = "hello"
function newHelloService (line 29) | func newHelloService() *corev1.Service {
function newHelloServiceExport (line 61) | func newHelloServiceExport() *v1beta1.ServiceExport {
function socatListenerScript (line 73) | func socatListenerScript(protocol string) string {
function podContainers (line 112) | func podContainers() []corev1.Container {
function newHelloDeployment (line 157) | func newHelloDeployment() *appsv1.Deployment {
function newStatefulSet (line 181) | func newStatefulSet(replicas int) *appsv1.StatefulSet {
function newRequestPod (line 209) | func newRequestPod() *corev1.Pod {
FILE: conformance/report.go
constant OptionalLabel (line 38) | OptionalLabel = "Optional"
constant RequiredLabel (line 39) | RequiredLabel = "Required"
constant DNSLabel (line 40) | DNSLabel = "DNS"
constant ConnectivityLabel (line 41) | ConnectivityLabel = "Connectivity"
constant ClusterIPLabel (line 42) | ClusterIPLabel = "ClusterIP"
constant HeadlessLabel (line 43) | HeadlessLabel = "Headless"
constant ExternalNameLabel (line 44) | ExternalNameLabel = "ExternalName"
constant EndpointSliceLabel (line 45) | EndpointSliceLabel = "EndpointSlice"
constant ExportedLabelsLabel (line 46) | ExportedLabelsLabel = "ExportedLabels"
constant StrictPortConflictLabel (line 47) | StrictPortConflictLabel = "StrictPortConflict"
constant SpecRefReportEntry (line 48) | SpecRefReportEntry = "spec-ref"
constant NonConformantReportEntry (line 49) | NonConformantReportEntry = "non-conformant"
type testInfo (line 60) | type testInfo struct
type testGrouping (line 71) | type testGrouping struct
type implementationInfo (line 76) | type implementationInfo struct
function SpecifyWithSpecRef (line 98) | func SpecifyWithSpecRef(text string, specRef string, args ...interface{}...
function lookupSpecRef (line 103) | func lookupSpecRef(fullText string) string {
function init (line 112) | func init() {
function parseFailureMessage (line 285) | func parseFailureMessage(s string) string {
function firstLine (line 307) | func firstLine(s string) string {
function reportNonConformant (line 315) | func reportNonConformant(msg string) func() string {
function cancelNonConformanceReport (line 322) | func cancelNonConformanceReport() {
FILE: conformance/service_import.go
function testGeneralServiceImport (line 41) | func testGeneralServiceImport() {
function testClusterIPServiceImport (line 141) | func testClusterIPServiceImport() {
function testHeadlessServiceImport (line 288) | func testHeadlessServiceImport() {
function testExternalNameService (line 325) | func testExternalNameService() {
function testServiceTypeConflict (line 343) | func testServiceTypeConflict() {
FILE: controllers/cmd/servicecontroller/servicecontroller.go
function init (line 39) | func init() {
function main (line 44) | func main() {
FILE: controllers/common.go
constant DerivedServiceAnnotation (line 35) | DerivedServiceAnnotation = "multicluster.kubernetes.io/derived-service"
constant serviceImportKind (line 36) | serviceImportKind = "ServiceImport"
function derivedName (line 39) | func derivedName(name types.NamespacedName) string {
function Start (line 46) | func Start(ctx context.Context, cfg *rest.Config, setupLog logr.Logger, ...
FILE: controllers/controllers_suite_test.go
constant clusterName (line 45) | clusterName = "test-cluster"
function TestControllers (line 106) | func TestControllers(t *testing.T) {
FILE: controllers/endpointslice.go
type EndpointSliceReconciler (line 31) | type EndpointSliceReconciler struct
method Reconcile (line 49) | func (r *EndpointSliceReconciler) Reconcile(ctx context.Context, req c...
method SetupWithManager (line 74) | func (r *EndpointSliceReconciler) SetupWithManager(mgr ctrl.Manager) e...
function shouldIgnoreEndpointSlice (line 38) | func shouldIgnoreEndpointSlice(epSlice *discoveryv1.EndpointSlice) bool {
FILE: controllers/service.go
type ServiceReconciler (line 33) | type ServiceReconciler struct
method Reconcile (line 50) | func (r *ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Re...
method SetupWithManager (line 86) | func (r *ServiceReconciler) SetupWithManager(mgr ctrl.Manager) error {
function serviceImportOwner (line 40) | func serviceImportOwner(refs []metav1.OwnerReference) string {
FILE: controllers/serviceimport.go
type ServiceImportReconciler (line 33) | type ServiceImportReconciler struct
method Reconcile (line 67) | func (r *ServiceImportReconciler) Reconcile(ctx context.Context, req c...
method SetupWithManager (line 146) | func (r *ServiceImportReconciler) SetupWithManager(mgr ctrl.Manager) e...
function servicePorts (line 40) | func servicePorts(svcImport *v1beta1.ServiceImport) []v1.ServicePort {
function shouldIgnoreImport (line 53) | func shouldIgnoreImport(svcImport *v1beta1.ServiceImport) bool {
FILE: e2e/e2e_suite_test.go
function tryParseBool (line 54) | func tryParseBool(s string) bool {
type clusterClients (line 59) | type clusterClients struct
function TestE2E (line 64) | func TestE2E(t *testing.T) {
function execCmd (line 91) | func execCmd(k8s kubernetes.Interface, config *restclient.Config, podNam...
function exportService (line 116) | func exportService(ctx context.Context, fromCluster, toCluster clusterCl...
FILE: hack/boilerplate/boilerplate.py
function get_refs (line 54) | def get_refs():
function file_passes (line 68) | def file_passes(filename, refs, regexs):
function file_extension (line 134) | def file_extension(filename):
function normalize_files (line 146) | def normalize_files(files):
function get_files (line 158) | def get_files(extensions):
function get_regexs (line 186) | def get_regexs():
function main (line 202) | def main():
FILE: pkg/apis/v1alpha1/serviceexport.go
constant ServiceExportPluralName (line 25) | ServiceExportPluralName = "serviceexports"
constant ServiceExportKindName (line 27) | ServiceExportKindName = "ServiceExport"
constant ServiceExportFullName (line 29) | ServiceExportFullName = ServiceExportPluralName + "." + GroupName
type ServiceExport (line 41) | type ServiceExport struct
type ServiceExportSpec (line 57) | type ServiceExportSpec struct
type ServiceExportStatus (line 67) | type ServiceExportStatus struct
constant ServiceExportValid (line 83) | ServiceExportValid = "Valid"
constant ServiceExportConflict (line 92) | ServiceExportConflict = "Conflict"
type ServiceExportList (line 98) | type ServiceExportList struct
type ServiceExportConditionType (line 111) | type ServiceExportConditionType
type ServiceExportConditionReason (line 115) | type ServiceExportConditionReason
function NewServiceExportCondition (line 118) | func NewServiceExportCondition(t ServiceExportConditionType, status meta...
constant ServiceExportConditionValid (line 146) | ServiceExportConditionValid ServiceExportConditionType = "Valid"
constant ServiceExportReasonValid (line 150) | ServiceExportReasonValid ServiceExportConditionReason = "Valid"
constant ServiceExportReasonNoService (line 154) | ServiceExportReasonNoService ServiceExportConditionReason = "NoService"
constant ServiceExportReasonInvalidServiceType (line 159) | ServiceExportReasonInvalidServiceType ServiceExportConditionReason = "In...
constant ServiceExportConditionReady (line 184) | ServiceExportConditionReady ServiceExportConditionType = "Ready"
constant ServiceExportReasonExported (line 190) | ServiceExportReasonExported ServiceExportConditionReason = "Exported"
constant ServiceExportReasonReady (line 196) | ServiceExportReasonReady ServiceExportConditionReason = "Ready"
constant ServiceExportReasonPending (line 200) | ServiceExportReasonPending ServiceExportConditionReason = "Pending"
constant ServiceExportReasonFailed (line 205) | ServiceExportReasonFailed ServiceExportConditionReason = "Failed"
constant ServiceExportConditionConflict (line 235) | ServiceExportConditionConflict ServiceExportConditionType = "Conflict"
constant ServiceExportReasonPortConflict (line 240) | ServiceExportReasonPortConflict ServiceExportConditionReason = "PortConf...
constant ServiceExportReasonTypeConflict (line 245) | ServiceExportReasonTypeConflict ServiceExportConditionReason = "TypeConf...
constant ServiceExportReasonSessionAffinityConflict (line 249) | ServiceExportReasonSessionAffinityConflict ServiceExportConditionReason ...
constant ServiceExportReasonSessionAffinityConfigConflict (line 254) | ServiceExportReasonSessionAffinityConfigConflict ServiceExportConditionR...
constant ServiceExportReasonLabelsConflict (line 259) | ServiceExportReasonLabelsConflict ServiceExportConditionReason = "Labels...
constant ServiceExportReasonAnnotationsConflict (line 264) | ServiceExportReasonAnnotationsConflict ServiceExportConditionReason = "A...
constant ServiceExportReasonInternalTrafficPolicyConflict (line 268) | ServiceExportReasonInternalTrafficPolicyConflict ServiceExportConditionR...
constant ServiceExportReasonTrafficDistributionConflict (line 272) | ServiceExportReasonTrafficDistributionConflict ServiceExportConditionRea...
constant ServiceExportReasonIPFamilyConflict (line 279) | ServiceExportReasonIPFamilyConflict ServiceExportConditionReason = "IPFa...
constant ServiceExportReasonNoConflicts (line 283) | ServiceExportReasonNoConflicts ServiceExportConditionReason = "NoConflicts"
FILE: pkg/apis/v1alpha1/serviceimport.go
constant ServiceImportPluralName (line 26) | ServiceImportPluralName = "serviceimports"
constant ServiceImportKindName (line 28) | ServiceImportKindName = "ServiceImport"
constant ServiceImportFullName (line 30) | ServiceImportFullName = ServiceImportPluralName + "." + GroupName
type ServiceImport (line 41) | type ServiceImport struct
type ServiceImportType (line 55) | type ServiceImportType
constant ClusterSetIP (line 59) | ClusterSetIP ServiceImportType = "ClusterSetIP"
constant Headless (line 61) | Headless ServiceImportType = "Headless"
type ServiceImportSpec (line 65) | type ServiceImportSpec struct
type ServicePort (line 112) | type ServicePort struct
type ServiceImportStatus (line 148) | type ServiceImportStatus struct
type ClusterStatus (line 166) | type ClusterStatus struct
type ServiceImportList (line 175) | type ServiceImportList struct
type ServiceImportConditionType (line 188) | type ServiceImportConditionType
type ServiceImportConditionReason (line 192) | type ServiceImportConditionReason
function NewServiceImportCondition (line 195) | func NewServiceImportCondition(t ServiceImportConditionType, status meta...
constant ServiceImportConditionReady (line 225) | ServiceImportConditionReady ServiceImportConditionType = "Ready"
constant ServiceImportReasonReady (line 229) | ServiceImportReasonReady ServiceImportConditionReason = "Ready"
constant ServiceImportReasonPending (line 233) | ServiceImportReasonPending ServiceImportConditionReason = "Pending"
constant ServiceImportReasonIPFamilyNotSupported (line 238) | ServiceImportReasonIPFamilyNotSupported ServiceImportConditionReason = "...
FILE: pkg/apis/v1alpha1/well_known_labels.go
constant LabelServiceName (line 22) | LabelServiceName = "multicluster.kubernetes.io/service-name"
constant LabelSourceCluster (line 25) | LabelSourceCluster = "multicluster.kubernetes.io/source-cluster"
FILE: pkg/apis/v1alpha1/zz_generated.deepcopy.go
method DeepCopyInto (line 30) | func (in *ClusterStatus) DeepCopyInto(out *ClusterStatus) {
method DeepCopy (line 35) | func (in *ClusterStatus) DeepCopy() *ClusterStatus {
method DeepCopyInto (line 45) | func (in *ServiceExport) DeepCopyInto(out *ServiceExport) {
method DeepCopy (line 54) | func (in *ServiceExport) DeepCopy() *ServiceExport {
method DeepCopyObject (line 64) | func (in *ServiceExport) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 72) | func (in *ServiceExportList) DeepCopyInto(out *ServiceExportList) {
method DeepCopy (line 86) | func (in *ServiceExportList) DeepCopy() *ServiceExportList {
method DeepCopyObject (line 96) | func (in *ServiceExportList) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 104) | func (in *ServiceExportSpec) DeepCopyInto(out *ServiceExportSpec) {
method DeepCopy (line 123) | func (in *ServiceExportSpec) DeepCopy() *ServiceExportSpec {
method DeepCopyInto (line 133) | func (in *ServiceExportStatus) DeepCopyInto(out *ServiceExportStatus) {
method DeepCopy (line 145) | func (in *ServiceExportStatus) DeepCopy() *ServiceExportStatus {
method DeepCopyInto (line 155) | func (in *ServiceImport) DeepCopyInto(out *ServiceImport) {
method DeepCopy (line 164) | func (in *ServiceImport) DeepCopy() *ServiceImport {
method DeepCopyObject (line 174) | func (in *ServiceImport) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 182) | func (in *ServiceImportList) DeepCopyInto(out *ServiceImportList) {
method DeepCopy (line 196) | func (in *ServiceImportList) DeepCopy() *ServiceImportList {
method DeepCopyObject (line 206) | func (in *ServiceImportList) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 214) | func (in *ServiceImportSpec) DeepCopyInto(out *ServiceImportSpec) {
method DeepCopy (line 251) | func (in *ServiceImportSpec) DeepCopy() *ServiceImportSpec {
method DeepCopyInto (line 261) | func (in *ServiceImportStatus) DeepCopyInto(out *ServiceImportStatus) {
method DeepCopy (line 278) | func (in *ServiceImportStatus) DeepCopy() *ServiceImportStatus {
method DeepCopyInto (line 288) | func (in *ServicePort) DeepCopyInto(out *ServicePort) {
method DeepCopy (line 298) | func (in *ServicePort) DeepCopy() *ServicePort {
FILE: pkg/apis/v1alpha1/zz_generated.register.go
constant GroupName (line 31) | GroupName = "multicluster.x-k8s.io"
function Resource (line 41) | func Resource(resource string) schema.GroupResource {
function init (line 54) | func init() {
function addKnownTypes (line 62) | func addKnownTypes(scheme *runtime.Scheme) error {
FILE: pkg/apis/v1beta1/serviceexport.go
constant ServiceExportPluralName (line 25) | ServiceExportPluralName = "serviceexports"
constant ServiceExportKindName (line 27) | ServiceExportKindName = "ServiceExport"
constant ServiceExportFullName (line 29) | ServiceExportFullName = ServiceExportPluralName + "." + GroupName
type ServiceExport (line 42) | type ServiceExport struct
type ServiceExportSpec (line 58) | type ServiceExportSpec struct
type ServiceExportStatus (line 68) | type ServiceExportStatus struct
type ServiceExportList (line 80) | type ServiceExportList struct
type ServiceExportConditionType (line 93) | type ServiceExportConditionType
type ServiceExportConditionReason (line 97) | type ServiceExportConditionReason
function NewServiceExportCondition (line 100) | func NewServiceExportCondition(t ServiceExportConditionType, status meta...
constant ServiceExportConditionValid (line 128) | ServiceExportConditionValid ServiceExportConditionType = "Valid"
constant ServiceExportReasonValid (line 132) | ServiceExportReasonValid ServiceExportConditionReason = "Valid"
constant ServiceExportReasonNoService (line 136) | ServiceExportReasonNoService ServiceExportConditionReason = "NoService"
constant ServiceExportReasonInvalidServiceType (line 141) | ServiceExportReasonInvalidServiceType ServiceExportConditionReason = "In...
constant ServiceExportConditionReady (line 166) | ServiceExportConditionReady ServiceExportConditionType = "Ready"
constant ServiceExportReasonExported (line 172) | ServiceExportReasonExported ServiceExportConditionReason = "Exported"
constant ServiceExportReasonReady (line 178) | ServiceExportReasonReady ServiceExportConditionReason = "Ready"
constant ServiceExportReasonPending (line 182) | ServiceExportReasonPending ServiceExportConditionReason = "Pending"
constant ServiceExportReasonFailed (line 187) | ServiceExportReasonFailed ServiceExportConditionReason = "Failed"
constant ServiceExportConditionConflict (line 217) | ServiceExportConditionConflict ServiceExportConditionType = "Conflict"
constant ServiceExportReasonPortConflict (line 222) | ServiceExportReasonPortConflict ServiceExportConditionReason = "PortConf...
constant ServiceExportReasonTypeConflict (line 227) | ServiceExportReasonTypeConflict ServiceExportConditionReason = "TypeConf...
constant ServiceExportReasonSessionAffinityConflict (line 231) | ServiceExportReasonSessionAffinityConflict ServiceExportConditionReason ...
constant ServiceExportReasonSessionAffinityConfigConflict (line 236) | ServiceExportReasonSessionAffinityConfigConflict ServiceExportConditionR...
constant ServiceExportReasonLabelsConflict (line 241) | ServiceExportReasonLabelsConflict ServiceExportConditionReason = "Labels...
constant ServiceExportReasonAnnotationsConflict (line 246) | ServiceExportReasonAnnotationsConflict ServiceExportConditionReason = "A...
constant ServiceExportReasonInternalTrafficPolicyConflict (line 250) | ServiceExportReasonInternalTrafficPolicyConflict ServiceExportConditionR...
constant ServiceExportReasonTrafficDistributionConflict (line 254) | ServiceExportReasonTrafficDistributionConflict ServiceExportConditionRea...
constant ServiceExportReasonIPFamilyConflict (line 261) | ServiceExportReasonIPFamilyConflict ServiceExportConditionReason = "IPFa...
constant ServiceExportReasonNoConflicts (line 265) | ServiceExportReasonNoConflicts ServiceExportConditionReason = "NoConflicts"
FILE: pkg/apis/v1beta1/serviceimport.go
constant ServiceImportPluralName (line 26) | ServiceImportPluralName = "serviceimports"
constant ServiceImportKindName (line 28) | ServiceImportKindName = "ServiceImport"
constant ServiceImportFullName (line 30) | ServiceImportFullName = ServiceImportPluralName + "." + GroupName
type ServiceImport (line 42) | type ServiceImport struct
type ServiceImportType (line 56) | type ServiceImportType
constant ClusterSetIP (line 60) | ClusterSetIP ServiceImportType = "ClusterSetIP"
constant Headless (line 62) | Headless ServiceImportType = "Headless"
type ServiceImportSpec (line 66) | type ServiceImportSpec struct
type ServicePort (line 113) | type ServicePort struct
type ServiceImportStatus (line 149) | type ServiceImportStatus struct
type ClusterStatus (line 167) | type ClusterStatus struct
type ServiceImportList (line 176) | type ServiceImportList struct
type ServiceImportConditionType (line 189) | type ServiceImportConditionType
type ServiceImportConditionReason (line 193) | type ServiceImportConditionReason
function NewServiceImportCondition (line 196) | func NewServiceImportCondition(t ServiceImportConditionType, status meta...
constant ServiceImportConditionReady (line 226) | ServiceImportConditionReady ServiceImportConditionType = "Ready"
constant ServiceImportReasonReady (line 230) | ServiceImportReasonReady ServiceImportConditionReason = "Ready"
constant ServiceImportReasonPending (line 234) | ServiceImportReasonPending ServiceImportConditionReason = "Pending"
constant ServiceImportReasonIPFamilyNotSupported (line 239) | ServiceImportReasonIPFamilyNotSupported ServiceImportConditionReason = "...
FILE: pkg/apis/v1beta1/well_known_labels.go
constant LabelServiceName (line 22) | LabelServiceName = "multicluster.kubernetes.io/service-name"
constant LabelSourceCluster (line 25) | LabelSourceCluster = "multicluster.kubernetes.io/source-cluster"
FILE: pkg/apis/v1beta1/zz_generated.deepcopy.go
method DeepCopyInto (line 30) | func (in *ClusterStatus) DeepCopyInto(out *ClusterStatus) {
method DeepCopy (line 35) | func (in *ClusterStatus) DeepCopy() *ClusterStatus {
method DeepCopyInto (line 45) | func (in *ServiceExport) DeepCopyInto(out *ServiceExport) {
method DeepCopy (line 54) | func (in *ServiceExport) DeepCopy() *ServiceExport {
method DeepCopyObject (line 64) | func (in *ServiceExport) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 72) | func (in *ServiceExportList) DeepCopyInto(out *ServiceExportList) {
method DeepCopy (line 86) | func (in *ServiceExportList) DeepCopy() *ServiceExportList {
method DeepCopyObject (line 96) | func (in *ServiceExportList) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 104) | func (in *ServiceExportSpec) DeepCopyInto(out *ServiceExportSpec) {
method DeepCopy (line 123) | func (in *ServiceExportSpec) DeepCopy() *ServiceExportSpec {
method DeepCopyInto (line 133) | func (in *ServiceExportStatus) DeepCopyInto(out *ServiceExportStatus) {
method DeepCopy (line 145) | func (in *ServiceExportStatus) DeepCopy() *ServiceExportStatus {
method DeepCopyInto (line 155) | func (in *ServiceImport) DeepCopyInto(out *ServiceImport) {
method DeepCopy (line 164) | func (in *ServiceImport) DeepCopy() *ServiceImport {
method DeepCopyObject (line 174) | func (in *ServiceImport) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 182) | func (in *ServiceImportList) DeepCopyInto(out *ServiceImportList) {
method DeepCopy (line 196) | func (in *ServiceImportList) DeepCopy() *ServiceImportList {
method DeepCopyObject (line 206) | func (in *ServiceImportList) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 214) | func (in *ServiceImportSpec) DeepCopyInto(out *ServiceImportSpec) {
method DeepCopy (line 251) | func (in *ServiceImportSpec) DeepCopy() *ServiceImportSpec {
method DeepCopyInto (line 261) | func (in *ServiceImportStatus) DeepCopyInto(out *ServiceImportStatus) {
method DeepCopy (line 278) | func (in *ServiceImportStatus) DeepCopy() *ServiceImportStatus {
method DeepCopyInto (line 288) | func (in *ServicePort) DeepCopyInto(out *ServicePort) {
method DeepCopy (line 298) | func (in *ServicePort) DeepCopy() *ServicePort {
FILE: pkg/apis/v1beta1/zz_generated.register.go
constant GroupName (line 31) | GroupName = "multicluster.x-k8s.io"
function Resource (line 41) | func Resource(resource string) schema.GroupResource {
function init (line 54) | func init() {
function addKnownTypes (line 62) | func addKnownTypes(scheme *runtime.Scheme) error {
FILE: pkg/client/clientset/versioned/clientset.go
type Interface (line 32) | type Interface interface
type Clientset (line 39) | type Clientset struct
method MulticlusterV1alpha1 (line 46) | func (c *Clientset) MulticlusterV1alpha1() multiclusterv1alpha1.Multic...
method MulticlusterV1beta1 (line 51) | func (c *Clientset) MulticlusterV1beta1() multiclusterv1beta1.Multiclu...
method Discovery (line 56) | func (c *Clientset) Discovery() discovery.DiscoveryInterface {
function NewForConfig (line 68) | func NewForConfig(c *rest.Config) (*Clientset, error) {
function NewForConfigAndClient (line 88) | func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Cl...
function NewForConfigOrDie (line 117) | func NewForConfigOrDie(c *rest.Config) *Clientset {
function New (line 126) | func New(c rest.Interface) *Clientset {
FILE: pkg/client/clientset/versioned/fake/clientset_generated.go
function NewSimpleClientset (line 42) | func NewSimpleClientset(objects ...runtime.Object) *Clientset {
type Clientset (line 69) | type Clientset struct
method Discovery (line 75) | func (c *Clientset) Discovery() discovery.DiscoveryInterface {
method Tracker (line 79) | func (c *Clientset) Tracker() testing.ObjectTracker {
method MulticlusterV1alpha1 (line 89) | func (c *Clientset) MulticlusterV1alpha1() multiclusterv1alpha1.Multic...
method MulticlusterV1beta1 (line 94) | func (c *Clientset) MulticlusterV1beta1() multiclusterv1beta1.Multiclu...
FILE: pkg/client/clientset/versioned/fake/register.go
function init (line 55) | func init() {
FILE: pkg/client/clientset/versioned/scheme/register.go
function init (line 55) | func init() {
FILE: pkg/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go
type MulticlusterV1alpha1Interface (line 29) | type MulticlusterV1alpha1Interface interface
type MulticlusterV1alpha1Client (line 36) | type MulticlusterV1alpha1Client struct
method ServiceExports (line 40) | func (c *MulticlusterV1alpha1Client) ServiceExports(namespace string) ...
method ServiceImports (line 44) | func (c *MulticlusterV1alpha1Client) ServiceImports(namespace string) ...
method RESTClient (line 107) | func (c *MulticlusterV1alpha1Client) RESTClient() rest.Interface {
function NewForConfig (line 51) | func NewForConfig(c *rest.Config) (*MulticlusterV1alpha1Client, error) {
function NewForConfigAndClient (line 65) | func NewForConfigAndClient(c *rest.Config, h *http.Client) (*Multicluste...
function NewForConfigOrDie (line 79) | func NewForConfigOrDie(c *rest.Config) *MulticlusterV1alpha1Client {
function New (line 88) | func New(c rest.Interface) *MulticlusterV1alpha1Client {
function setConfigDefaults (line 92) | func setConfigDefaults(config *rest.Config) error {
FILE: pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go
type FakeMulticlusterV1alpha1 (line 27) | type FakeMulticlusterV1alpha1 struct
method ServiceExports (line 31) | func (c *FakeMulticlusterV1alpha1) ServiceExports(namespace string) v1...
method ServiceImports (line 35) | func (c *FakeMulticlusterV1alpha1) ServiceImports(namespace string) v1...
method RESTClient (line 41) | func (c *FakeMulticlusterV1alpha1) RESTClient() rest.Interface {
FILE: pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_serviceexport.go
type fakeServiceExports (line 28) | type fakeServiceExports struct
function newFakeServiceExports (line 33) | func newFakeServiceExports(fake *FakeMulticlusterV1alpha1, namespace str...
FILE: pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_serviceimport.go
type fakeServiceImports (line 28) | type fakeServiceImports struct
function newFakeServiceImports (line 33) | func newFakeServiceImports(fake *FakeMulticlusterV1alpha1, namespace str...
FILE: pkg/client/clientset/versioned/typed/apis/v1alpha1/generated_expansion.go
type ServiceExportExpansion (line 21) | type ServiceExportExpansion interface
type ServiceImportExpansion (line 23) | type ServiceImportExpansion interface
FILE: pkg/client/clientset/versioned/typed/apis/v1alpha1/serviceexport.go
type ServiceExportsGetter (line 34) | type ServiceExportsGetter interface
type ServiceExportInterface (line 39) | type ServiceExportInterface interface
type serviceExports (line 54) | type serviceExports struct
function newServiceExports (line 59) | func newServiceExports(c *MulticlusterV1alpha1Client, namespace string) ...
FILE: pkg/client/clientset/versioned/typed/apis/v1alpha1/serviceimport.go
type ServiceImportsGetter (line 34) | type ServiceImportsGetter interface
type ServiceImportInterface (line 39) | type ServiceImportInterface interface
type serviceImports (line 54) | type serviceImports struct
function newServiceImports (line 59) | func newServiceImports(c *MulticlusterV1alpha1Client, namespace string) ...
FILE: pkg/client/clientset/versioned/typed/apis/v1beta1/apis_client.go
type MulticlusterV1beta1Interface (line 29) | type MulticlusterV1beta1Interface interface
type MulticlusterV1beta1Client (line 36) | type MulticlusterV1beta1Client struct
method ServiceExports (line 40) | func (c *MulticlusterV1beta1Client) ServiceExports(namespace string) S...
method ServiceImports (line 44) | func (c *MulticlusterV1beta1Client) ServiceImports(namespace string) S...
method RESTClient (line 107) | func (c *MulticlusterV1beta1Client) RESTClient() rest.Interface {
function NewForConfig (line 51) | func NewForConfig(c *rest.Config) (*MulticlusterV1beta1Client, error) {
function NewForConfigAndClient (line 65) | func NewForConfigAndClient(c *rest.Config, h *http.Client) (*Multicluste...
function NewForConfigOrDie (line 79) | func NewForConfigOrDie(c *rest.Config) *MulticlusterV1beta1Client {
function New (line 88) | func New(c rest.Interface) *MulticlusterV1beta1Client {
function setConfigDefaults (line 92) | func setConfigDefaults(config *rest.Config) error {
FILE: pkg/client/clientset/versioned/typed/apis/v1beta1/fake/fake_apis_client.go
type FakeMulticlusterV1beta1 (line 27) | type FakeMulticlusterV1beta1 struct
method ServiceExports (line 31) | func (c *FakeMulticlusterV1beta1) ServiceExports(namespace string) v1b...
method ServiceImports (line 35) | func (c *FakeMulticlusterV1beta1) ServiceImports(namespace string) v1b...
method RESTClient (line 41) | func (c *FakeMulticlusterV1beta1) RESTClient() rest.Interface {
FILE: pkg/client/clientset/versioned/typed/apis/v1beta1/fake/fake_serviceexport.go
type fakeServiceExports (line 28) | type fakeServiceExports struct
function newFakeServiceExports (line 33) | func newFakeServiceExports(fake *FakeMulticlusterV1beta1, namespace stri...
FILE: pkg/client/clientset/versioned/typed/apis/v1beta1/fake/fake_serviceimport.go
type fakeServiceImports (line 28) | type fakeServiceImports struct
function newFakeServiceImports (line 33) | func newFakeServiceImports(fake *FakeMulticlusterV1beta1, namespace stri...
FILE: pkg/client/clientset/versioned/typed/apis/v1beta1/generated_expansion.go
type ServiceExportExpansion (line 21) | type ServiceExportExpansion interface
type ServiceImportExpansion (line 23) | type ServiceImportExpansion interface
FILE: pkg/client/clientset/versioned/typed/apis/v1beta1/serviceexport.go
type ServiceExportsGetter (line 34) | type ServiceExportsGetter interface
type ServiceExportInterface (line 39) | type ServiceExportInterface interface
type serviceExports (line 54) | type serviceExports struct
function newServiceExports (line 59) | func newServiceExports(c *MulticlusterV1beta1Client, namespace string) *...
FILE: pkg/client/clientset/versioned/typed/apis/v1beta1/serviceimport.go
type ServiceImportsGetter (line 34) | type ServiceImportsGetter interface
type ServiceImportInterface (line 39) | type ServiceImportInterface interface
type serviceImports (line 54) | type serviceImports struct
function newServiceImports (line 59) | func newServiceImports(c *MulticlusterV1beta1Client, namespace string) *...
FILE: pkg/client/informers/externalversions/apis/interface.go
type Interface (line 28) | type Interface interface
type group (line 35) | type group struct
method V1alpha1 (line 47) | func (g *group) V1alpha1() v1alpha1.Interface {
method V1beta1 (line 52) | func (g *group) V1beta1() v1beta1.Interface {
function New (line 42) | func New(f internalinterfaces.SharedInformerFactory, namespace string, t...
FILE: pkg/client/informers/externalversions/apis/v1alpha1/interface.go
type Interface (line 26) | type Interface interface
type version (line 33) | type version struct
method ServiceExports (line 45) | func (v *version) ServiceExports() ServiceExportInformer {
method ServiceImports (line 50) | func (v *version) ServiceImports() ServiceImportInformer {
function New (line 40) | func New(f internalinterfaces.SharedInformerFactory, namespace string, t...
FILE: pkg/client/informers/externalversions/apis/v1alpha1/serviceexport.go
type ServiceExportInformer (line 37) | type ServiceExportInformer interface
type serviceExportInformer (line 42) | type serviceExportInformer struct
method defaultInformer (line 80) | func (f *serviceExportInformer) defaultInformer(client versioned.Inter...
method Informer (line 84) | func (f *serviceExportInformer) Informer() cache.SharedIndexInformer {
method Lister (line 88) | func (f *serviceExportInformer) Lister() apisv1alpha1.ServiceExportLis...
function NewServiceExportInformer (line 51) | func NewServiceExportInformer(client versioned.Interface, namespace stri...
function NewFilteredServiceExportInformer (line 58) | func NewFilteredServiceExportInformer(client versioned.Interface, namesp...
FILE: pkg/client/informers/externalversions/apis/v1alpha1/serviceimport.go
type ServiceImportInformer (line 37) | type ServiceImportInformer interface
type serviceImportInformer (line 42) | type serviceImportInformer struct
method defaultInformer (line 80) | func (f *serviceImportInformer) defaultInformer(client versioned.Inter...
method Informer (line 84) | func (f *serviceImportInformer) Informer() cache.SharedIndexInformer {
method Lister (line 88) | func (f *serviceImportInformer) Lister() apisv1alpha1.ServiceImportLis...
function NewServiceImportInformer (line 51) | func NewServiceImportInformer(client versioned.Interface, namespace stri...
function NewFilteredServiceImportInformer (line 58) | func NewFilteredServiceImportInformer(client versioned.Interface, namesp...
FILE: pkg/client/informers/externalversions/apis/v1beta1/interface.go
type Interface (line 26) | type Interface interface
type version (line 33) | type version struct
method ServiceExports (line 45) | func (v *version) ServiceExports() ServiceExportInformer {
method ServiceImports (line 50) | func (v *version) ServiceImports() ServiceImportInformer {
function New (line 40) | func New(f internalinterfaces.SharedInformerFactory, namespace string, t...
FILE: pkg/client/informers/externalversions/apis/v1beta1/serviceexport.go
type ServiceExportInformer (line 37) | type ServiceExportInformer interface
type serviceExportInformer (line 42) | type serviceExportInformer struct
method defaultInformer (line 80) | func (f *serviceExportInformer) defaultInformer(client versioned.Inter...
method Informer (line 84) | func (f *serviceExportInformer) Informer() cache.SharedIndexInformer {
method Lister (line 88) | func (f *serviceExportInformer) Lister() apisv1beta1.ServiceExportList...
function NewServiceExportInformer (line 51) | func NewServiceExportInformer(client versioned.Interface, namespace stri...
function NewFilteredServiceExportInformer (line 58) | func NewFilteredServiceExportInformer(client versioned.Interface, namesp...
FILE: pkg/client/informers/externalversions/apis/v1beta1/serviceimport.go
type ServiceImportInformer (line 37) | type ServiceImportInformer interface
type serviceImportInformer (line 42) | type serviceImportInformer struct
method defaultInformer (line 80) | func (f *serviceImportInformer) defaultInformer(client versioned.Inter...
method Informer (line 84) | func (f *serviceImportInformer) Informer() cache.SharedIndexInformer {
method Lister (line 88) | func (f *serviceImportInformer) Lister() apisv1beta1.ServiceImportList...
function NewServiceImportInformer (line 51) | func NewServiceImportInformer(client versioned.Interface, namespace stri...
function NewFilteredServiceImportInformer (line 58) | func NewFilteredServiceImportInformer(client versioned.Interface, namesp...
FILE: pkg/client/informers/externalversions/factory.go
type SharedInformerOption (line 36) | type SharedInformerOption
type sharedInformerFactory (line 38) | type sharedInformerFactory struct
method Start (line 124) | func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) {
method Shutdown (line 148) | func (f *sharedInformerFactory) Shutdown() {
method WaitForCacheSync (line 157) | func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{...
method InformerFor (line 180) | func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFun...
method Multicluster (line 260) | func (f *sharedInformerFactory) Multicluster() apis.Interface {
function WithCustomResyncConfig (line 59) | func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) Sh...
function WithTweakListOptions (line 69) | func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListO...
function WithNamespace (line 77) | func WithNamespace(namespace string) SharedInformerOption {
function WithTransform (line 85) | func WithTransform(transform cache.TransformFunc) SharedInformerOption {
function NewSharedInformerFactory (line 93) | func NewSharedInformerFactory(client versioned.Interface, defaultResync ...
function NewFilteredSharedInformerFactory (line 101) | func NewFilteredSharedInformerFactory(client versioned.Interface, defaul...
function NewSharedInformerFactoryWithOptions (line 106) | func NewSharedInformerFactoryWithOptions(client versioned.Interface, def...
type SharedInformerFactory (line 226) | type SharedInformerFactory interface
FILE: pkg/client/informers/externalversions/generic.go
type GenericInformer (line 32) | type GenericInformer interface
type genericInformer (line 37) | type genericInformer struct
method Informer (line 43) | func (f *genericInformer) Informer() cache.SharedIndexInformer {
method Lister (line 48) | func (f *genericInformer) Lister() cache.GenericLister {
method ForResource (line 54) | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersion...
FILE: pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go
type NewInformerFunc (line 31) | type NewInformerFunc
type SharedInformerFactory (line 34) | type SharedInformerFactory interface
type TweakListOptionsFunc (line 40) | type TweakListOptionsFunc
FILE: pkg/client/listers/apis/v1alpha1/expansion_generated.go
type ServiceExportListerExpansion (line 23) | type ServiceExportListerExpansion interface
type ServiceExportNamespaceListerExpansion (line 27) | type ServiceExportNamespaceListerExpansion interface
type ServiceImportListerExpansion (line 31) | type ServiceImportListerExpansion interface
type ServiceImportNamespaceListerExpansion (line 35) | type ServiceImportNamespaceListerExpansion interface
FILE: pkg/client/listers/apis/v1alpha1/serviceexport.go
type ServiceExportLister (line 30) | type ServiceExportLister interface
type serviceExportLister (line 40) | type serviceExportLister struct
method ServiceExports (line 50) | func (s *serviceExportLister) ServiceExports(namespace string) Service...
function NewServiceExportLister (line 45) | func NewServiceExportLister(indexer cache.Indexer) ServiceExportLister {
type ServiceExportNamespaceLister (line 56) | type ServiceExportNamespaceLister interface
type serviceExportNamespaceLister (line 68) | type serviceExportNamespaceLister struct
FILE: pkg/client/listers/apis/v1alpha1/serviceimport.go
type ServiceImportLister (line 30) | type ServiceImportLister interface
type serviceImportLister (line 40) | type serviceImportLister struct
method ServiceImports (line 50) | func (s *serviceImportLister) ServiceImports(namespace string) Service...
function NewServiceImportLister (line 45) | func NewServiceImportLister(indexer cache.Indexer) ServiceImportLister {
type ServiceImportNamespaceLister (line 56) | type ServiceImportNamespaceLister interface
type serviceImportNamespaceLister (line 68) | type serviceImportNamespaceLister struct
FILE: pkg/client/listers/apis/v1beta1/expansion_generated.go
type ServiceExportListerExpansion (line 23) | type ServiceExportListerExpansion interface
type ServiceExportNamespaceListerExpansion (line 27) | type ServiceExportNamespaceListerExpansion interface
type ServiceImportListerExpansion (line 31) | type ServiceImportListerExpansion interface
type ServiceImportNamespaceListerExpansion (line 35) | type ServiceImportNamespaceListerExpansion interface
FILE: pkg/client/listers/apis/v1beta1/serviceexport.go
type ServiceExportLister (line 30) | type ServiceExportLister interface
type serviceExportLister (line 40) | type serviceExportLister struct
method ServiceExports (line 50) | func (s *serviceExportLister) ServiceExports(namespace string) Service...
function NewServiceExportLister (line 45) | func NewServiceExportLister(indexer cache.Indexer) ServiceExportLister {
type ServiceExportNamespaceLister (line 56) | type ServiceExportNamespaceLister interface
type serviceExportNamespaceLister (line 68) | type serviceExportNamespaceLister struct
FILE: pkg/client/listers/apis/v1beta1/serviceimport.go
type ServiceImportLister (line 30) | type ServiceImportLister interface
type serviceImportLister (line 40) | type serviceImportLister struct
method ServiceImports (line 50) | func (s *serviceImportLister) ServiceImports(namespace string) Service...
function NewServiceImportLister (line 45) | func NewServiceImportLister(indexer cache.Indexer) ServiceImportLister {
type ServiceImportNamespaceLister (line 56) | type ServiceImportNamespaceLister interface
type serviceImportNamespaceLister (line 68) | type serviceImportNamespaceLister struct
Condensed preview — 138 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (522K chars).
[
{
"path": ".github/workflows/auto-label.yml",
"chars": 427,
"preview": "---\nname: Label issues\non:\n issues:\n types:\n - opened\n - reopened\njobs:\n label_issues:\n runs-on: ubunt"
},
{
"path": ".gitignore",
"chars": 531,
"preview": "*~\n.\\#*\n._*\n\\#*\\#\n/_artifacts/\n/bazel-*\nbin\n.classpath\n/cluster\n/.config/gcloud*/\n*.dll\n/doc_tmp/\n!\\.drone\\.sec\n.DS_Stor"
},
{
"path": "CONTRIBUTING.md",
"chars": 1687,
"preview": "# Contributing Guidelines\n\nWelcome to Kubernetes. We are excited about the prospect of you joining our [community](https"
},
{
"path": "Dockerfile",
"chars": 719,
"preview": "# Build the manager binary\nFROM golang:1.23 as builder\n\nWORKDIR /workspace\n# Copy the Go Modules manifests\nCOPY go.mod g"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "Makefile",
"chars": 3055,
"preview": "# Copyright 2019 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
},
{
"path": "OWNERS",
"chars": 314,
"preview": "# See the OWNERS docs at https://go.k8s.io/owners\n\nreviewers:\n - jeremyot\n - lauralorenz\n - skitt\n - MrFreezeex\n - "
},
{
"path": "README.md",
"chars": 1670,
"preview": "# Multi-cluster Service APIs\n\nThis repository hosts the Multi-Cluster Service APIs. Providers can import packages in thi"
},
{
"path": "RELEASE.md",
"chars": 529,
"preview": "# Release Process\n\nThe Kubernetes Template Project is released on an as-needed basis. The process is as follows:\n\n1. An "
},
{
"path": "SECURITY.md",
"chars": 1069,
"preview": "# Security Policy\n\n## Security Announcements\n\nJoin the [kubernetes-security-announce] group for security and vulnerabili"
},
{
"path": "SECURITY_CONTACTS",
"chars": 534,
"preview": "# Defined below are the security contacts for this repo.\n#\n# They are the contact point for the Product Security Committ"
},
{
"path": "code-of-conduct.md",
"chars": 148,
"preview": "# Kubernetes Community Code of Conduct\n\nPlease refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/co"
},
{
"path": "config/crd/embed.go",
"chars": 1281,
"preview": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "config/crd/multicluster.x-k8s.io_serviceexports.yaml",
"chars": 12654,
"preview": "# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
},
{
"path": "config/crd/multicluster.x-k8s.io_serviceimports.yaml",
"chars": 25496,
"preview": "# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
},
{
"path": "config/crd-base/multicluster.x-k8s.io_serviceexports.yaml",
"chars": 1620,
"preview": "# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
},
{
"path": "config/crd-base/multicluster.x-k8s.io_serviceimports.yaml",
"chars": 2066,
"preview": "# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
},
{
"path": "config/rbac/role.yaml",
"chars": 501,
"preview": "---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n name: mcs-derived-service-manager\nrules:\n- ap"
},
{
"path": "conformance/clusterip_service_dns.go",
"chars": 6069,
"preview": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "conformance/conformance_suite.go",
"chars": 18557,
"preview": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "conformance/conformance_suite_test.go",
"chars": 780,
"preview": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "conformance/connectivity.go",
"chars": 5051,
"preview": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "conformance/endpoint_slice.go",
"chars": 5000,
"preview": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "conformance/framework.go",
"chars": 1682,
"preview": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "conformance/go.mod",
"chars": 2467,
"preview": "module sigs.k8s.io/mcs-api/conformance\n\ngo 1.23.0\n\nrequire (\n\tgithub.com/onsi/ginkgo/v2 v2.21.0\n\tgithub.com/onsi/gomega "
},
{
"path": "conformance/go.sum",
"chars": 14811,
"preview": "github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=\ngithub.com"
},
{
"path": "conformance/headless_service_dns.go",
"chars": 9288,
"preview": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "conformance/k8s_objects.go",
"chars": 6538,
"preview": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "conformance/report.go",
"chars": 9349,
"preview": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "conformance/report_template.gohtml",
"chars": 1352,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <title>MCS API conformance report</title>\n <st"
},
{
"path": "conformance/service_import.go",
"chars": 17785,
"preview": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "controllers/cmd/servicecontroller/servicecontroller.go",
"chars": 1992,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "controllers/common.go",
"chars": 2532,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "controllers/controllers_suite_test.go",
"chars": 3030,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "controllers/endpointslice.go",
"chars": 2501,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "controllers/endpointslice_test.go",
"chars": 3967,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "controllers/go.mod",
"chars": 3456,
"preview": "module sigs.k8s.io/mcs-api/controllers\n\ngo 1.23.0\n\nrequire (\n\tgithub.com/go-logr/logr v1.4.2\n\tgithub.com/onsi/ginkgo/v2 "
},
{
"path": "controllers/go.sum",
"chars": 18806,
"preview": "github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=\ngithub.com/BurntSushi/toml v1.0.0/go.m"
},
{
"path": "controllers/service.go",
"chars": 2684,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "controllers/serviceimport.go",
"chars": 4461,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "controllers/serviceimport_test.go",
"chars": 5923,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "demo/.gitignore",
"chars": 18,
"preview": "*.kubeconfig\n*.tmp"
},
{
"path": "demo/demo.sh",
"chars": 4115,
"preview": "#!/bin/bash\n\n# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
},
{
"path": "demo/edit-meta",
"chars": 556,
"preview": "#!/usr/bin/env python3\n\nimport sys\nimport argparse\nimport yaml\n\nif __name__ == '__main__':\n parser = argparse.Argumen"
},
{
"path": "demo/reset.sh",
"chars": 784,
"preview": "#!/bin/bash\n\n# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
},
{
"path": "demo/udemo.sh",
"chars": 2083,
"preview": "#!/bin/bash\n# Copyright 2016 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");"
},
{
"path": "demo/yaml/dep1.yaml",
"chars": 666,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: serve\n namespace: demo\nspec:\n replicas: 1\n selector:\n match"
},
{
"path": "demo/yaml/dep2.yaml",
"chars": 666,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: serve\n namespace: demo\nspec:\n replicas: 1\n selector:\n match"
},
{
"path": "demo/yaml/serviceimport-with-vip.yaml",
"chars": 199,
"preview": "apiVersion: multicluster.x-k8s.io/v1beta1\nkind: ServiceImport\nmetadata:\n name: serve-with-vip\n namespace: demo\nspec:\n "
},
{
"path": "demo/yaml/serviceimport.yaml",
"chars": 171,
"preview": "apiVersion: multicluster.x-k8s.io/v1beta1\nkind: ServiceImport\nmetadata:\n name: serve\n namespace: demo\nspec:\n type: Cl"
},
{
"path": "demo/yaml/svc.yaml",
"chars": 147,
"preview": "apiVersion: v1\nkind: Service\nmetadata:\n name: serve\n namespace: demo\nspec:\n ports:\n - port: 80\n targetPort: 8080\n"
},
{
"path": "e2e/connectivity_test.go",
"chars": 9289,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "e2e/e2e_suite_test.go",
"chars": 4951,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "e2e/go.mod",
"chars": 2483,
"preview": "module sigs.k8s.io/mcs-api/e2e\n\ngo 1.23.0\n\nrequire (\n\tgithub.com/onsi/ginkgo/v2 v2.21.0\n\tgithub.com/onsi/gomega v1.35.1\n"
},
{
"path": "e2e/go.sum",
"chars": 14811,
"preview": "github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=\ngithub.com"
},
{
"path": "e2e/localserviceimpact_test.go",
"chars": 7072,
"preview": "/*\nCopyright 2024 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "go.mod",
"chars": 1970,
"preview": "module sigs.k8s.io/mcs-api\n\ngo 1.23.0\n\nrequire (\n\tk8s.io/api v0.32.5\n\tk8s.io/apimachinery v0.32.5\n\tk8s.io/client-go v0.3"
},
{
"path": "go.sum",
"chars": 14011,
"preview": "github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go"
},
{
"path": "hack/boilerplate/boilerplate.go.txt",
"chars": 569,
"preview": "/*\nCopyright YEAR The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "hack/boilerplate/boilerplate.py",
"chars": 6260,
"preview": "#!/usr/bin/env python3\n\n# Copyright 2015 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the"
},
{
"path": "hack/boilerplate/boilerplate.py.txt",
"chars": 612,
"preview": "#!/usr/bin/env python3\n\n# Copyright YEAR The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the"
},
{
"path": "hack/boilerplate/boilerplate.sh.txt",
"chars": 588,
"preview": "# Copyright YEAR The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
},
{
"path": "hack/boilerplate.go.txt",
"chars": 570,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "hack/kube-env.sh",
"chars": 1823,
"preview": "#!/bin/bash\n\n# Copyright 2014 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
},
{
"path": "hack/update-codegen.sh",
"chars": 2763,
"preview": "#!/usr/bin/env bash\n\n# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"L"
},
{
"path": "hack/update-k8s.sh",
"chars": 1123,
"preview": "#!/usr/bin/env bash\n\n# Copyright 2025 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"L"
},
{
"path": "hack/verify-all.sh",
"chars": 1643,
"preview": "#!/bin/bash\n\n# Copyright 2014 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
},
{
"path": "hack/verify-boilerplate.sh",
"chars": 1014,
"preview": "#!/bin/bash\n\n# Copyright 2014 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
},
{
"path": "hack/verify-codegen.sh",
"chars": 764,
"preview": "#!/bin/bash\n\n# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
},
{
"path": "hack/verify-crd-bump-revision.sh",
"chars": 1150,
"preview": "#!/bin/bash\n\n# Copyright 2025 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
},
{
"path": "hack/verify-crds.sh",
"chars": 1766,
"preview": "#!/bin/bash\n\n# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
},
{
"path": "hack/verify-gofmt.sh",
"chars": 1093,
"preview": "#!/bin/bash\n\n# Copyright 2014 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
},
{
"path": "hack/verify-golint.sh",
"chars": 1255,
"preview": "#!/bin/bash\n\n# Copyright 2014 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
},
{
"path": "pkg/apis/v1alpha1/BUILD",
"chars": 1019,
"preview": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n name = \"go_default_library\",\n srcs = [\n "
},
{
"path": "pkg/apis/v1alpha1/doc.go",
"chars": 767,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/apis/v1alpha1/serviceexport.go",
"chars": 11284,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/apis/v1alpha1/serviceimport.go",
"chars": 9070,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/apis/v1alpha1/well_known_labels.go",
"chars": 946,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/apis/v1alpha1/zz_generated.deepcopy.go",
"chars": 8946,
"preview": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version "
},
{
"path": "pkg/apis/v1alpha1/zz_generated.register.go",
"chars": 2467,
"preview": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed un"
},
{
"path": "pkg/apis/v1beta1/BUILD",
"chars": 1017,
"preview": "load(\"@io_bazel_rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n name = \"go_default_library\",\n srcs = [\n "
},
{
"path": "pkg/apis/v1beta1/doc.go",
"chars": 764,
"preview": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/apis/v1beta1/serviceexport.go",
"chars": 10531,
"preview": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/apis/v1beta1/serviceimport.go",
"chars": 9100,
"preview": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/apis/v1beta1/well_known_labels.go",
"chars": 945,
"preview": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/apis/v1beta1/zz_generated.deepcopy.go",
"chars": 8945,
"preview": "//go:build !ignore_autogenerated\n\n/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version "
},
{
"path": "pkg/apis/v1beta1/zz_generated.register.go",
"chars": 2464,
"preview": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed un"
},
{
"path": "pkg/client/clientset/versioned/clientset.go",
"chars": 4552,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/fake/clientset_generated.go",
"chars": 3548,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/fake/doc.go",
"chars": 695,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/fake/register.go",
"chars": 1944,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/scheme/doc.go",
"chars": 711,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/scheme/register.go",
"chars": 2000,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go",
"chars": 3495,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1alpha1/doc.go",
"chars": 698,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/doc.go",
"chars": 688,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go",
"chars": 1366,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_serviceexport.go",
"chars": 1907,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_serviceimport.go",
"chars": 1907,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1alpha1/generated_expansion.go",
"chars": 717,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1alpha1/serviceexport.go",
"chars": 3077,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1alpha1/serviceimport.go",
"chars": 3077,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1beta1/apis_client.go",
"chars": 3475,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1beta1/doc.go",
"chars": 697,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1beta1/fake/doc.go",
"chars": 688,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1beta1/fake/fake_apis_client.go",
"chars": 1358,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1beta1/fake/fake_serviceexport.go",
"chars": 1885,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1beta1/fake/fake_serviceimport.go",
"chars": 1885,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1beta1/generated_expansion.go",
"chars": 716,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1beta1/serviceexport.go",
"chars": 3056,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/clientset/versioned/typed/apis/v1beta1/serviceimport.go",
"chars": 3056,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/informers/externalversions/apis/interface.go",
"chars": 1958,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/informers/externalversions/apis/v1alpha1/interface.go",
"chars": 1917,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/informers/externalversions/apis/v1alpha1/serviceexport.go",
"chars": 3758,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/informers/externalversions/apis/v1alpha1/serviceimport.go",
"chars": 3758,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/informers/externalversions/apis/v1beta1/interface.go",
"chars": 1916,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/informers/externalversions/apis/v1beta1/serviceexport.go",
"chars": 3746,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/informers/externalversions/apis/v1beta1/serviceimport.go",
"chars": 3746,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/informers/externalversions/factory.go",
"chars": 9213,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/informers/externalversions/generic.go",
"chars": 2713,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go",
"chars": 1423,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/listers/apis/v1alpha1/expansion_generated.go",
"chars": 1249,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/listers/apis/v1alpha1/serviceexport.go",
"chars": 2832,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/listers/apis/v1alpha1/serviceimport.go",
"chars": 2832,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/listers/apis/v1beta1/expansion_generated.go",
"chars": 1248,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/listers/apis/v1beta1/serviceexport.go",
"chars": 2821,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "pkg/client/listers/apis/v1beta1/serviceimport.go",
"chars": 2821,
"preview": "/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "scripts/.gitignore",
"chars": 18,
"preview": "*.kubeconfig\n*.tmp"
},
{
"path": "scripts/c1.yaml",
"chars": 379,
"preview": "kind: Cluster\napiVersion: \"kind.x-k8s.io/v1alpha4\"\nnetworking:\n podSubnet: \"10.10.0.0/16\"\n serviceSubnet: \"10.11.0.0/1"
},
{
"path": "scripts/c2.yaml",
"chars": 379,
"preview": "kind: Cluster\napiVersion: \"kind.x-k8s.io/v1alpha4\"\nnetworking:\n podSubnet: \"10.12.0.0/16\"\n serviceSubnet: \"10.13.0.0/1"
},
{
"path": "scripts/coredns-rbac.json",
"chars": 248,
"preview": "[\n {\n \"op\": \"add\",\n \"path\": \"/rules/-\",\n \"value\": {\n \"apiGroups\": [\n \"multicluster.x-k8s.io\"\n "
},
{
"path": "scripts/down.sh",
"chars": 809,
"preview": "#!/bin/bash\n\n# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
},
{
"path": "scripts/e2e-test.sh",
"chars": 1055,
"preview": "#!/bin/bash\n\n# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
},
{
"path": "scripts/up.sh",
"chars": 3706,
"preview": "#!/bin/bash\n\n# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
},
{
"path": "scripts/util.sh",
"chars": 800,
"preview": "#!/bin/bash\n\n# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\")"
},
{
"path": "tools/go.mod",
"chars": 1735,
"preview": "module github.com/kubernetes-sigs/mcs-api/tools\n\ngo 1.23.0\n\nrequire (\n\tgolang.org/x/lint v0.0.0-20210508222113-6edffad5e"
},
{
"path": "tools/go.sum",
"chars": 13573,
"preview": "github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/davecgh/go-spe"
},
{
"path": "tools/tools.go",
"chars": 1117,
"preview": "//go:build tools\n// +build tools\n\n/*\nCopyright 2020 The Kubernetes Authors.\n\nLicensed under the Apache License, Version "
}
]
About this extraction
This page contains the full source code of the kubernetes-sigs/mcs-api GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 138 files (476.5 KB), approximately 148.5k tokens, and a symbol index with 447 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.