Full Code of kubernetes-sigs/mcs-api for AI

master 4546fabf7251 cached
138 files
476.5 KB
148.5k tokens
447 symbols
1 requests
Download .txt
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
Download .txt
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
Download .txt
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.

Copied to clipboard!