├── demo
├── .gitignore
├── yaml
│ ├── svc.yaml
│ ├── serviceimport.yaml
│ ├── serviceimport-with-vip.yaml
│ ├── dep1.yaml
│ └── dep2.yaml
├── edit-meta
├── reset.sh
├── udemo.sh
└── demo.sh
├── scripts
├── .gitignore
├── coredns-rbac.json
├── c1.yaml
├── c2.yaml
├── util.sh
├── down.sh
├── e2e-test.sh
└── up.sh
├── code-of-conduct.md
├── OWNERS
├── .github
└── workflows
│ └── auto-label.yml
├── RELEASE.md
├── SECURITY_CONTACTS
├── hack
├── boilerplate.go.txt
├── boilerplate
│ ├── boilerplate.go.txt
│ ├── boilerplate.sh.txt
│ ├── boilerplate.py.txt
│ └── boilerplate.py
├── verify-codegen.sh
├── verify-boilerplate.sh
├── verify-crd-bump-revision.sh
├── update-k8s.sh
├── verify-gofmt.sh
├── verify-golint.sh
├── verify-all.sh
├── kube-env.sh
├── verify-crds.sh
└── update-codegen.sh
├── config
├── rbac
│ └── role.yaml
├── crd
│ ├── embed.go
│ └── multicluster.x-k8s.io_serviceexports.yaml
└── crd-base
│ ├── multicluster.x-k8s.io_serviceexports.yaml
│ └── multicluster.x-k8s.io_serviceimports.yaml
├── .gitignore
├── pkg
├── client
│ ├── clientset
│ │ └── versioned
│ │ │ ├── fake
│ │ │ ├── doc.go
│ │ │ ├── register.go
│ │ │ └── clientset_generated.go
│ │ │ ├── typed
│ │ │ └── apis
│ │ │ │ └── v1alpha1
│ │ │ │ ├── fake
│ │ │ │ ├── doc.go
│ │ │ │ ├── fake_apis_client.go
│ │ │ │ ├── fake_serviceexport.go
│ │ │ │ └── fake_serviceimport.go
│ │ │ │ ├── doc.go
│ │ │ │ ├── generated_expansion.go
│ │ │ │ ├── serviceexport.go
│ │ │ │ ├── serviceimport.go
│ │ │ │ └── apis_client.go
│ │ │ ├── scheme
│ │ │ ├── doc.go
│ │ │ └── register.go
│ │ │ └── clientset.go
│ ├── listers
│ │ └── apis
│ │ │ └── v1alpha1
│ │ │ ├── expansion_generated.go
│ │ │ ├── serviceexport.go
│ │ │ └── serviceimport.go
│ └── informers
│ │ └── externalversions
│ │ ├── internalinterfaces
│ │ └── factory_interfaces.go
│ │ ├── apis
│ │ ├── interface.go
│ │ └── v1alpha1
│ │ │ ├── interface.go
│ │ │ ├── serviceexport.go
│ │ │ └── serviceimport.go
│ │ └── generic.go
└── apis
│ └── v1alpha1
│ ├── doc.go
│ ├── well_known_labels.go
│ ├── BUILD
│ └── zz_generated.register.go
├── Dockerfile
├── conformance
├── conformance_suite_test.go
├── report_template.gohtml
├── framework.go
├── go.mod
├── connectivity.go
├── endpoint_slice.go
├── k8s_objects.go
└── clusterip_service_dns.go
├── SECURITY.md
├── tools
├── tools.go
└── go.mod
├── CONTRIBUTING.md
├── README.md
├── go.mod
├── controllers
├── cmd
│ └── servicecontroller
│ │ └── servicecontroller.go
├── endpointslice.go
├── common.go
├── service.go
├── controllers_suite_test.go
├── go.mod
├── endpointslice_test.go
├── serviceimport.go
└── serviceimport_test.go
├── e2e
├── go.mod
├── e2e_suite_test.go
└── localserviceimpact_test.go
└── Makefile
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | *.kubeconfig
2 | *.tmp
--------------------------------------------------------------------------------
/scripts/.gitignore:
--------------------------------------------------------------------------------
1 | *.kubeconfig
2 | *.tmp
--------------------------------------------------------------------------------
/code-of-conduct.md:
--------------------------------------------------------------------------------
1 | # Kubernetes Community Code of Conduct
2 |
3 | Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)
4 |
--------------------------------------------------------------------------------
/demo/yaml/svc.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: serve
5 | namespace: demo
6 | spec:
7 | ports:
8 | - port: 80
9 | targetPort: 8080
10 | selector:
11 | app: serve
12 |
--------------------------------------------------------------------------------
/demo/yaml/serviceimport.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: multicluster.x-k8s.io/v1alpha1
2 | kind: ServiceImport
3 | metadata:
4 | name: serve
5 | namespace: demo
6 | spec:
7 | type: ClusterSetIP
8 | ports:
9 | - port: 80
10 | protocol: TCP
11 |
--------------------------------------------------------------------------------
/demo/yaml/serviceimport-with-vip.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: multicluster.x-k8s.io/v1alpha1
2 | kind: ServiceImport
3 | metadata:
4 | name: serve-with-vip
5 | namespace: demo
6 | spec:
7 | type: ClusterSetIP
8 | ips:
9 | - 1.2.3.4
10 | ports:
11 | - port: 80
12 | protocol: TCP
13 |
--------------------------------------------------------------------------------
/scripts/coredns-rbac.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "op": "add",
4 | "path": "/rules/-",
5 | "value": {
6 | "apiGroups": [
7 | "multicluster.x-k8s.io"
8 | ],
9 | "resources": [
10 | "serviceimports"
11 | ],
12 | "verbs": [
13 | "list",
14 | "watch"
15 | ]
16 | }
17 | }
18 | ]
19 |
--------------------------------------------------------------------------------
/OWNERS:
--------------------------------------------------------------------------------
1 | # See the OWNERS docs at https://go.k8s.io/owners
2 |
3 | reviewers:
4 | - jeremyot
5 | - lauralorenz
6 | - skitt
7 | - MrFreezeex
8 | - munnerz
9 | - RainbowMango
10 | - ryanzhang-oss
11 | # Pending org membership
12 | # - corentone
13 | # - jnpacker
14 |
15 | approvers:
16 | - JeremyOT
17 | - lauralorenz
18 | - skitt
19 |
20 | emeritus_approvers:
21 | - pmorie
22 |
--------------------------------------------------------------------------------
/scripts/c1.yaml:
--------------------------------------------------------------------------------
1 | kind: Cluster
2 | apiVersion: "kind.x-k8s.io/v1alpha4"
3 | networking:
4 | podSubnet: "10.10.0.0/16"
5 | serviceSubnet: "10.11.0.0/16"
6 | nodes:
7 | - role: control-plane
8 | kubeadmConfigPatches:
9 | - |
10 | kind: ClusterConfiguration
11 | dns:
12 | # TODO: Remove this after Kubernetes 1.35.
13 | # Reference: https://github.com/kubernetes/kubernetes/pull/132288
14 | imageTag: v1.12.2
15 |
--------------------------------------------------------------------------------
/scripts/c2.yaml:
--------------------------------------------------------------------------------
1 | kind: Cluster
2 | apiVersion: "kind.x-k8s.io/v1alpha4"
3 | networking:
4 | podSubnet: "10.12.0.0/16"
5 | serviceSubnet: "10.13.0.0/16"
6 | nodes:
7 | - role: control-plane
8 | kubeadmConfigPatches:
9 | - |
10 | kind: ClusterConfiguration
11 | dns:
12 | # TODO: Remove this after Kubernetes 1.35.
13 | # Reference: https://github.com/kubernetes/kubernetes/pull/132288
14 | imageTag: v1.12.2
15 |
--------------------------------------------------------------------------------
/.github/workflows/auto-label.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Label issues
3 | on:
4 | issues:
5 | types:
6 | - opened
7 | - reopened
8 | jobs:
9 | label_issues:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | issues: write
13 | steps:
14 | - run: gh issue edit "$NUMBER" --add-label "$LABELS"
15 | env:
16 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
17 | GH_REPO: ${{ github.repository }}
18 | NUMBER: ${{ github.event.issue.number }}
19 | LABELS: sig/multicluster
20 |
--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------
1 | # Release Process
2 |
3 | The Kubernetes Template Project is released on an as-needed basis. The process is as follows:
4 |
5 | 1. An issue is proposing a new release with a changelog since the last release
6 | 1. All [OWNERS](OWNERS) must LGTM this release
7 | 1. An OWNER runs `git tag -s $VERSION` and inserts the changelog and pushes the tag with `git push $VERSION`
8 | 1. The release issue is closed
9 | 1. An announcement email is sent to `kubernetes-dev@googlegroups.com` with the subject `[ANNOUNCE] kubernetes-template-project $VERSION is released`
10 |
--------------------------------------------------------------------------------
/SECURITY_CONTACTS:
--------------------------------------------------------------------------------
1 | # Defined below are the security contacts for this repo.
2 | #
3 | # They are the contact point for the Product Security Committee to reach out
4 | # to for triaging and handling of incoming issues.
5 | #
6 | # The below names agree to abide by the
7 | # [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy)
8 | # and will be removed and replaced if they violate that agreement.
9 | #
10 | # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
11 | # INSTRUCTIONS AT https://kubernetes.io/security/
12 |
13 | pmorie
14 | JeremyOT
15 |
--------------------------------------------------------------------------------
/demo/edit-meta:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | import argparse
5 | import yaml
6 |
7 | if __name__ == '__main__':
8 | parser = argparse.ArgumentParser()
9 | parser.add_argument('-m', '--metadata', dest='metadata', help="Overwrite the resource's metadata with this value, may be any valid YAML")
10 | args = parser.parse_args()
11 |
12 | data = yaml.load(sys.stdin, Loader=yaml.Loader)
13 | if args.metadata:
14 | value = yaml.load(args.metadata, Loader=yaml.Loader)
15 | data['metadata'] = value
16 |
17 | print(yaml.dump(data, indent=2, default_flow_style=False, Dumper=yaml.Dumper))
18 |
--------------------------------------------------------------------------------
/hack/boilerplate.go.txt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
--------------------------------------------------------------------------------
/hack/boilerplate/boilerplate.go.txt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright YEAR The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
--------------------------------------------------------------------------------
/hack/boilerplate/boilerplate.sh.txt:
--------------------------------------------------------------------------------
1 | # Copyright YEAR The Kubernetes Authors.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 |
--------------------------------------------------------------------------------
/config/rbac/role.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: mcs-derived-service-manager
6 | rules:
7 | - apiGroups:
8 | - ""
9 | resources:
10 | - services
11 | - services/status
12 | verbs:
13 | - create
14 | - get
15 | - list
16 | - patch
17 | - update
18 | - watch
19 | - apiGroups:
20 | - discovery.k8s.io
21 | resources:
22 | - endpointslices
23 | verbs:
24 | - get
25 | - list
26 | - patch
27 | - update
28 | - watch
29 | - apiGroups:
30 | - multicluster.x-k8s.io
31 | resources:
32 | - serviceimports
33 | verbs:
34 | - get
35 | - list
36 | - patch
37 | - update
38 | - watch
39 |
--------------------------------------------------------------------------------
/hack/boilerplate/boilerplate.py.txt:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright YEAR The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | .\#*
3 | ._*
4 | \#*\#
5 | /_artifacts/
6 | /bazel-*
7 | bin
8 | .classpath
9 | /cluster
10 | /.config/gcloud*/
11 | *.dll
12 | /doc_tmp/
13 | !\.drone\.sec
14 | .DS_Store
15 | *.dylib
16 | .envrc
17 | *.exe
18 | *.exe~
19 | /_gopath/
20 | /.gsutil/
21 | /hack/.test-cmd-auth
22 | **/.hg*
23 | .idea/
24 | *.iml
25 | /junit*.xml
26 | /.make/
27 | .netrwhist
28 | network_closure.sh
29 | *.out
30 | /_output*/
31 | /output*/
32 | .project
33 | *.pyc
34 | [._]*.s[a-w][a-z]
35 | [._]s[a-w][a-z]
36 | Session.vim
37 | .settings/**
38 | *.so
39 | *.swo
40 | *.swp
41 | .tags*
42 | *.test
43 | /third_party/pkg
44 | .*.timestamp
45 | /_tmp/
46 | *.un~
47 | .vagrant
48 | !vendor/**/zz_generated.*
49 | .vscode
50 | /www/test_out
51 | report.html
52 | .*.timestamp
53 |
--------------------------------------------------------------------------------
/demo/yaml/dep1.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: serve
5 | namespace: demo
6 | spec:
7 | replicas: 1
8 | selector:
9 | matchLabels:
10 | app: serve
11 | template:
12 | metadata:
13 | labels:
14 | app: serve
15 | spec:
16 | containers:
17 | - name: serve
18 | image: jeremyot/serve:0a40de8
19 | args:
20 | - "--message='hello from cluster 1 (Node: {{env \"NODE_NAME\"}} Pod: {{env \"POD_NAME\"}} Address: {{addr}})'"
21 | env:
22 | - name: NODE_NAME
23 | valueFrom:
24 | fieldRef:
25 | fieldPath: spec.nodeName
26 | - name: POD_NAME
27 | valueFrom:
28 | fieldRef:
29 | fieldPath: metadata.name
--------------------------------------------------------------------------------
/demo/yaml/dep2.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: serve
5 | namespace: demo
6 | spec:
7 | replicas: 1
8 | selector:
9 | matchLabels:
10 | app: serve
11 | template:
12 | metadata:
13 | labels:
14 | app: serve
15 | spec:
16 | containers:
17 | - name: serve
18 | image: jeremyot/serve:0a40de8
19 | args:
20 | - "--message='hello from cluster 2 (Node: {{env \"NODE_NAME\"}} Pod: {{env \"POD_NAME\"}} Address: {{addr}})'"
21 | env:
22 | - name: NODE_NAME
23 | valueFrom:
24 | fieldRef:
25 | fieldPath: spec.nodeName
26 | - name: POD_NAME
27 | valueFrom:
28 | fieldRef:
29 | fieldPath: metadata.name
--------------------------------------------------------------------------------
/pkg/client/clientset/versioned/fake/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | // This package has the automatically generated fake clientset.
20 | package fake
21 |
--------------------------------------------------------------------------------
/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | // Package fake has the automatically generated clients.
20 | package fake
21 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Build the manager binary
2 | FROM golang:1.23 as builder
3 |
4 | WORKDIR /workspace
5 | # Copy the Go Modules manifests
6 | COPY go.mod go.mod
7 | COPY go.sum go.sum
8 | RUN go mod download
9 |
10 | # Copy the go source
11 | COPY controllers/ controllers/
12 | RUN go -C controllers mod download
13 | COPY pkg/ pkg/
14 |
15 | # Build
16 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go -C controllers build -a -o /workspace/controller cmd/servicecontroller/servicecontroller.go
17 |
18 | # Use distroless as minimal base image to package the manager binary
19 | # Refer to https://github.com/GoogleContainerTools/distroless for more details
20 | FROM gcr.io/distroless/static:nonroot
21 | WORKDIR /
22 | COPY --from=builder /workspace/controller .
23 | USER nonroot:nonroot
24 |
25 | ENTRYPOINT ["/controller"]
26 |
--------------------------------------------------------------------------------
/pkg/client/clientset/versioned/scheme/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | // This package contains the scheme of the automatically generated clientset.
20 | package scheme
21 |
--------------------------------------------------------------------------------
/pkg/client/clientset/versioned/typed/apis/v1alpha1/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | // This package has the automatically generated typed clients.
20 | package v1alpha1
21 |
--------------------------------------------------------------------------------
/pkg/apis/v1alpha1/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Package v1alpha1 contains API schema definitions for the Multi-Cluster
18 | // Services v1alpha1 API group.
19 | // +kubebuilder:object:generate=true
20 | // +groupName=multicluster.x-k8s.io
21 | package v1alpha1
22 |
--------------------------------------------------------------------------------
/hack/verify-codegen.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2020 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -o errexit
18 | set -o nounset
19 | set -o pipefail
20 |
21 | SCRIPT_ROOT=$(dirname "${BASH_SOURCE}")/..
22 |
23 | cd $SCRIPT_ROOT
24 | VERIFY_CODEGEN=true $SCRIPT_ROOT/hack/update-codegen.sh
25 |
--------------------------------------------------------------------------------
/pkg/client/clientset/versioned/typed/apis/v1alpha1/generated_expansion.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | type ServiceExportExpansion interface{}
22 |
23 | type ServiceImportExpansion interface{}
24 |
--------------------------------------------------------------------------------
/demo/reset.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2020 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | cd $(dirname ${BASH_SOURCE})
18 | . ./udemo.sh
19 |
20 | c1=c1
21 | c2=c2
22 | k1="kubectl --kubeconfig ${c1}.kubeconfig"
23 | k2="kubectl --kubeconfig ${c2}.kubeconfig"
24 |
25 | ${k1} delete ns demo
26 | ${k2} delete ns demo
--------------------------------------------------------------------------------
/scripts/util.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2020 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | function waitfor() {
18 | for i in {1..30}; do
19 | if [ ! -z "$(${@})" ]; then
20 | break
21 | fi
22 | sleep 1
23 | done
24 | if [ -z "$(${@})" ]; then
25 | echo "No results for '${1}' after 30 attempts"
26 | fi
27 | }
--------------------------------------------------------------------------------
/conformance/conformance_suite_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package conformance_test
18 |
19 | import (
20 | "testing"
21 |
22 | . "github.com/onsi/ginkgo/v2"
23 | . "github.com/onsi/gomega"
24 | )
25 |
26 | func TestConformance(t *testing.T) {
27 | RegisterFailHandler(Fail)
28 | RunSpecs(t, "Conformance Suite")
29 | }
30 |
--------------------------------------------------------------------------------
/scripts/down.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2020 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -e
18 |
19 | cd $(dirname ${BASH_SOURCE})
20 |
21 | kind() {
22 | go -C ../controllers run sigs.k8s.io/kind "$@"
23 | }
24 |
25 | c1=${CLUSTER1:-c1}
26 | c2=${CLUSTER2:-c2}
27 |
28 | kind delete cluster --name "${c1}"
29 | kind delete cluster --name "${c2}"
30 |
--------------------------------------------------------------------------------
/pkg/apis/v1alpha1/well_known_labels.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package v1alpha1
18 |
19 | const (
20 | // LabelServiceName is used to indicate the name of multi-cluster service
21 | // that an EndpointSlice belongs to.
22 | LabelServiceName = "multicluster.kubernetes.io/service-name"
23 |
24 | // LabelSourceCluster is used to indicate the name of the cluster in which an exported resource exists.
25 | LabelSourceCluster = "multicluster.kubernetes.io/source-cluster"
26 | )
27 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Security Announcements
4 |
5 | Join the [kubernetes-security-announce] group for security and vulnerability announcements.
6 |
7 | You can also subscribe to an RSS feed of the above using [this link][kubernetes-security-announce-rss].
8 |
9 | ## Reporting a Vulnerability
10 |
11 | Instructions for reporting a vulnerability can be found on the
12 | [Kubernetes Security and Disclosure Information] page.
13 |
14 | ## Supported Versions
15 |
16 | Information about supported Kubernetes versions can be found on the
17 | [Kubernetes version and version skew support policy] page on the Kubernetes website.
18 |
19 | [kubernetes-security-announce]: https://groups.google.com/forum/#!forum/kubernetes-security-announce
20 | [kubernetes-security-announce-rss]: https://groups.google.com/forum/feed/kubernetes-security-announce/msgs/rss_v2_0.xml?num=50
21 | [Kubernetes version and version skew support policy]: https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-versions
22 | [Kubernetes Security and Disclosure Information]: https://kubernetes.io/docs/reference/issues-security/security/#report-a-vulnerability
23 |
--------------------------------------------------------------------------------
/conformance/report_template.gohtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MCS API conformance report
6 |
14 |
15 |
16 | MCS Conformance Report
17 |
18 | {{if .SuiteFailure }}
19 | {{.SuiteFailure}}
20 | {{end}}
21 |
22 | {{range .Groups}}
23 | {{.Name}}
24 |
25 |
26 |
27 | | Conformant |
28 | Description |
29 |
30 |
31 | {{range .Tests}}
32 |
33 | {{ if .Failed }}
34 | | Unknown{{.Message}} |
35 | {{ else if .Conformant }}
36 | Yes{{.Message}} |
37 | {{ else }}
38 | No{{.Message}} |
39 | {{end}}
40 | {{.Desc}} |
41 |
42 | {{end}}
43 |
44 | {{end}}
45 |
46 |
--------------------------------------------------------------------------------
/hack/verify-boilerplate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2014 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -o errexit
18 | set -o nounset
19 | set -o pipefail
20 |
21 | SCRIPT_ROOT=$(dirname "${BASH_SOURCE}")/..
22 |
23 | boilerDir="${SCRIPT_ROOT}/hack/boilerplate"
24 | boiler="${boilerDir}/boilerplate.py"
25 |
26 | files_need_boilerplate=($(${boiler} "$@"))
27 |
28 | # Run boilerplate check
29 | if [[ ${#files_need_boilerplate[@]} -gt 0 ]]; then
30 | for file in "${files_need_boilerplate[@]}"; do
31 | echo "Boilerplate header is wrong for: ${file}"
32 | done
33 |
34 | exit 1
35 | fi
36 |
--------------------------------------------------------------------------------
/pkg/apis/v1alpha1/BUILD:
--------------------------------------------------------------------------------
1 | load("@io_bazel_rules_go//go:def.bzl", "go_library")
2 |
3 | go_library(
4 | name = "go_default_library",
5 | srcs = [
6 | "doc.go",
7 | "register.go",
8 | "types.go",
9 | "well_known_labels.go",
10 | "zz_generated.deepcopy.go",
11 | ],
12 | importmap = "k8s.io/kubernetes/vendor/k8s.io/mcs-api/pkg/apis/v1alpha1",
13 | importpath = "k8s.io/mcs-api/pkg/apis/v1alpha1",
14 | visibility = ["//visibility:public"],
15 | deps = [
16 | "//staging/src/k8s.io/api/core/v1:go_default_library",
17 | "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
18 | "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
19 | "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
20 | ],
21 | )
22 |
23 | filegroup(
24 | name = "package-srcs",
25 | srcs = glob(["**"]),
26 | tags = ["automanaged"],
27 | visibility = ["//visibility:private"],
28 | )
29 |
30 | filegroup(
31 | name = "all-srcs",
32 | srcs = [":package-srcs"],
33 | tags = ["automanaged"],
34 | visibility = ["//visibility:public"],
35 | )
36 |
--------------------------------------------------------------------------------
/scripts/e2e-test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2020 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | cd $(dirname ${BASH_SOURCE})
18 |
19 | set -e
20 |
21 | export KUBECONFIG1=$(mktemp --suffix=".kubeconfig")
22 | export KUBECONFIG2=$(mktemp --suffix=".kubeconfig")
23 |
24 | function cleanup() {
25 | if [ -z "${NO_TEAR_DOWN}" ]; then
26 | ./down.sh
27 | rm ${KUBECONFIG1}
28 | rm ${KUBECONFIG2}
29 | else
30 | echo "KUBECONFIG1=${KUBECONFIG1}"
31 | echo "KUBECONFIG2=${KUBECONFIG2}"
32 | fi
33 | }
34 |
35 | trap cleanup EXIT
36 |
37 | ./up.sh
38 | go -C ../e2e run github.com/onsi/ginkgo/v2/ginkgo .
39 |
--------------------------------------------------------------------------------
/hack/verify-crd-bump-revision.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2025 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | # Check against master if PULL_BASE_SHA is not defined by prow
18 | BASE_REF="${PULL_BASE_SHA:-master}"
19 |
20 | crd_changed="$(git diff --name-only "${BASE_REF}" | grep -c "^config/crd/.*\.yaml$")"
21 | version_label_changed="$(git diff -U0 "${BASE_REF}" -- "config/crd-base/" | grep -c "multicluster.x-k8s.io/crd-schema-revision")"
22 |
23 | if [ "${crd_changed}" -gt 0 ] && [ "${version_label_changed}" -ne 4 ]; then
24 | echo "❌ CRDs were modified, but the CRD revision labels were not changed in 'config/crd-base/'. Please bump the CRDs revision."
25 | exit 1
26 | fi
27 |
--------------------------------------------------------------------------------
/tools/tools.go:
--------------------------------------------------------------------------------
1 | //go:build tools
2 | // +build tools
3 |
4 | /*
5 | Copyright 2020 The Kubernetes Authors.
6 |
7 | Licensed under the Apache License, Version 2.0 (the "License");
8 | you may not use this file except in compliance with the License.
9 | You may obtain a copy of the License at
10 |
11 | http://www.apache.org/licenses/LICENSE-2.0
12 |
13 | Unless required by applicable law or agreed to in writing, software
14 | distributed under the License is distributed on an "AS IS" BASIS,
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | See the License for the specific language governing permissions and
17 | limitations under the License.
18 | */
19 |
20 | // This package contains import references to packages required only for the
21 | // build process.
22 | // https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module
23 | package tools
24 |
25 | import (
26 | _ "golang.org/x/lint/golint"
27 | _ "k8s.io/code-generator/cmd/client-gen"
28 | _ "k8s.io/code-generator/cmd/deepcopy-gen"
29 | _ "k8s.io/code-generator/cmd/informer-gen"
30 | _ "k8s.io/code-generator/cmd/lister-gen"
31 | _ "k8s.io/code-generator/cmd/register-gen"
32 | _ "sigs.k8s.io/controller-tools/cmd/controller-gen"
33 | )
34 |
--------------------------------------------------------------------------------
/hack/update-k8s.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Copyright 2025 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | # This script updates mcs-api dependencies to either the latest
18 | # k8s.io dependencies, or to the version given as argument
19 | # (with the @, e.g. @v0.32.5).
20 |
21 | set -o errexit
22 | set -o nounset
23 | set -o pipefail
24 |
25 | SCRIPT_ROOT=$(dirname "${BASH_SOURCE}")/..
26 |
27 | for dir in "$SCRIPT_ROOT"{,/tools}; do
28 | awk '/[^.]k8s.io[/][^ ]+ v[.0-9]+$/ { print $1 "'"$1"'" }' "$dir/go.mod" |
29 | xargs -r -n 1 go -C "$dir" get
30 | done
31 |
32 | for mod in "$SCRIPT_ROOT"/go.mod "$SCRIPT_ROOT"/*/go.mod; do
33 | go -C "${mod%/*}" mod tidy
34 | done
35 |
36 | make generate
37 |
--------------------------------------------------------------------------------
/hack/verify-gofmt.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2014 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | # GoFmt apparently is changing @ head...
18 |
19 | set -o errexit
20 | set -o nounset
21 | set -o pipefail
22 |
23 | KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
24 |
25 | cd "${KUBE_ROOT}"
26 |
27 | find_files() {
28 | find . -not \( \
29 | \( \
30 | -wholename './.git' \
31 | -o -wholename '*/vendor/*' \
32 | \) -prune \
33 | \) -name '*.go'
34 | }
35 |
36 | GOFMT="gofmt -s"
37 | bad_files=$(find_files | xargs $GOFMT -l)
38 | if [[ -n "${bad_files}" ]]; then
39 | echo "!!! '$GOFMT' needs to be run on the following files: "
40 | echo "${bad_files}"
41 | exit 1
42 | fi
43 |
--------------------------------------------------------------------------------
/config/crd/embed.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2025 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package crd
18 |
19 | import _ "embed" // import embed to be able to use go:embed
20 |
21 | var (
22 | // ServiceExportCRD is the embedded YAML for the ServiceExport CRD
23 | //go:embed multicluster.x-k8s.io_serviceexports.yaml
24 | ServiceExportCRD []byte
25 | // ServiceImportCRD is the embedded YAML for the ServiceImport CRD
26 | //go:embed multicluster.x-k8s.io_serviceimports.yaml
27 | ServiceImportCRD []byte
28 | )
29 |
30 | const (
31 | // ReleaseVersionLabel is the label which indicate the release version
32 | ReleaseVersionLabel = "multicluster.x-k8s.io/release-version"
33 | // CustomResourceDefinitionSchemaRevisionLabel is the label which holds the CRD schema revision
34 | CustomResourceDefinitionSchemaRevisionLabel = "multicluster.x-k8s.io/crd-schema-revision"
35 | )
36 |
--------------------------------------------------------------------------------
/pkg/client/listers/apis/v1alpha1/expansion_generated.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by lister-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | // ServiceExportListerExpansion allows custom methods to be added to
22 | // ServiceExportLister.
23 | type ServiceExportListerExpansion interface{}
24 |
25 | // ServiceExportNamespaceListerExpansion allows custom methods to be added to
26 | // ServiceExportNamespaceLister.
27 | type ServiceExportNamespaceListerExpansion interface{}
28 |
29 | // ServiceImportListerExpansion allows custom methods to be added to
30 | // ServiceImportLister.
31 | type ServiceImportListerExpansion interface{}
32 |
33 | // ServiceImportNamespaceListerExpansion allows custom methods to be added to
34 | // ServiceImportNamespaceLister.
35 | type ServiceImportNamespaceListerExpansion interface{}
36 |
--------------------------------------------------------------------------------
/hack/verify-golint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2014 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -o errexit
18 | set -o nounset
19 | set -o pipefail
20 |
21 | KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
22 |
23 | cd "${KUBE_ROOT}"
24 |
25 | PACKAGES=($(go list ./... | sed sXsigs.k8s.io/mcs-apiX..X))
26 | bad_files=()
27 | for package in "${PACKAGES[@]}"; do
28 | out=$(go -C tools run golang.org/x/lint/golint -min_confidence=0.9 "${package}" 2>&1 |
29 | sed 'sX^../XX;/should not use dot imports/d;/exported const OptionalLabel/d;/^go: downloading/d' ||:)
30 | if [[ -n "${out}" ]]; then
31 | bad_files+=("${out}")
32 | fi
33 | done
34 | if [[ "${#bad_files[@]}" -ne 0 ]]; then
35 | echo "!!! golint problems: "
36 | for err in "${bad_files[@]}"; do
37 | echo "$err"
38 | done
39 | exit 1
40 | fi
41 |
42 | # ex: ts=2 sw=2 et filetype=sh
43 |
--------------------------------------------------------------------------------
/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | package fake
20 |
21 | import (
22 | rest "k8s.io/client-go/rest"
23 | testing "k8s.io/client-go/testing"
24 | v1alpha1 "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned/typed/apis/v1alpha1"
25 | )
26 |
27 | type FakeMulticlusterV1alpha1 struct {
28 | *testing.Fake
29 | }
30 |
31 | func (c *FakeMulticlusterV1alpha1) ServiceExports(namespace string) v1alpha1.ServiceExportInterface {
32 | return newFakeServiceExports(c, namespace)
33 | }
34 |
35 | func (c *FakeMulticlusterV1alpha1) ServiceImports(namespace string) v1alpha1.ServiceImportInterface {
36 | return newFakeServiceImports(c, namespace)
37 | }
38 |
39 | // RESTClient returns a RESTClient that is used to communicate
40 | // with API server by this client implementation.
41 | func (c *FakeMulticlusterV1alpha1) RESTClient() rest.Interface {
42 | var ret *rest.RESTClient
43 | return ret
44 | }
45 |
--------------------------------------------------------------------------------
/config/crd-base/multicluster.x-k8s.io_serviceexports.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2020 The Kubernetes Authors.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | apiVersion: apiextensions.k8s.io/v1
15 | kind: CustomResourceDefinition
16 | metadata:
17 | name: serviceexports.multicluster.x-k8s.io
18 | labels:
19 | multicluster.x-k8s.io/release-version: "v0.3.0"
20 | # The revision is updated on each CRD change and reset back to 0 on every new version.
21 | # It can be used together with the version label when installing those CRDs
22 | # and prevent any downgrades.
23 | multicluster.x-k8s.io/crd-schema-revision: "1"
24 | spec:
25 | group: multicluster.x-k8s.io
26 | scope: Namespaced
27 | names:
28 | plural: serviceexports
29 | singular: serviceexport
30 | kind: ServiceExport
31 | shortNames:
32 | - svcex
33 | - svcexport
34 | versions:
35 | - name: v1alpha1
36 | served: true
37 | storage: true
38 | subresources:
39 | status: {}
40 | additionalPrinterColumns:
41 | - name: Age
42 | type: date
43 | jsonPath: .metadata.creationTimestamp
44 |
--------------------------------------------------------------------------------
/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by informer-gen. DO NOT EDIT.
18 |
19 | package internalinterfaces
20 |
21 | import (
22 | time "time"
23 |
24 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25 | runtime "k8s.io/apimachinery/pkg/runtime"
26 | cache "k8s.io/client-go/tools/cache"
27 | versioned "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned"
28 | )
29 |
30 | // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer.
31 | type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer
32 |
33 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle
34 | type SharedInformerFactory interface {
35 | Start(stopCh <-chan struct{})
36 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer
37 | }
38 |
39 | // TweakListOptionsFunc is a function that transforms a v1.ListOptions.
40 | type TweakListOptionsFunc func(*v1.ListOptions)
41 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | 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:
4 |
5 | _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._
6 |
7 | ## Getting Started
8 |
9 | We have full documentation on how to get started contributing here:
10 |
11 |
14 |
15 | - [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
16 | - [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)
17 | - [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet) - Common resources for existing developers
18 |
19 | ## Mentorship
20 |
21 | - [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
22 |
23 | ## Contact Information
24 |
25 | - [Slack](https://kubernetes.slack.com/messages/sig-service-catalog)
26 | - [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-service-catalog)
27 |
--------------------------------------------------------------------------------
/pkg/client/informers/externalversions/apis/interface.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by informer-gen. DO NOT EDIT.
18 |
19 | package apis
20 |
21 | import (
22 | v1alpha1 "sigs.k8s.io/mcs-api/pkg/client/informers/externalversions/apis/v1alpha1"
23 | internalinterfaces "sigs.k8s.io/mcs-api/pkg/client/informers/externalversions/internalinterfaces"
24 | )
25 |
26 | // Interface provides access to each of this group's versions.
27 | type Interface interface {
28 | // V1alpha1 provides access to shared informers for resources in V1alpha1.
29 | V1alpha1() v1alpha1.Interface
30 | }
31 |
32 | type group struct {
33 | factory internalinterfaces.SharedInformerFactory
34 | namespace string
35 | tweakListOptions internalinterfaces.TweakListOptionsFunc
36 | }
37 |
38 | // New returns a new Interface.
39 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
40 | return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
41 | }
42 |
43 | // V1alpha1 returns a new v1alpha1.Interface.
44 | func (g *group) V1alpha1() v1alpha1.Interface {
45 | return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)
46 | }
47 |
--------------------------------------------------------------------------------
/config/crd-base/multicluster.x-k8s.io_serviceimports.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2020 The Kubernetes Authors.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | apiVersion: apiextensions.k8s.io/v1
15 | kind: CustomResourceDefinition
16 | metadata:
17 | name: serviceimports.multicluster.x-k8s.io
18 | labels:
19 | multicluster.x-k8s.io/release-version: "v0.3.0"
20 | # The revision is updated on each CRD change and reset back to 0 on every new version.
21 | # It can be used together with the version label when installing those CRDs
22 | # and prevent any downgrades.
23 | multicluster.x-k8s.io/crd-schema-revision: "1"
24 | spec:
25 | group: multicluster.x-k8s.io
26 | scope: Namespaced
27 | names:
28 | plural: serviceimports
29 | singular: serviceimport
30 | kind: ServiceImport
31 | shortNames:
32 | - svcim
33 | - svcimport
34 | versions:
35 | - name: v1alpha1
36 | served: true
37 | storage: true
38 | subresources:
39 | status: {}
40 | additionalPrinterColumns:
41 | - name: Type
42 | type: string
43 | description: The type of this ServiceImport
44 | jsonPath: .spec.type
45 | - name: IP
46 | type: string
47 | description: The VIP for this ServiceImport
48 | jsonPath: .spec.ips
49 | - name: Age
50 | type: date
51 | jsonPath: .metadata.creationTimestamp
52 |
--------------------------------------------------------------------------------
/tools/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/kubernetes-sigs/mcs-api/tools
2 |
3 | go 1.23.0
4 |
5 | require (
6 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
7 | k8s.io/code-generator v0.32.5
8 | sigs.k8s.io/controller-tools v0.17.3
9 | )
10 |
11 | require (
12 | github.com/fatih/color v1.18.0 // indirect
13 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect
14 | github.com/go-logr/logr v1.4.2 // indirect
15 | github.com/gobuffalo/flect v1.0.3 // indirect
16 | github.com/gogo/protobuf v1.3.2 // indirect
17 | github.com/google/gofuzz v1.2.0 // indirect
18 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
19 | github.com/json-iterator/go v1.1.12 // indirect
20 | github.com/mattn/go-colorable v0.1.13 // indirect
21 | github.com/mattn/go-isatty v0.0.20 // indirect
22 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
23 | github.com/modern-go/reflect2 v1.0.2 // indirect
24 | github.com/spf13/cobra v1.9.1 // indirect
25 | github.com/spf13/pflag v1.0.6 // indirect
26 | github.com/x448/float16 v0.8.4 // indirect
27 | golang.org/x/mod v0.23.0 // indirect
28 | golang.org/x/net v0.35.0 // indirect
29 | golang.org/x/sync v0.11.0 // indirect
30 | golang.org/x/sys v0.30.0 // indirect
31 | golang.org/x/text v0.22.0 // indirect
32 | golang.org/x/tools v0.30.0 // indirect
33 | gopkg.in/inf.v0 v0.9.1 // indirect
34 | gopkg.in/yaml.v2 v2.4.0 // indirect
35 | gopkg.in/yaml.v3 v3.0.1 // indirect
36 | k8s.io/api v0.32.2 // indirect
37 | k8s.io/apiextensions-apiserver v0.32.2 // indirect
38 | k8s.io/apimachinery v0.32.5 // indirect
39 | k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 // indirect
40 | k8s.io/klog/v2 v2.130.1 // indirect
41 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
42 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
43 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
44 | sigs.k8s.io/yaml v1.4.0 // indirect
45 | )
46 |
--------------------------------------------------------------------------------
/conformance/framework.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2023 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package conformance
18 |
19 | import (
20 | "bytes"
21 | "context"
22 | "time"
23 |
24 | v1 "k8s.io/api/core/v1"
25 | "k8s.io/client-go/kubernetes"
26 | "k8s.io/client-go/kubernetes/scheme"
27 | rest "k8s.io/client-go/rest"
28 | "k8s.io/client-go/tools/remotecommand"
29 | )
30 |
31 | func execCmd(k8s kubernetes.Interface, config *rest.Config, podName string, podNamespace string, command []string) ([]byte, []byte, error) {
32 | req := k8s.CoreV1().RESTClient().Post().Resource("pods").Name(podName).Namespace(podNamespace).SubResource("exec")
33 | req.VersionedParams(&v1.PodExecOptions{
34 | Command: command,
35 | Stdin: false,
36 | Stdout: true,
37 | Stderr: true,
38 | TTY: true,
39 | }, scheme.ParameterCodec)
40 |
41 | exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
42 | if err != nil {
43 | return []byte{}, []byte{}, err
44 | }
45 |
46 | var stdout, stderr bytes.Buffer
47 |
48 | ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
49 | defer cancel()
50 |
51 | err = exec.StreamWithContext(ctx, remotecommand.StreamOptions{
52 | Stdin: nil,
53 | Stdout: &stdout,
54 | Stderr: &stderr,
55 | })
56 | if err != nil {
57 | return []byte{}, []byte{}, err
58 | }
59 |
60 | return stdout.Bytes(), stderr.Bytes(), nil
61 | }
62 |
--------------------------------------------------------------------------------
/hack/verify-all.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2014 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -o errexit
18 | set -o nounset
19 | set -o pipefail
20 |
21 | SCRIPT_ROOT=$(dirname "${BASH_SOURCE}")/..
22 | source "${SCRIPT_ROOT}/hack/kube-env.sh"
23 |
24 | SILENT=true
25 |
26 | function is-excluded {
27 | for e in $EXCLUDE; do
28 | if [[ $1 -ef ${BASH_SOURCE} ]]; then
29 | return
30 | fi
31 | if [[ $1 -ef "$SCRIPT_ROOT/hack/$e" ]]; then
32 | return
33 | fi
34 | done
35 | return 1
36 | }
37 |
38 | while getopts ":v" opt; do
39 | case $opt in
40 | v)
41 | SILENT=false
42 | ;;
43 | \?)
44 | echo "Invalid flag: -$OPTARG" >&2
45 | exit 1
46 | ;;
47 | esac
48 | done
49 |
50 | if $SILENT ; then
51 | echo "Running in the silent mode, run with -v if you want to see script logs."
52 | fi
53 |
54 | EXCLUDE="verify-all.sh"
55 |
56 | ret=0
57 | for t in "$SCRIPT_ROOT"/hack/verify-*.sh
58 | do
59 | if is-excluded $t ; then
60 | echo "Skipping $t"
61 | continue
62 | fi
63 | if $SILENT ; then
64 | echo -e "Verifying $t"
65 | if bash "$t" &> /dev/null; then
66 | echo -e "${color_green}SUCCESS${color_norm}"
67 | else
68 | echo -e "${color_red}FAILED${color_norm}"
69 | ret=1
70 | fi
71 | else
72 | bash "$t" || ret=1
73 | fi
74 | done
75 |
76 | exit $ret
77 |
78 | # ex: ts=2 sw=2 et filetype=sh
79 |
--------------------------------------------------------------------------------
/hack/kube-env.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2014 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | # Some useful colors.
18 | if [[ -z "${color_start-}" ]]; then
19 | declare -r color_start="\033["
20 | declare -r color_red="${color_start}0;31m"
21 | declare -r color_yellow="${color_start}0;33m"
22 | declare -r color_green="${color_start}0;32m"
23 | declare -r color_norm="${color_start}0m"
24 | fi
25 |
26 | # Returns the server version as MMmmpp, with MM as the major
27 | # component, mm the minor component, and pp as the patch
28 | # revision. e.g. 0.7.1 is echoed as 701, and 1.0.11 would be
29 | # 10011. (This makes for easy integer comparison in bash.)
30 | function kube_server_version() {
31 | local server_version
32 | local major
33 | local minor
34 | local patch
35 |
36 | # This sed expression is the POSIX BRE to match strings like:
37 | # Server Version: &version.Info{Major:"0", Minor:"7+", GitVersion:"v0.7.0-dirty", GitCommit:"ad44234f7152e9c66bc2853575445c7071335e57", GitTreeState:"dirty"}
38 | # and capture the GitVersion portion (which has the patch level)
39 | server_version=$(${KUBECTL} --match-server-version=false version | grep "Server Version:")
40 | read major minor patch < <(
41 | echo ${server_version} | \
42 | sed "s/.*GitVersion:\"v\([0-9]\{1,\}\)\.\([0-9]\{1,\}\)\.\([0-9]\{1,\}\).*/\1 \2 \3/")
43 | printf "%02d%02d%02d" ${major} ${minor} ${patch} | sed 's/^0*//'
44 | }
45 |
--------------------------------------------------------------------------------
/hack/verify-crds.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2020 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -o errexit
18 | set -o nounset
19 | set -o pipefail
20 |
21 | SCRIPT_ROOT=$(dirname "${BASH_SOURCE}")/..
22 | DIFFROOT="${SCRIPT_ROOT}/config"
23 | TMP_DIFFROOT="${SCRIPT_ROOT}/_tmp/config"
24 | _tmp="${SCRIPT_ROOT}/_tmp"
25 | # The controller-gen command for generating CRDs from API definitions.
26 | CONTROLLER_GEN="go -C tools run sigs.k8s.io/controller-tools/cmd/controller-gen"
27 | # Need v1 to support defaults in CRDs, unfortunately limiting us to k8s 1.16+
28 | CRD_OPTIONS="crd:crdVersions=v1"
29 |
30 | cd "${SCRIPT_ROOT}"
31 |
32 | cleanup() {
33 | rm -rf "${_tmp}"
34 | }
35 | trap "cleanup" EXIT SIGINT
36 |
37 | cleanup
38 |
39 | mkdir -p "${TMP_DIFFROOT}"
40 | cp -a "${DIFFROOT}"/* "${TMP_DIFFROOT}"
41 |
42 | ${CONTROLLER_GEN} ${CRD_OPTIONS} rbac:roleName=mcs-derived-service-manager webhook \
43 | paths="${SCRIPT_ROOT}/..." schemapatch:manifests="${SCRIPT_ROOT}/config/crd-base" output:crd:none \
44 | output:schemapatch:dir="${TMP_DIFFROOT}/crd" output:rbac:dir="${TMP_DIFFROOT}/rbac"
45 |
46 | echo "diffing ${DIFFROOT} against freshly generated codegen in ${TMP_DIFFROOT}"
47 | ret=0
48 | diff -Naupr "${DIFFROOT}" "${TMP_DIFFROOT}" || ret=$?
49 | if [[ $ret -eq 0 ]]
50 | then
51 | echo "${DIFFROOT} up to date."
52 | else
53 | echo "${DIFFROOT} is out of date. Please run 'make manifests'"
54 | exit 1
55 | fi
56 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Multi-cluster Service APIs
2 |
3 | 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.
4 |
5 | This repo contains the initial implementation according to [KEP-1645][kep] and will
6 | be used for iterative development as we work to meet our Alpha -> Beta
7 | [graduation requirements][grad-reqs].
8 |
9 | [kep]: https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api
10 | [grad-reqs]: https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#alpha---beta-graduation
11 |
12 | ## Try it out
13 |
14 | To see the API in action, run `make demo` to build and run a local demo against
15 | a pair of kind clusters. Alternatively, you can take a self guided tour. Use:
16 |
17 | - `./scripts/up.sh` to create a pair of clusters with mutually connected networks
18 | and install the `mcs-api-controller`.
19 |
20 | _This will use a pre-existing controller image if available, it's recommended
21 | to run `make docker-build` first._
22 | - `./demo/demo.sh` to run the same demo as above against your newly created
23 | clusters (must run `./scripts/up.sh` first).
24 | - `./scripts/down.sh` to tear down your clusters.
25 |
26 | ## Community, discussion, contribution, and support
27 |
28 | Learn how to engage with the Kubernetes community on the [community page](http://kubernetes.io/community/).
29 |
30 | You can reach the maintainers of this project at:
31 |
32 | - [Slack](https://kubernetes.slack.com/messages/sig-multicluster)
33 | - [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-multicluster)
34 |
35 | [Our meeting schedule is here]( https://github.com/kubernetes/community/tree/master/sig-multicluster#meetings)
36 |
37 |
38 | ## Technical Leads
39 |
40 | - @pmorie
41 | - @jeremyot
42 |
43 | ### Code of conduct
44 |
45 | Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md).
46 |
--------------------------------------------------------------------------------
/pkg/client/clientset/versioned/fake/register.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | package fake
20 |
21 | import (
22 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23 | runtime "k8s.io/apimachinery/pkg/runtime"
24 | schema "k8s.io/apimachinery/pkg/runtime/schema"
25 | serializer "k8s.io/apimachinery/pkg/runtime/serializer"
26 | utilruntime "k8s.io/apimachinery/pkg/util/runtime"
27 | multiclusterv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
28 | )
29 |
30 | var scheme = runtime.NewScheme()
31 | var codecs = serializer.NewCodecFactory(scheme)
32 |
33 | var localSchemeBuilder = runtime.SchemeBuilder{
34 | multiclusterv1alpha1.AddToScheme,
35 | }
36 |
37 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition
38 | // of clientsets, like in:
39 | //
40 | // import (
41 | // "k8s.io/client-go/kubernetes"
42 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme"
43 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
44 | // )
45 | //
46 | // kclientset, _ := kubernetes.NewForConfig(c)
47 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
48 | //
49 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
50 | // correctly.
51 | var AddToScheme = localSchemeBuilder.AddToScheme
52 |
53 | func init() {
54 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
55 | utilruntime.Must(AddToScheme(scheme))
56 | }
57 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module sigs.k8s.io/mcs-api
2 |
3 | go 1.23.0
4 |
5 | require (
6 | k8s.io/api v0.32.5
7 | k8s.io/apimachinery v0.32.5
8 | k8s.io/client-go v0.32.5
9 | )
10 |
11 | require (
12 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
13 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect
14 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect
15 | github.com/go-logr/logr v1.4.2 // indirect
16 | github.com/go-openapi/jsonpointer v0.21.0 // indirect
17 | github.com/go-openapi/jsonreference v0.20.2 // indirect
18 | github.com/go-openapi/swag v0.23.0 // indirect
19 | github.com/gogo/protobuf v1.3.2 // indirect
20 | github.com/golang/protobuf v1.5.4 // indirect
21 | github.com/google/gnostic-models v0.6.8 // indirect
22 | github.com/google/go-cmp v0.6.0 // indirect
23 | github.com/google/gofuzz v1.2.0 // indirect
24 | github.com/google/uuid v1.6.0 // indirect
25 | github.com/josharian/intern v1.0.0 // indirect
26 | github.com/json-iterator/go v1.1.12 // indirect
27 | github.com/mailru/easyjson v0.7.7 // indirect
28 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
29 | github.com/modern-go/reflect2 v1.0.2 // indirect
30 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
31 | github.com/pkg/errors v0.9.1 // indirect
32 | github.com/x448/float16 v0.8.4 // indirect
33 | golang.org/x/net v0.30.0 // indirect
34 | golang.org/x/oauth2 v0.23.0 // indirect
35 | golang.org/x/sys v0.26.0 // indirect
36 | golang.org/x/term v0.25.0 // indirect
37 | golang.org/x/text v0.19.0 // indirect
38 | golang.org/x/time v0.7.0 // indirect
39 | google.golang.org/protobuf v1.35.1 // indirect
40 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
41 | gopkg.in/inf.v0 v0.9.1 // indirect
42 | gopkg.in/yaml.v3 v3.0.1 // indirect
43 | k8s.io/klog/v2 v2.130.1 // indirect
44 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
45 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
46 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
47 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
48 | sigs.k8s.io/yaml v1.4.0 // indirect
49 | )
50 |
--------------------------------------------------------------------------------
/pkg/client/clientset/versioned/scheme/register.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | package scheme
20 |
21 | import (
22 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23 | runtime "k8s.io/apimachinery/pkg/runtime"
24 | schema "k8s.io/apimachinery/pkg/runtime/schema"
25 | serializer "k8s.io/apimachinery/pkg/runtime/serializer"
26 | utilruntime "k8s.io/apimachinery/pkg/util/runtime"
27 | multiclusterv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
28 | )
29 |
30 | var Scheme = runtime.NewScheme()
31 | var Codecs = serializer.NewCodecFactory(Scheme)
32 | var ParameterCodec = runtime.NewParameterCodec(Scheme)
33 | var localSchemeBuilder = runtime.SchemeBuilder{
34 | multiclusterv1alpha1.AddToScheme,
35 | }
36 |
37 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition
38 | // of clientsets, like in:
39 | //
40 | // import (
41 | // "k8s.io/client-go/kubernetes"
42 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme"
43 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
44 | // )
45 | //
46 | // kclientset, _ := kubernetes.NewForConfig(c)
47 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
48 | //
49 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
50 | // correctly.
51 | var AddToScheme = localSchemeBuilder.AddToScheme
52 |
53 | func init() {
54 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
55 | utilruntime.Must(AddToScheme(Scheme))
56 | }
57 |
--------------------------------------------------------------------------------
/pkg/client/informers/externalversions/apis/v1alpha1/interface.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by informer-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | import (
22 | internalinterfaces "sigs.k8s.io/mcs-api/pkg/client/informers/externalversions/internalinterfaces"
23 | )
24 |
25 | // Interface provides access to all the informers in this group version.
26 | type Interface interface {
27 | // ServiceExports returns a ServiceExportInformer.
28 | ServiceExports() ServiceExportInformer
29 | // ServiceImports returns a ServiceImportInformer.
30 | ServiceImports() ServiceImportInformer
31 | }
32 |
33 | type version struct {
34 | factory internalinterfaces.SharedInformerFactory
35 | namespace string
36 | tweakListOptions internalinterfaces.TweakListOptionsFunc
37 | }
38 |
39 | // New returns a new Interface.
40 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
41 | return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
42 | }
43 |
44 | // ServiceExports returns a ServiceExportInformer.
45 | func (v *version) ServiceExports() ServiceExportInformer {
46 | return &serviceExportInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
47 | }
48 |
49 | // ServiceImports returns a ServiceImportInformer.
50 | func (v *version) ServiceImports() ServiceImportInformer {
51 | return &serviceImportInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
52 | }
53 |
--------------------------------------------------------------------------------
/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_serviceexport.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | package fake
20 |
21 | import (
22 | gentype "k8s.io/client-go/gentype"
23 | v1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
24 | apisv1alpha1 "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned/typed/apis/v1alpha1"
25 | )
26 |
27 | // fakeServiceExports implements ServiceExportInterface
28 | type fakeServiceExports struct {
29 | *gentype.FakeClientWithList[*v1alpha1.ServiceExport, *v1alpha1.ServiceExportList]
30 | Fake *FakeMulticlusterV1alpha1
31 | }
32 |
33 | func newFakeServiceExports(fake *FakeMulticlusterV1alpha1, namespace string) apisv1alpha1.ServiceExportInterface {
34 | return &fakeServiceExports{
35 | gentype.NewFakeClientWithList[*v1alpha1.ServiceExport, *v1alpha1.ServiceExportList](
36 | fake.Fake,
37 | namespace,
38 | v1alpha1.SchemeGroupVersion.WithResource("serviceexports"),
39 | v1alpha1.SchemeGroupVersion.WithKind("ServiceExport"),
40 | func() *v1alpha1.ServiceExport { return &v1alpha1.ServiceExport{} },
41 | func() *v1alpha1.ServiceExportList { return &v1alpha1.ServiceExportList{} },
42 | func(dst, src *v1alpha1.ServiceExportList) { dst.ListMeta = src.ListMeta },
43 | func(list *v1alpha1.ServiceExportList) []*v1alpha1.ServiceExport {
44 | return gentype.ToPointerSlice(list.Items)
45 | },
46 | func(list *v1alpha1.ServiceExportList, items []*v1alpha1.ServiceExport) {
47 | list.Items = gentype.FromPointerSlice(items)
48 | },
49 | ),
50 | fake,
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_serviceimport.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | package fake
20 |
21 | import (
22 | gentype "k8s.io/client-go/gentype"
23 | v1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
24 | apisv1alpha1 "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned/typed/apis/v1alpha1"
25 | )
26 |
27 | // fakeServiceImports implements ServiceImportInterface
28 | type fakeServiceImports struct {
29 | *gentype.FakeClientWithList[*v1alpha1.ServiceImport, *v1alpha1.ServiceImportList]
30 | Fake *FakeMulticlusterV1alpha1
31 | }
32 |
33 | func newFakeServiceImports(fake *FakeMulticlusterV1alpha1, namespace string) apisv1alpha1.ServiceImportInterface {
34 | return &fakeServiceImports{
35 | gentype.NewFakeClientWithList[*v1alpha1.ServiceImport, *v1alpha1.ServiceImportList](
36 | fake.Fake,
37 | namespace,
38 | v1alpha1.SchemeGroupVersion.WithResource("serviceimports"),
39 | v1alpha1.SchemeGroupVersion.WithKind("ServiceImport"),
40 | func() *v1alpha1.ServiceImport { return &v1alpha1.ServiceImport{} },
41 | func() *v1alpha1.ServiceImportList { return &v1alpha1.ServiceImportList{} },
42 | func(dst, src *v1alpha1.ServiceImportList) { dst.ListMeta = src.ListMeta },
43 | func(list *v1alpha1.ServiceImportList) []*v1alpha1.ServiceImport {
44 | return gentype.ToPointerSlice(list.Items)
45 | },
46 | func(list *v1alpha1.ServiceImportList, items []*v1alpha1.ServiceImport) {
47 | list.Items = gentype.FromPointerSlice(items)
48 | },
49 | ),
50 | fake,
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/controllers/cmd/servicecontroller/servicecontroller.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package main
18 |
19 | import (
20 | "flag"
21 | "os"
22 |
23 | "k8s.io/apimachinery/pkg/runtime"
24 | clientgoscheme "k8s.io/client-go/kubernetes/scheme"
25 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
26 | ctrl "sigs.k8s.io/controller-runtime"
27 | "sigs.k8s.io/controller-runtime/pkg/log/zap"
28 | "sigs.k8s.io/controller-runtime/pkg/metrics/server"
29 | "sigs.k8s.io/controller-runtime/pkg/webhook"
30 | "sigs.k8s.io/mcs-api/controllers"
31 | "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
32 | )
33 |
34 | var (
35 | scheme = runtime.NewScheme()
36 | setupLog = ctrl.Log.WithName("setup")
37 | )
38 |
39 | func init() {
40 | clientgoscheme.AddToScheme(scheme)
41 | v1alpha1.AddToScheme(scheme)
42 | }
43 |
44 | func main() {
45 | var metricsAddr string
46 | var enableLeaderElection bool
47 | flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
48 | flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
49 | "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
50 | flag.Parse()
51 | opts := ctrl.Options{
52 | Scheme: scheme,
53 | Metrics: server.Options{
54 | BindAddress: metricsAddr,
55 | },
56 | LeaderElection: enableLeaderElection,
57 | WebhookServer: webhook.NewServer(webhook.Options{
58 | Port: 9443,
59 | }),
60 | }
61 | ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
62 |
63 | if err := controllers.Start(ctrl.SetupSignalHandler(), ctrl.GetConfigOrDie(), setupLog, opts); err != nil {
64 | setupLog.Error(err, "problem running controllers")
65 | os.Exit(1)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/demo/udemo.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright 2016 The Kubernetes Authors.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | readonly reset=$(tput sgr0)
17 | readonly green=$(tput bold; tput setaf 2)
18 | readonly yellow=$(tput bold; tput setaf 3)
19 | readonly blue=$(tput bold; tput setaf 6)
20 | readonly timeout=$(if [ "$(uname)" == "Darwin" ]; then echo "1"; else echo "0.1"; fi)
21 |
22 | function desc() {
23 | maybe_first_prompt
24 | echo "$blue# $@$reset"
25 | prompt
26 | }
27 |
28 | function prompt() {
29 | echo -n "$yellow\$ $reset"
30 | }
31 |
32 | started=""
33 | function maybe_first_prompt() {
34 | if [ -z "$started" ]; then
35 | prompt
36 | started=true
37 | fi
38 | }
39 |
40 | # After a `run` this variable will hold the stdout of the command that was run.
41 | # If the command was interactive, this will likely be garbage.
42 | DEMO_RUN_STDOUT=""
43 |
44 | function run() {
45 | maybe_first_prompt
46 | rate=25
47 | if [ -n "$DEMO_RUN_FAST" ]; then
48 | rate=1000
49 | fi
50 | echo "$green$1$reset" | pv -qL $rate
51 | if [ -n "$DEMO_RUN_FAST" ]; then
52 | sleep 0.5
53 | fi
54 | OFILE="$(mktemp -t $(basename $0).XXXXXX)"
55 | if [[ "$OSTYPE" == "darwin"* ]]; then
56 | script -q -a "$OFILE" bash -c "$1"
57 | else
58 | script -eq -c "$1" -f "$OFILE"
59 | fi
60 | r=$?
61 | read -d '' -t "${timeout}" -n 10000 # clear stdin
62 | prompt
63 | if [ -z "$DEMO_AUTO_RUN" ]; then
64 | read -s
65 | fi
66 | DEMO_RUN_STDOUT="$(tail -n +2 $OFILE | sed 's/\r//g')"
67 | return $r
68 | }
69 |
70 | function relative() {
71 | for arg; do
72 | echo "$(realpath $(dirname $(which $0)))/$arg" | sed "s|$(realpath $(pwd))|.|"
73 | done
74 | }
75 |
76 | SSH_NODE=$(kubectl get nodes | tail -1 | cut -f1 -d' ')
77 |
78 | trap "echo" EXIT
79 |
--------------------------------------------------------------------------------
/pkg/client/informers/externalversions/generic.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by informer-gen. DO NOT EDIT.
18 |
19 | package externalversions
20 |
21 | import (
22 | fmt "fmt"
23 |
24 | schema "k8s.io/apimachinery/pkg/runtime/schema"
25 | cache "k8s.io/client-go/tools/cache"
26 | v1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
27 | )
28 |
29 | // GenericInformer is type of SharedIndexInformer which will locate and delegate to other
30 | // sharedInformers based on type
31 | type GenericInformer interface {
32 | Informer() cache.SharedIndexInformer
33 | Lister() cache.GenericLister
34 | }
35 |
36 | type genericInformer struct {
37 | informer cache.SharedIndexInformer
38 | resource schema.GroupResource
39 | }
40 |
41 | // Informer returns the SharedIndexInformer.
42 | func (f *genericInformer) Informer() cache.SharedIndexInformer {
43 | return f.informer
44 | }
45 |
46 | // Lister returns the GenericLister.
47 | func (f *genericInformer) Lister() cache.GenericLister {
48 | return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource)
49 | }
50 |
51 | // ForResource gives generic access to a shared informer of the matching type
52 | // TODO extend this to unknown resources with a client pool
53 | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
54 | switch resource {
55 | // Group=multicluster.x-k8s.io, Version=v1alpha1
56 | case v1alpha1.SchemeGroupVersion.WithResource("serviceexports"):
57 | return &genericInformer{resource: resource.GroupResource(), informer: f.Multicluster().V1alpha1().ServiceExports().Informer()}, nil
58 | case v1alpha1.SchemeGroupVersion.WithResource("serviceimports"):
59 | return &genericInformer{resource: resource.GroupResource(), informer: f.Multicluster().V1alpha1().ServiceImports().Informer()}, nil
60 |
61 | }
62 |
63 | return nil, fmt.Errorf("no informer found for %v", resource)
64 | }
65 |
--------------------------------------------------------------------------------
/conformance/go.mod:
--------------------------------------------------------------------------------
1 | module sigs.k8s.io/mcs-api/conformance
2 |
3 | go 1.23.0
4 |
5 | require (
6 | github.com/onsi/ginkgo/v2 v2.21.0
7 | github.com/onsi/gomega v1.35.1
8 | k8s.io/api v0.32.5
9 | k8s.io/apimachinery v0.32.5
10 | k8s.io/client-go v0.32.5
11 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
12 | sigs.k8s.io/mcs-api v0.3.0
13 | )
14 |
15 | replace sigs.k8s.io/mcs-api => ..
16 |
17 | require (
18 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
19 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect
20 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect
21 | github.com/go-logr/logr v1.4.2 // indirect
22 | github.com/go-openapi/jsonpointer v0.21.0 // indirect
23 | github.com/go-openapi/jsonreference v0.20.2 // indirect
24 | github.com/go-openapi/swag v0.23.0 // indirect
25 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
26 | github.com/gogo/protobuf v1.3.2 // indirect
27 | github.com/golang/protobuf v1.5.4 // indirect
28 | github.com/google/gnostic-models v0.6.8 // indirect
29 | github.com/google/go-cmp v0.6.0 // indirect
30 | github.com/google/gofuzz v1.2.0 // indirect
31 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
32 | github.com/google/uuid v1.6.0 // indirect
33 | github.com/gorilla/websocket v1.5.0 // indirect
34 | github.com/josharian/intern v1.0.0 // indirect
35 | github.com/json-iterator/go v1.1.12 // indirect
36 | github.com/mailru/easyjson v0.7.7 // indirect
37 | github.com/moby/spdystream v0.5.0 // indirect
38 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
39 | github.com/modern-go/reflect2 v1.0.2 // indirect
40 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
41 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
42 | github.com/pkg/errors v0.9.1 // indirect
43 | github.com/spf13/pflag v1.0.5 // indirect
44 | github.com/x448/float16 v0.8.4 // indirect
45 | golang.org/x/net v0.30.0 // indirect
46 | golang.org/x/oauth2 v0.23.0 // indirect
47 | golang.org/x/sys v0.26.0 // indirect
48 | golang.org/x/term v0.25.0 // indirect
49 | golang.org/x/text v0.19.0 // indirect
50 | golang.org/x/time v0.7.0 // indirect
51 | golang.org/x/tools v0.26.0 // indirect
52 | google.golang.org/protobuf v1.35.1 // indirect
53 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
54 | gopkg.in/inf.v0 v0.9.1 // indirect
55 | gopkg.in/yaml.v3 v3.0.1 // indirect
56 | k8s.io/klog/v2 v2.130.1 // indirect
57 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
58 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
59 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
60 | sigs.k8s.io/yaml v1.4.0 // indirect
61 | )
62 |
--------------------------------------------------------------------------------
/e2e/go.mod:
--------------------------------------------------------------------------------
1 | module sigs.k8s.io/mcs-api/e2e
2 |
3 | go 1.23.0
4 |
5 | require (
6 | github.com/onsi/ginkgo/v2 v2.21.0
7 | github.com/onsi/gomega v1.35.1
8 | k8s.io/api v0.32.5
9 | k8s.io/apimachinery v0.32.5
10 | k8s.io/client-go v0.32.5
11 | sigs.k8s.io/mcs-api v0.3.0
12 | )
13 |
14 | replace sigs.k8s.io/mcs-api => ..
15 |
16 | require (
17 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
18 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect
19 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect
20 | github.com/go-logr/logr v1.4.2 // indirect
21 | github.com/go-openapi/jsonpointer v0.21.0 // indirect
22 | github.com/go-openapi/jsonreference v0.20.2 // indirect
23 | github.com/go-openapi/swag v0.23.0 // indirect
24 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
25 | github.com/gogo/protobuf v1.3.2 // indirect
26 | github.com/golang/protobuf v1.5.4 // indirect
27 | github.com/google/gnostic-models v0.6.8 // indirect
28 | github.com/google/go-cmp v0.6.0 // indirect
29 | github.com/google/gofuzz v1.2.0 // indirect
30 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
31 | github.com/google/uuid v1.6.0 // indirect
32 | github.com/gorilla/websocket v1.5.0 // indirect
33 | github.com/josharian/intern v1.0.0 // indirect
34 | github.com/json-iterator/go v1.1.12 // indirect
35 | github.com/mailru/easyjson v0.7.7 // indirect
36 | github.com/moby/spdystream v0.5.0 // indirect
37 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
38 | github.com/modern-go/reflect2 v1.0.2 // indirect
39 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
40 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
41 | github.com/pkg/errors v0.9.1 // indirect
42 | github.com/spf13/pflag v1.0.5 // indirect
43 | github.com/x448/float16 v0.8.4 // indirect
44 | golang.org/x/net v0.30.0 // indirect
45 | golang.org/x/oauth2 v0.23.0 // indirect
46 | golang.org/x/sys v0.26.0 // indirect
47 | golang.org/x/term v0.25.0 // indirect
48 | golang.org/x/text v0.19.0 // indirect
49 | golang.org/x/time v0.7.0 // indirect
50 | golang.org/x/tools v0.26.0 // indirect
51 | google.golang.org/protobuf v1.35.1 // indirect
52 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
53 | gopkg.in/inf.v0 v0.9.1 // indirect
54 | gopkg.in/yaml.v3 v3.0.1 // indirect
55 | k8s.io/klog/v2 v2.130.1 // indirect
56 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
57 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
58 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
59 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
60 | sigs.k8s.io/yaml v1.4.0 // indirect
61 | )
62 |
--------------------------------------------------------------------------------
/pkg/apis/v1alpha1/zz_generated.register.go:
--------------------------------------------------------------------------------
1 | //go:build !ignore_autogenerated
2 | // +build !ignore_autogenerated
3 |
4 | /*
5 | Copyright 2020 The Kubernetes Authors.
6 |
7 | Licensed under the Apache License, Version 2.0 (the "License");
8 | you may not use this file except in compliance with the License.
9 | You may obtain a copy of the License at
10 |
11 | http://www.apache.org/licenses/LICENSE-2.0
12 |
13 | Unless required by applicable law or agreed to in writing, software
14 | distributed under the License is distributed on an "AS IS" BASIS,
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | See the License for the specific language governing permissions and
17 | limitations under the License.
18 | */
19 |
20 | // Code generated by register-gen. DO NOT EDIT.
21 |
22 | package v1alpha1
23 |
24 | import (
25 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 | runtime "k8s.io/apimachinery/pkg/runtime"
27 | schema "k8s.io/apimachinery/pkg/runtime/schema"
28 | )
29 |
30 | // GroupName specifies the group name used to register the objects.
31 | const GroupName = "multicluster.x-k8s.io"
32 |
33 | // GroupVersion specifies the group and the version used to register the objects.
34 | var GroupVersion = v1.GroupVersion{Group: GroupName, Version: "v1alpha1"}
35 |
36 | // SchemeGroupVersion is group version used to register these objects
37 | // Deprecated: use GroupVersion instead.
38 | var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
39 |
40 | // Resource takes an unqualified resource and returns a Group qualified GroupResource
41 | func Resource(resource string) schema.GroupResource {
42 | return SchemeGroupVersion.WithResource(resource).GroupResource()
43 | }
44 |
45 | var (
46 | // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.
47 | SchemeBuilder runtime.SchemeBuilder
48 | localSchemeBuilder = &SchemeBuilder
49 | // Deprecated: use Install instead
50 | AddToScheme = localSchemeBuilder.AddToScheme
51 | Install = localSchemeBuilder.AddToScheme
52 | )
53 |
54 | func init() {
55 | // We only register manually written functions here. The registration of the
56 | // generated functions takes place in the generated files. The separation
57 | // makes the code compile even when the generated files are missing.
58 | localSchemeBuilder.Register(addKnownTypes)
59 | }
60 |
61 | // Adds the list of known types to Scheme.
62 | func addKnownTypes(scheme *runtime.Scheme) error {
63 | scheme.AddKnownTypes(SchemeGroupVersion,
64 | &ServiceExport{},
65 | &ServiceExportList{},
66 | &ServiceImport{},
67 | &ServiceImportList{},
68 | )
69 | // AddToGroupVersion allows the serialization of client types like ListOptions.
70 | v1.AddToGroupVersion(scheme, SchemeGroupVersion)
71 | return nil
72 | }
73 |
--------------------------------------------------------------------------------
/hack/update-codegen.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Copyright 2020 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -o errexit
18 | set -o nounset
19 | set -o pipefail
20 |
21 | SCRIPT_ROOT=$(dirname "${BASH_SOURCE}")/..
22 |
23 | go -C tools install k8s.io/code-generator/cmd/{client-gen,lister-gen,informer-gen,deepcopy-gen,register-gen}
24 |
25 | # Go installs the above commands to get installed in $GOBIN if defined, and $GOPATH/bin otherwise:
26 | GOBIN="$(go env GOBIN)"
27 | gobin="${GOBIN:-$(go env GOPATH)/bin}"
28 |
29 | OUTPUT_PKG=sigs.k8s.io/mcs-api/pkg/client
30 | OUTPUT_DIR=$SCRIPT_ROOT/pkg/client
31 | FQ_APIS=sigs.k8s.io/mcs-api/pkg/apis/v1alpha1
32 | CLIENTSET_NAME=versioned
33 | CLIENTSET_PKG_NAME=clientset
34 |
35 | if [[ "${VERIFY_CODEGEN:-}" == "true" ]]; then
36 | echo "Running in verification mode"
37 | ORIG_OUTPUT_DIR="$OUTPUT_DIR"
38 | OUTPUT_DIR=$(mktemp -d)
39 | trap "rm -rf $OUTPUT_DIR" EXIT
40 | else
41 | # Clear existing code before re-generating it
42 | rm -rf "$OUTPUT_DIR"
43 | fi
44 | COMMON_FLAGS="--go-header-file ${SCRIPT_ROOT}/hack/boilerplate.go.txt"
45 |
46 | echo "Generating clientset at ${OUTPUT_PKG}/${CLIENTSET_PKG_NAME}"
47 | "${gobin}/client-gen" --clientset-name "${CLIENTSET_NAME}" --input-base "" --input "${FQ_APIS}" --output-pkg "${OUTPUT_PKG}/${CLIENTSET_PKG_NAME}" --output-dir "$OUTPUT_DIR/$CLIENTSET_PKG_NAME" ${COMMON_FLAGS}
48 |
49 | echo "Generating listers at ${OUTPUT_PKG}/listers"
50 | "${gobin}/lister-gen" "${FQ_APIS}" --output-pkg "${OUTPUT_PKG}/listers" --output-dir "${OUTPUT_DIR}/listers" ${COMMON_FLAGS}
51 |
52 | echo "Generating informers at ${OUTPUT_PKG}/informers"
53 | "${gobin}/informer-gen" \
54 | "${FQ_APIS}" \
55 | --versioned-clientset-package "${OUTPUT_PKG}/${CLIENTSET_PKG_NAME}/${CLIENTSET_NAME}" \
56 | --listers-package "${OUTPUT_PKG}/listers" \
57 | --output-pkg "${OUTPUT_PKG}/informers" \
58 | --output-dir "${OUTPUT_DIR}/informers" \
59 | ${COMMON_FLAGS}
60 |
61 | echo "Generating register at ${FQ_APIS}"
62 | "${gobin}/register-gen" ${FQ_APIS} --output-file zz_generated.register.go ${COMMON_FLAGS}
63 |
64 | if [[ "${VERIFY_CODEGEN:-}" == "true" ]]; then
65 | diff -urN "$ORIG_OUTPUT_DIR" "$OUTPUT_DIR"
66 | fi
67 |
--------------------------------------------------------------------------------
/controllers/endpointslice.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package controllers
18 |
19 | import (
20 | "context"
21 |
22 | "github.com/go-logr/logr"
23 | discoveryv1 "k8s.io/api/discovery/v1"
24 | "k8s.io/apimachinery/pkg/types"
25 | ctrl "sigs.k8s.io/controller-runtime"
26 | "sigs.k8s.io/controller-runtime/pkg/client"
27 | "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
28 | )
29 |
30 | // EndpointSliceReconciler reconciles a EndpointSlice object
31 | type EndpointSliceReconciler struct {
32 | client.Client
33 | Log logr.Logger
34 | }
35 |
36 | // +kubebuilder:rbac:groups=discovery.k8s.io,resources=endpointslices,verbs=get;list;watch;update;patch
37 |
38 | func shouldIgnoreEndpointSlice(epSlice *discoveryv1.EndpointSlice) bool {
39 | if epSlice.DeletionTimestamp != nil {
40 | return true
41 | }
42 | if epSlice.Labels[v1alpha1.LabelServiceName] == "" {
43 | return true
44 | }
45 | return false
46 | }
47 |
48 | // Reconcile the changes.
49 | func (r *EndpointSliceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
50 | log := r.Log.WithValues("endpointslice", req.NamespacedName)
51 |
52 | var epSlice discoveryv1.EndpointSlice
53 | if err := r.Client.Get(ctx, req.NamespacedName, &epSlice); err != nil {
54 | return ctrl.Result{}, client.IgnoreNotFound(err)
55 | }
56 | if shouldIgnoreEndpointSlice(&epSlice) {
57 | return ctrl.Result{}, nil
58 | }
59 | // Ensure the EndpointSlice is labelled to match the ServiceImport's derived
60 | // Service.
61 | serviceName := derivedName(types.NamespacedName{Namespace: epSlice.Namespace, Name: epSlice.Labels[v1alpha1.LabelServiceName]})
62 | if epSlice.Labels[discoveryv1.LabelServiceName] == serviceName {
63 | return ctrl.Result{}, nil
64 | }
65 | epSlice.Labels[discoveryv1.LabelServiceName] = serviceName
66 | if err := r.Client.Update(ctx, &epSlice); err != nil {
67 | return ctrl.Result{}, err
68 | }
69 | log.Info("added label", discoveryv1.LabelServiceName, serviceName)
70 | return ctrl.Result{}, nil
71 | }
72 |
73 | // SetupWithManager wires up the controller.
74 | func (r *EndpointSliceReconciler) SetupWithManager(mgr ctrl.Manager) error {
75 | return ctrl.NewControllerManagedBy(mgr).For(&discoveryv1.EndpointSlice{}).Complete(r)
76 | }
77 |
--------------------------------------------------------------------------------
/controllers/common.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package controllers
18 |
19 | import (
20 | "context"
21 | "crypto/sha256"
22 | "encoding/base32"
23 | "os"
24 | "strings"
25 |
26 | "github.com/go-logr/logr"
27 | "k8s.io/apimachinery/pkg/types"
28 | "k8s.io/client-go/rest"
29 | ctrl "sigs.k8s.io/controller-runtime"
30 | )
31 |
32 | const (
33 | // DerivedServiceAnnotation is set on a ServiceImport to reference the
34 | // derived Service that represents the imported service for kube-proxy.
35 | DerivedServiceAnnotation = "multicluster.kubernetes.io/derived-service"
36 | serviceImportKind = "ServiceImport"
37 | )
38 |
39 | func derivedName(name types.NamespacedName) string {
40 | hash := sha256.New()
41 | hash.Write([]byte(name.String()))
42 | return "derived-" + strings.ToLower(base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString(hash.Sum(nil)))[:10]
43 | }
44 |
45 | // Start the controllers with the supplied config
46 | func Start(ctx context.Context, cfg *rest.Config, setupLog logr.Logger, opts ctrl.Options) error {
47 | mgr, err := ctrl.NewManager(cfg, opts)
48 | if err != nil {
49 | setupLog.Error(err, "unable to start manager")
50 | os.Exit(1)
51 | }
52 |
53 | if err = (&ServiceImportReconciler{
54 | Client: mgr.GetClient(),
55 | Log: ctrl.Log.WithName("controllers").WithName("ServiceImport"),
56 | }).SetupWithManager(mgr); err != nil {
57 | setupLog.Error(err, "unable to create controller", "controller", "ServiceImport")
58 | return err
59 | }
60 | if err = (&ServiceReconciler{
61 | Client: mgr.GetClient(),
62 | Log: ctrl.Log.WithName("controllers").WithName("Service"),
63 | }).SetupWithManager(mgr); err != nil {
64 | setupLog.Error(err, "unable to create controller", "controller", "Service")
65 | return err
66 | }
67 | if err = (&EndpointSliceReconciler{
68 | Client: mgr.GetClient(),
69 | Log: ctrl.Log.WithName("controllers").WithName("EndpointSlice"),
70 | }).SetupWithManager(mgr); err != nil {
71 | setupLog.Error(err, "unable to create controller", "controller", "EndpointSlice")
72 | return err
73 | }
74 |
75 | setupLog.Info("starting manager")
76 | if err := mgr.Start(ctx); err != nil {
77 | setupLog.Error(err, "problem running manager")
78 | return err
79 | }
80 | return nil
81 | }
82 |
--------------------------------------------------------------------------------
/controllers/service.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package controllers
18 |
19 | import (
20 | "context"
21 | "slices"
22 |
23 | "github.com/go-logr/logr"
24 | v1 "k8s.io/api/core/v1"
25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 | "k8s.io/apimachinery/pkg/types"
27 | ctrl "sigs.k8s.io/controller-runtime"
28 | "sigs.k8s.io/controller-runtime/pkg/client"
29 | "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
30 | )
31 |
32 | // ServiceReconciler reconciles a Service object
33 | type ServiceReconciler struct {
34 | client.Client
35 | Log logr.Logger
36 | }
37 |
38 | // +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch
39 |
40 | func serviceImportOwner(refs []metav1.OwnerReference) string {
41 | for _, ref := range refs {
42 | if ref.APIVersion == v1alpha1.GroupVersion.String() && ref.Kind == serviceImportKind {
43 | return ref.Name
44 | }
45 | }
46 | return ""
47 | }
48 |
49 | // Reconcile the changes.
50 | func (r *ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
51 | log := r.Log.WithValues("service", req.NamespacedName)
52 | var service v1.Service
53 | if err := r.Client.Get(ctx, req.NamespacedName, &service); err != nil {
54 | return ctrl.Result{}, client.IgnoreNotFound(err)
55 | }
56 | if service.DeletionTimestamp != nil {
57 | return ctrl.Result{}, nil
58 | }
59 | importName := serviceImportOwner(service.OwnerReferences)
60 | if importName == "" {
61 | return ctrl.Result{}, nil
62 | }
63 | var svcImport v1alpha1.ServiceImport
64 | if err := r.Client.Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: importName}, &svcImport); err != nil {
65 | return ctrl.Result{}, err
66 | }
67 |
68 | ipsLen := min(2, len(service.Spec.ClusterIPs))
69 | desiredIPs := service.Spec.ClusterIPs[:ipsLen]
70 | if service.Spec.ClusterIP == v1.ClusterIPNone {
71 | desiredIPs = []string{}
72 | }
73 | if slices.Equal(desiredIPs, svcImport.Spec.IPs) {
74 | return ctrl.Result{}, nil
75 | }
76 |
77 | svcImport.Spec.IPs = desiredIPs
78 | if err := r.Client.Update(ctx, &svcImport); err != nil {
79 | return ctrl.Result{}, err
80 | }
81 | log.Info("updated serviceimport ip", "ip", service.Spec.ClusterIP)
82 | return ctrl.Result{}, nil
83 | }
84 |
85 | // SetupWithManager wires up the controller.
86 | func (r *ServiceReconciler) SetupWithManager(mgr ctrl.Manager) error {
87 | return ctrl.NewControllerManagedBy(mgr).For(&v1.Service{}).Complete(r)
88 | }
89 |
--------------------------------------------------------------------------------
/pkg/client/listers/apis/v1alpha1/serviceexport.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by lister-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | import (
22 | labels "k8s.io/apimachinery/pkg/labels"
23 | listers "k8s.io/client-go/listers"
24 | cache "k8s.io/client-go/tools/cache"
25 | apisv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
26 | )
27 |
28 | // ServiceExportLister helps list ServiceExports.
29 | // All objects returned here must be treated as read-only.
30 | type ServiceExportLister interface {
31 | // List lists all ServiceExports in the indexer.
32 | // Objects returned here must be treated as read-only.
33 | List(selector labels.Selector) (ret []*apisv1alpha1.ServiceExport, err error)
34 | // ServiceExports returns an object that can list and get ServiceExports.
35 | ServiceExports(namespace string) ServiceExportNamespaceLister
36 | ServiceExportListerExpansion
37 | }
38 |
39 | // serviceExportLister implements the ServiceExportLister interface.
40 | type serviceExportLister struct {
41 | listers.ResourceIndexer[*apisv1alpha1.ServiceExport]
42 | }
43 |
44 | // NewServiceExportLister returns a new ServiceExportLister.
45 | func NewServiceExportLister(indexer cache.Indexer) ServiceExportLister {
46 | return &serviceExportLister{listers.New[*apisv1alpha1.ServiceExport](indexer, apisv1alpha1.Resource("serviceexport"))}
47 | }
48 |
49 | // ServiceExports returns an object that can list and get ServiceExports.
50 | func (s *serviceExportLister) ServiceExports(namespace string) ServiceExportNamespaceLister {
51 | return serviceExportNamespaceLister{listers.NewNamespaced[*apisv1alpha1.ServiceExport](s.ResourceIndexer, namespace)}
52 | }
53 |
54 | // ServiceExportNamespaceLister helps list and get ServiceExports.
55 | // All objects returned here must be treated as read-only.
56 | type ServiceExportNamespaceLister interface {
57 | // List lists all ServiceExports in the indexer for a given namespace.
58 | // Objects returned here must be treated as read-only.
59 | List(selector labels.Selector) (ret []*apisv1alpha1.ServiceExport, err error)
60 | // Get retrieves the ServiceExport from the indexer for a given namespace and name.
61 | // Objects returned here must be treated as read-only.
62 | Get(name string) (*apisv1alpha1.ServiceExport, error)
63 | ServiceExportNamespaceListerExpansion
64 | }
65 |
66 | // serviceExportNamespaceLister implements the ServiceExportNamespaceLister
67 | // interface.
68 | type serviceExportNamespaceLister struct {
69 | listers.ResourceIndexer[*apisv1alpha1.ServiceExport]
70 | }
71 |
--------------------------------------------------------------------------------
/pkg/client/listers/apis/v1alpha1/serviceimport.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by lister-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | import (
22 | labels "k8s.io/apimachinery/pkg/labels"
23 | listers "k8s.io/client-go/listers"
24 | cache "k8s.io/client-go/tools/cache"
25 | apisv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
26 | )
27 |
28 | // ServiceImportLister helps list ServiceImports.
29 | // All objects returned here must be treated as read-only.
30 | type ServiceImportLister interface {
31 | // List lists all ServiceImports in the indexer.
32 | // Objects returned here must be treated as read-only.
33 | List(selector labels.Selector) (ret []*apisv1alpha1.ServiceImport, err error)
34 | // ServiceImports returns an object that can list and get ServiceImports.
35 | ServiceImports(namespace string) ServiceImportNamespaceLister
36 | ServiceImportListerExpansion
37 | }
38 |
39 | // serviceImportLister implements the ServiceImportLister interface.
40 | type serviceImportLister struct {
41 | listers.ResourceIndexer[*apisv1alpha1.ServiceImport]
42 | }
43 |
44 | // NewServiceImportLister returns a new ServiceImportLister.
45 | func NewServiceImportLister(indexer cache.Indexer) ServiceImportLister {
46 | return &serviceImportLister{listers.New[*apisv1alpha1.ServiceImport](indexer, apisv1alpha1.Resource("serviceimport"))}
47 | }
48 |
49 | // ServiceImports returns an object that can list and get ServiceImports.
50 | func (s *serviceImportLister) ServiceImports(namespace string) ServiceImportNamespaceLister {
51 | return serviceImportNamespaceLister{listers.NewNamespaced[*apisv1alpha1.ServiceImport](s.ResourceIndexer, namespace)}
52 | }
53 |
54 | // ServiceImportNamespaceLister helps list and get ServiceImports.
55 | // All objects returned here must be treated as read-only.
56 | type ServiceImportNamespaceLister interface {
57 | // List lists all ServiceImports in the indexer for a given namespace.
58 | // Objects returned here must be treated as read-only.
59 | List(selector labels.Selector) (ret []*apisv1alpha1.ServiceImport, err error)
60 | // Get retrieves the ServiceImport from the indexer for a given namespace and name.
61 | // Objects returned here must be treated as read-only.
62 | Get(name string) (*apisv1alpha1.ServiceImport, error)
63 | ServiceImportNamespaceListerExpansion
64 | }
65 |
66 | // serviceImportNamespaceLister implements the ServiceImportNamespaceLister
67 | // interface.
68 | type serviceImportNamespaceLister struct {
69 | listers.ResourceIndexer[*apisv1alpha1.ServiceImport]
70 | }
71 |
--------------------------------------------------------------------------------
/pkg/client/clientset/versioned/typed/apis/v1alpha1/serviceexport.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | import (
22 | context "context"
23 |
24 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25 | types "k8s.io/apimachinery/pkg/types"
26 | watch "k8s.io/apimachinery/pkg/watch"
27 | gentype "k8s.io/client-go/gentype"
28 | apisv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
29 | scheme "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned/scheme"
30 | )
31 |
32 | // ServiceExportsGetter has a method to return a ServiceExportInterface.
33 | // A group's client should implement this interface.
34 | type ServiceExportsGetter interface {
35 | ServiceExports(namespace string) ServiceExportInterface
36 | }
37 |
38 | // ServiceExportInterface has methods to work with ServiceExport resources.
39 | type ServiceExportInterface interface {
40 | Create(ctx context.Context, serviceExport *apisv1alpha1.ServiceExport, opts v1.CreateOptions) (*apisv1alpha1.ServiceExport, error)
41 | Update(ctx context.Context, serviceExport *apisv1alpha1.ServiceExport, opts v1.UpdateOptions) (*apisv1alpha1.ServiceExport, error)
42 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
43 | UpdateStatus(ctx context.Context, serviceExport *apisv1alpha1.ServiceExport, opts v1.UpdateOptions) (*apisv1alpha1.ServiceExport, error)
44 | Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
45 | DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
46 | Get(ctx context.Context, name string, opts v1.GetOptions) (*apisv1alpha1.ServiceExport, error)
47 | List(ctx context.Context, opts v1.ListOptions) (*apisv1alpha1.ServiceExportList, error)
48 | Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
49 | Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *apisv1alpha1.ServiceExport, err error)
50 | ServiceExportExpansion
51 | }
52 |
53 | // serviceExports implements ServiceExportInterface
54 | type serviceExports struct {
55 | *gentype.ClientWithList[*apisv1alpha1.ServiceExport, *apisv1alpha1.ServiceExportList]
56 | }
57 |
58 | // newServiceExports returns a ServiceExports
59 | func newServiceExports(c *MulticlusterV1alpha1Client, namespace string) *serviceExports {
60 | return &serviceExports{
61 | gentype.NewClientWithList[*apisv1alpha1.ServiceExport, *apisv1alpha1.ServiceExportList](
62 | "serviceexports",
63 | c.RESTClient(),
64 | scheme.ParameterCodec,
65 | namespace,
66 | func() *apisv1alpha1.ServiceExport { return &apisv1alpha1.ServiceExport{} },
67 | func() *apisv1alpha1.ServiceExportList { return &apisv1alpha1.ServiceExportList{} },
68 | ),
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/pkg/client/clientset/versioned/typed/apis/v1alpha1/serviceimport.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | import (
22 | context "context"
23 |
24 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25 | types "k8s.io/apimachinery/pkg/types"
26 | watch "k8s.io/apimachinery/pkg/watch"
27 | gentype "k8s.io/client-go/gentype"
28 | apisv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
29 | scheme "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned/scheme"
30 | )
31 |
32 | // ServiceImportsGetter has a method to return a ServiceImportInterface.
33 | // A group's client should implement this interface.
34 | type ServiceImportsGetter interface {
35 | ServiceImports(namespace string) ServiceImportInterface
36 | }
37 |
38 | // ServiceImportInterface has methods to work with ServiceImport resources.
39 | type ServiceImportInterface interface {
40 | Create(ctx context.Context, serviceImport *apisv1alpha1.ServiceImport, opts v1.CreateOptions) (*apisv1alpha1.ServiceImport, error)
41 | Update(ctx context.Context, serviceImport *apisv1alpha1.ServiceImport, opts v1.UpdateOptions) (*apisv1alpha1.ServiceImport, error)
42 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
43 | UpdateStatus(ctx context.Context, serviceImport *apisv1alpha1.ServiceImport, opts v1.UpdateOptions) (*apisv1alpha1.ServiceImport, error)
44 | Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
45 | DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
46 | Get(ctx context.Context, name string, opts v1.GetOptions) (*apisv1alpha1.ServiceImport, error)
47 | List(ctx context.Context, opts v1.ListOptions) (*apisv1alpha1.ServiceImportList, error)
48 | Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
49 | Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *apisv1alpha1.ServiceImport, err error)
50 | ServiceImportExpansion
51 | }
52 |
53 | // serviceImports implements ServiceImportInterface
54 | type serviceImports struct {
55 | *gentype.ClientWithList[*apisv1alpha1.ServiceImport, *apisv1alpha1.ServiceImportList]
56 | }
57 |
58 | // newServiceImports returns a ServiceImports
59 | func newServiceImports(c *MulticlusterV1alpha1Client, namespace string) *serviceImports {
60 | return &serviceImports{
61 | gentype.NewClientWithList[*apisv1alpha1.ServiceImport, *apisv1alpha1.ServiceImportList](
62 | "serviceimports",
63 | c.RESTClient(),
64 | scheme.ParameterCodec,
65 | namespace,
66 | func() *apisv1alpha1.ServiceImport { return &apisv1alpha1.ServiceImport{} },
67 | func() *apisv1alpha1.ServiceImportList { return &apisv1alpha1.ServiceImportList{} },
68 | ),
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/pkg/client/clientset/versioned/fake/clientset_generated.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | package fake
20 |
21 | import (
22 | "k8s.io/apimachinery/pkg/runtime"
23 | "k8s.io/apimachinery/pkg/watch"
24 | "k8s.io/client-go/discovery"
25 | fakediscovery "k8s.io/client-go/discovery/fake"
26 | "k8s.io/client-go/testing"
27 | clientset "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned"
28 | multiclusterv1alpha1 "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned/typed/apis/v1alpha1"
29 | fakemulticlusterv1alpha1 "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake"
30 | )
31 |
32 | // NewSimpleClientset returns a clientset that will respond with the provided objects.
33 | // It's backed by a very simple object tracker that processes creates, updates and deletions as-is,
34 | // without applying any field management, validations and/or defaults. It shouldn't be considered a replacement
35 | // for a real clientset and is mostly useful in simple unit tests.
36 | //
37 | // DEPRECATED: NewClientset replaces this with support for field management, which significantly improves
38 | // server side apply testing. NewClientset is only available when apply configurations are generated (e.g.
39 | // via --with-applyconfig).
40 | func NewSimpleClientset(objects ...runtime.Object) *Clientset {
41 | o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
42 | for _, obj := range objects {
43 | if err := o.Add(obj); err != nil {
44 | panic(err)
45 | }
46 | }
47 |
48 | cs := &Clientset{tracker: o}
49 | cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}
50 | cs.AddReactor("*", "*", testing.ObjectReaction(o))
51 | cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
52 | gvr := action.GetResource()
53 | ns := action.GetNamespace()
54 | watch, err := o.Watch(gvr, ns)
55 | if err != nil {
56 | return false, nil, err
57 | }
58 | return true, watch, nil
59 | })
60 |
61 | return cs
62 | }
63 |
64 | // Clientset implements clientset.Interface. Meant to be embedded into a
65 | // struct to get a default implementation. This makes faking out just the method
66 | // you want to test easier.
67 | type Clientset struct {
68 | testing.Fake
69 | discovery *fakediscovery.FakeDiscovery
70 | tracker testing.ObjectTracker
71 | }
72 |
73 | func (c *Clientset) Discovery() discovery.DiscoveryInterface {
74 | return c.discovery
75 | }
76 |
77 | func (c *Clientset) Tracker() testing.ObjectTracker {
78 | return c.tracker
79 | }
80 |
81 | var (
82 | _ clientset.Interface = &Clientset{}
83 | _ testing.FakeClient = &Clientset{}
84 | )
85 |
86 | // MulticlusterV1alpha1 retrieves the MulticlusterV1alpha1Client
87 | func (c *Clientset) MulticlusterV1alpha1() multiclusterv1alpha1.MulticlusterV1alpha1Interface {
88 | return &fakemulticlusterv1alpha1.FakeMulticlusterV1alpha1{Fake: &c.Fake}
89 | }
90 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2019 The Kubernetes Authors.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | DOCKER ?= docker
16 | # TOP is the current directory where this Makefile lives.
17 | TOP := $(dir $(firstword $(MAKEFILE_LIST)))
18 | # ROOT is the root of the mkdocs tree.
19 | ROOT := $(abspath $(TOP))
20 | # Image URL to use all building/pushing image targets
21 | IMG ?= mcs-api-controller:latest
22 | # Need v1 to support defaults in CRDs, unfortunately limiting us to k8s 1.16+
23 | CRD_OPTIONS ?= "crd:crdVersions=v1"
24 |
25 | CONTROLLER_GEN=go -C tools run sigs.k8s.io/controller-tools/cmd/controller-gen
26 | # enable Go modules
27 | export GO111MODULE=on
28 |
29 | .PHONY: all
30 | all: generate manifests controller verify
31 |
32 | .PHONY: e2e-test
33 | e2e-test: export MCS_CONTROLLER_IMAGE := $(IMG)
34 | e2e-test: docker-build
35 | ./scripts/e2e-test.sh
36 |
37 | .PHONY: demo
38 | demo: export MCS_CONTROLLER_IMAGE := $(IMG)
39 | demo: docker-build
40 | ./scripts/up.sh
41 | ./demo/demo.sh
42 | ./scripts/down.sh
43 |
44 | # Build controller binary
45 | .PHONY: controller
46 | controller: generate fmt vet
47 | go -C controllers build -o $(ROOT)/bin/manager cmd/servicecontroller/servicecontroller.go
48 |
49 | # Run go fmt against code
50 | .PHONY: fmt
51 | fmt:
52 | for m in . conformance controllers e2e; do go -C $$m fmt ./...; done
53 |
54 | # Run go vet against code
55 | .PHONY: vet
56 | vet:
57 | for m in . conformance controllers e2e; do go -C $$m vet ./...; done
58 |
59 | # Run generators for Deepcopy funcs and CRDs
60 | .PHONY: generate
61 | generate:
62 | ./hack/update-codegen.sh
63 | $(CONTROLLER_GEN) object:headerFile=$(ROOT)/hack/boilerplate.go.txt paths="$(ROOT)/..."
64 |
65 | # Generate manifests e.g. CRD, RBAC etc.
66 | .PHONY: manifests
67 | manifests:
68 | $(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"
69 |
70 | # Run tests
71 | .PHONY: test
72 | test: generate fmt vet manifests
73 | for m in . controllers; do go -C $$m test ./... -coverprofile cover.out; done
74 |
75 | # Install CRD's and example resources to a pre-existing cluster.
76 | .PHONY: install
77 | install: manifests crd
78 |
79 | # Remove installed CRD's and CR's.
80 | .PHONY: uninstall
81 | uninstall:
82 | ./hack/delete-crds.sh
83 |
84 | # Run static analysis.
85 | .PHONY: verify
86 | verify:
87 | ./hack/verify-all.sh -v
88 |
89 | # Build docker containers
90 | .PHONY: docker-build
91 | docker-build: generate fmt vet manifests
92 | docker build . -t ${IMG}
93 |
94 | # Push the docker image
95 | .PHONY: docker-push
96 | docker-push: docker-build
97 | docker push ${IMG}
98 |
99 | # Run against the configured Kubernetes cluster in ~/.kube/config
100 | run: generate fmt vet manifests
101 | go run ./cmd/servicecontroller/servicecontroller.go
102 |
--------------------------------------------------------------------------------
/controllers/controllers_suite_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package controllers
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "math/rand"
23 | "path/filepath"
24 | "testing"
25 | "time"
26 |
27 | . "github.com/onsi/ginkgo/v2"
28 | . "github.com/onsi/gomega"
29 | v1 "k8s.io/api/core/v1"
30 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 | "k8s.io/apimachinery/pkg/runtime"
32 | clientgoscheme "k8s.io/client-go/kubernetes/scheme"
33 | "k8s.io/client-go/rest"
34 | "k8s.io/client-go/tools/clientcmd"
35 | ctrl "sigs.k8s.io/controller-runtime"
36 | "sigs.k8s.io/controller-runtime/pkg/client"
37 | "sigs.k8s.io/controller-runtime/pkg/envtest"
38 | "sigs.k8s.io/controller-runtime/pkg/log"
39 | "sigs.k8s.io/controller-runtime/pkg/log/zap"
40 | "sigs.k8s.io/kind/pkg/cluster"
41 | "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
42 | )
43 |
44 | const (
45 | clusterName = "test-cluster"
46 | )
47 |
48 | var (
49 | cfg *rest.Config
50 | k8s client.Client
51 | env *envtest.Environment
52 | clusterProvider *cluster.Provider
53 | testNS string
54 | )
55 |
56 | var _ = BeforeSuite(func(done Done) {
57 | rand.Seed(GinkgoRandomSeed())
58 | log.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)))
59 | // Use Kind for a more up-to-date K8s
60 | clusterProvider = cluster.NewProvider()
61 | Expect(clusterProvider.Create(clusterName)).To(Succeed())
62 | kubeconfig, err := clusterProvider.KubeConfig(clusterName, false)
63 | Expect(err).ToNot(HaveOccurred())
64 |
65 | cfg, err := clientcmd.RESTConfigFromKubeConfig([]byte(kubeconfig))
66 | Expect(err).ToNot(HaveOccurred())
67 | scheme := runtime.NewScheme()
68 | Expect(clientgoscheme.AddToScheme(scheme)).To(Succeed())
69 | Expect(v1alpha1.AddToScheme(scheme)).To(Succeed())
70 | Expect(err).ToNot(HaveOccurred())
71 | existingCluster := true
72 | env = &envtest.Environment{
73 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd")},
74 | UseExistingCluster: &existingCluster,
75 | Config: cfg,
76 | }
77 | cfg, err = env.Start()
78 | Expect(err).ToNot(HaveOccurred())
79 | Expect(cfg).ToNot(BeNil())
80 |
81 | k8s, err = client.New(cfg, client.Options{Scheme: scheme})
82 | Expect(err).ToNot(HaveOccurred())
83 | Expect(k8s).ToNot(BeNil())
84 |
85 | testNS = fmt.Sprintf("test-%v", time.Now().Unix())
86 | Expect(k8s.Create(context.Background(), &v1.Namespace{
87 | ObjectMeta: metav1.ObjectMeta{
88 | Name: testNS,
89 | },
90 | })).To(Succeed())
91 |
92 | opts := ctrl.Options{
93 | Scheme: scheme,
94 | }
95 |
96 | go Start(context.TODO(), cfg, log.Log, opts)
97 | close(done)
98 | })
99 |
100 | var _ = AfterSuite(func() {
101 | Expect(clusterProvider.Delete(clusterName, "")).To(Succeed())
102 | err := env.Stop()
103 | Expect(err).ToNot(HaveOccurred())
104 | })
105 |
106 | func TestControllers(t *testing.T) {
107 | RegisterFailHandler(Fail)
108 | }
109 |
--------------------------------------------------------------------------------
/controllers/go.mod:
--------------------------------------------------------------------------------
1 | module sigs.k8s.io/mcs-api/controllers
2 |
3 | go 1.23.0
4 |
5 | require (
6 | github.com/go-logr/logr v1.4.2
7 | github.com/onsi/ginkgo/v2 v2.22.0
8 | github.com/onsi/gomega v1.36.1
9 | k8s.io/api v0.32.5
10 | k8s.io/apimachinery v0.32.5
11 | k8s.io/client-go v0.32.5
12 | sigs.k8s.io/controller-runtime v0.20.4
13 | sigs.k8s.io/kind v0.23.0
14 | sigs.k8s.io/mcs-api v0.3.0
15 | )
16 |
17 | replace sigs.k8s.io/mcs-api => ..
18 |
19 | require (
20 | github.com/BurntSushi/toml v1.0.0 // indirect
21 | github.com/alessio/shellescape v1.4.1 // indirect
22 | github.com/beorn7/perks v1.0.1 // indirect
23 | github.com/cespare/xxhash/v2 v2.3.0 // indirect
24 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
25 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect
26 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect
27 | github.com/fsnotify/fsnotify v1.7.0 // indirect
28 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect
29 | github.com/go-logr/zapr v1.3.0 // indirect
30 | github.com/go-openapi/jsonpointer v0.21.0 // indirect
31 | github.com/go-openapi/jsonreference v0.20.2 // indirect
32 | github.com/go-openapi/swag v0.23.0 // indirect
33 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
34 | github.com/gogo/protobuf v1.3.2 // indirect
35 | github.com/golang/protobuf v1.5.4 // indirect
36 | github.com/google/btree v1.1.3 // indirect
37 | github.com/google/gnostic-models v0.6.8 // indirect
38 | github.com/google/go-cmp v0.6.0 // indirect
39 | github.com/google/gofuzz v1.2.0 // indirect
40 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
41 | github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect
42 | github.com/google/uuid v1.6.0 // indirect
43 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
44 | github.com/josharian/intern v1.0.0 // indirect
45 | github.com/json-iterator/go v1.1.12 // indirect
46 | github.com/mailru/easyjson v0.7.7 // indirect
47 | github.com/mattn/go-isatty v0.0.20 // indirect
48 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
49 | github.com/modern-go/reflect2 v1.0.2 // indirect
50 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
51 | github.com/pelletier/go-toml v1.9.4 // indirect
52 | github.com/pkg/errors v0.9.1 // indirect
53 | github.com/prometheus/client_golang v1.19.1 // indirect
54 | github.com/prometheus/client_model v0.6.1 // indirect
55 | github.com/prometheus/common v0.55.0 // indirect
56 | github.com/prometheus/procfs v0.15.1 // indirect
57 | github.com/spf13/cobra v1.8.1 // indirect
58 | github.com/spf13/pflag v1.0.5 // indirect
59 | github.com/x448/float16 v0.8.4 // indirect
60 | go.uber.org/multierr v1.11.0 // indirect
61 | go.uber.org/zap v1.27.0 // indirect
62 | golang.org/x/net v0.30.0 // indirect
63 | golang.org/x/oauth2 v0.23.0 // indirect
64 | golang.org/x/sync v0.8.0 // indirect
65 | golang.org/x/sys v0.26.0 // indirect
66 | golang.org/x/term v0.25.0 // indirect
67 | golang.org/x/text v0.19.0 // indirect
68 | golang.org/x/time v0.7.0 // indirect
69 | golang.org/x/tools v0.26.0 // indirect
70 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
71 | google.golang.org/protobuf v1.35.1 // indirect
72 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
73 | gopkg.in/inf.v0 v0.9.1 // indirect
74 | gopkg.in/yaml.v3 v3.0.1 // indirect
75 | k8s.io/apiextensions-apiserver v0.32.1 // indirect
76 | k8s.io/klog/v2 v2.130.1 // indirect
77 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
78 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
79 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
80 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
81 | sigs.k8s.io/yaml v1.4.0 // indirect
82 | )
83 |
--------------------------------------------------------------------------------
/pkg/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | import (
22 | http "net/http"
23 |
24 | rest "k8s.io/client-go/rest"
25 | apisv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
26 | scheme "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned/scheme"
27 | )
28 |
29 | type MulticlusterV1alpha1Interface interface {
30 | RESTClient() rest.Interface
31 | ServiceExportsGetter
32 | ServiceImportsGetter
33 | }
34 |
35 | // MulticlusterV1alpha1Client is used to interact with features provided by the multicluster.x-k8s.io group.
36 | type MulticlusterV1alpha1Client struct {
37 | restClient rest.Interface
38 | }
39 |
40 | func (c *MulticlusterV1alpha1Client) ServiceExports(namespace string) ServiceExportInterface {
41 | return newServiceExports(c, namespace)
42 | }
43 |
44 | func (c *MulticlusterV1alpha1Client) ServiceImports(namespace string) ServiceImportInterface {
45 | return newServiceImports(c, namespace)
46 | }
47 |
48 | // NewForConfig creates a new MulticlusterV1alpha1Client for the given config.
49 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
50 | // where httpClient was generated with rest.HTTPClientFor(c).
51 | func NewForConfig(c *rest.Config) (*MulticlusterV1alpha1Client, error) {
52 | config := *c
53 | if err := setConfigDefaults(&config); err != nil {
54 | return nil, err
55 | }
56 | httpClient, err := rest.HTTPClientFor(&config)
57 | if err != nil {
58 | return nil, err
59 | }
60 | return NewForConfigAndClient(&config, httpClient)
61 | }
62 |
63 | // NewForConfigAndClient creates a new MulticlusterV1alpha1Client for the given config and http client.
64 | // Note the http client provided takes precedence over the configured transport values.
65 | func NewForConfigAndClient(c *rest.Config, h *http.Client) (*MulticlusterV1alpha1Client, error) {
66 | config := *c
67 | if err := setConfigDefaults(&config); err != nil {
68 | return nil, err
69 | }
70 | client, err := rest.RESTClientForConfigAndClient(&config, h)
71 | if err != nil {
72 | return nil, err
73 | }
74 | return &MulticlusterV1alpha1Client{client}, nil
75 | }
76 |
77 | // NewForConfigOrDie creates a new MulticlusterV1alpha1Client for the given config and
78 | // panics if there is an error in the config.
79 | func NewForConfigOrDie(c *rest.Config) *MulticlusterV1alpha1Client {
80 | client, err := NewForConfig(c)
81 | if err != nil {
82 | panic(err)
83 | }
84 | return client
85 | }
86 |
87 | // New creates a new MulticlusterV1alpha1Client for the given RESTClient.
88 | func New(c rest.Interface) *MulticlusterV1alpha1Client {
89 | return &MulticlusterV1alpha1Client{c}
90 | }
91 |
92 | func setConfigDefaults(config *rest.Config) error {
93 | gv := apisv1alpha1.SchemeGroupVersion
94 | config.GroupVersion = &gv
95 | config.APIPath = "/apis"
96 | config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()
97 |
98 | if config.UserAgent == "" {
99 | config.UserAgent = rest.DefaultKubernetesUserAgent()
100 | }
101 |
102 | return nil
103 | }
104 |
105 | // RESTClient returns a RESTClient that is used to communicate
106 | // with API server by this client implementation.
107 | func (c *MulticlusterV1alpha1Client) RESTClient() rest.Interface {
108 | if c == nil {
109 | return nil
110 | }
111 | return c.restClient
112 | }
113 |
--------------------------------------------------------------------------------
/pkg/client/informers/externalversions/apis/v1alpha1/serviceexport.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by informer-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | import (
22 | context "context"
23 | time "time"
24 |
25 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 | runtime "k8s.io/apimachinery/pkg/runtime"
27 | watch "k8s.io/apimachinery/pkg/watch"
28 | cache "k8s.io/client-go/tools/cache"
29 | pkgapisv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
30 | versioned "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned"
31 | internalinterfaces "sigs.k8s.io/mcs-api/pkg/client/informers/externalversions/internalinterfaces"
32 | apisv1alpha1 "sigs.k8s.io/mcs-api/pkg/client/listers/apis/v1alpha1"
33 | )
34 |
35 | // ServiceExportInformer provides access to a shared informer and lister for
36 | // ServiceExports.
37 | type ServiceExportInformer interface {
38 | Informer() cache.SharedIndexInformer
39 | Lister() apisv1alpha1.ServiceExportLister
40 | }
41 |
42 | type serviceExportInformer struct {
43 | factory internalinterfaces.SharedInformerFactory
44 | tweakListOptions internalinterfaces.TweakListOptionsFunc
45 | namespace string
46 | }
47 |
48 | // NewServiceExportInformer constructs a new informer for ServiceExport type.
49 | // Always prefer using an informer factory to get a shared informer instead of getting an independent
50 | // one. This reduces memory footprint and number of connections to the server.
51 | func NewServiceExportInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
52 | return NewFilteredServiceExportInformer(client, namespace, resyncPeriod, indexers, nil)
53 | }
54 |
55 | // NewFilteredServiceExportInformer constructs a new informer for ServiceExport type.
56 | // Always prefer using an informer factory to get a shared informer instead of getting an independent
57 | // one. This reduces memory footprint and number of connections to the server.
58 | func NewFilteredServiceExportInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
59 | return cache.NewSharedIndexInformer(
60 | &cache.ListWatch{
61 | ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
62 | if tweakListOptions != nil {
63 | tweakListOptions(&options)
64 | }
65 | return client.MulticlusterV1alpha1().ServiceExports(namespace).List(context.TODO(), options)
66 | },
67 | WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
68 | if tweakListOptions != nil {
69 | tweakListOptions(&options)
70 | }
71 | return client.MulticlusterV1alpha1().ServiceExports(namespace).Watch(context.TODO(), options)
72 | },
73 | },
74 | &pkgapisv1alpha1.ServiceExport{},
75 | resyncPeriod,
76 | indexers,
77 | )
78 | }
79 |
80 | func (f *serviceExportInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
81 | return NewFilteredServiceExportInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
82 | }
83 |
84 | func (f *serviceExportInformer) Informer() cache.SharedIndexInformer {
85 | return f.factory.InformerFor(&pkgapisv1alpha1.ServiceExport{}, f.defaultInformer)
86 | }
87 |
88 | func (f *serviceExportInformer) Lister() apisv1alpha1.ServiceExportLister {
89 | return apisv1alpha1.NewServiceExportLister(f.Informer().GetIndexer())
90 | }
91 |
--------------------------------------------------------------------------------
/pkg/client/informers/externalversions/apis/v1alpha1/serviceimport.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by informer-gen. DO NOT EDIT.
18 |
19 | package v1alpha1
20 |
21 | import (
22 | context "context"
23 | time "time"
24 |
25 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 | runtime "k8s.io/apimachinery/pkg/runtime"
27 | watch "k8s.io/apimachinery/pkg/watch"
28 | cache "k8s.io/client-go/tools/cache"
29 | pkgapisv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
30 | versioned "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned"
31 | internalinterfaces "sigs.k8s.io/mcs-api/pkg/client/informers/externalversions/internalinterfaces"
32 | apisv1alpha1 "sigs.k8s.io/mcs-api/pkg/client/listers/apis/v1alpha1"
33 | )
34 |
35 | // ServiceImportInformer provides access to a shared informer and lister for
36 | // ServiceImports.
37 | type ServiceImportInformer interface {
38 | Informer() cache.SharedIndexInformer
39 | Lister() apisv1alpha1.ServiceImportLister
40 | }
41 |
42 | type serviceImportInformer struct {
43 | factory internalinterfaces.SharedInformerFactory
44 | tweakListOptions internalinterfaces.TweakListOptionsFunc
45 | namespace string
46 | }
47 |
48 | // NewServiceImportInformer constructs a new informer for ServiceImport type.
49 | // Always prefer using an informer factory to get a shared informer instead of getting an independent
50 | // one. This reduces memory footprint and number of connections to the server.
51 | func NewServiceImportInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
52 | return NewFilteredServiceImportInformer(client, namespace, resyncPeriod, indexers, nil)
53 | }
54 |
55 | // NewFilteredServiceImportInformer constructs a new informer for ServiceImport type.
56 | // Always prefer using an informer factory to get a shared informer instead of getting an independent
57 | // one. This reduces memory footprint and number of connections to the server.
58 | func NewFilteredServiceImportInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
59 | return cache.NewSharedIndexInformer(
60 | &cache.ListWatch{
61 | ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
62 | if tweakListOptions != nil {
63 | tweakListOptions(&options)
64 | }
65 | return client.MulticlusterV1alpha1().ServiceImports(namespace).List(context.TODO(), options)
66 | },
67 | WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
68 | if tweakListOptions != nil {
69 | tweakListOptions(&options)
70 | }
71 | return client.MulticlusterV1alpha1().ServiceImports(namespace).Watch(context.TODO(), options)
72 | },
73 | },
74 | &pkgapisv1alpha1.ServiceImport{},
75 | resyncPeriod,
76 | indexers,
77 | )
78 | }
79 |
80 | func (f *serviceImportInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
81 | return NewFilteredServiceImportInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
82 | }
83 |
84 | func (f *serviceImportInformer) Informer() cache.SharedIndexInformer {
85 | return f.factory.InformerFor(&pkgapisv1alpha1.ServiceImport{}, f.defaultInformer)
86 | }
87 |
88 | func (f *serviceImportInformer) Lister() apisv1alpha1.ServiceImportLister {
89 | return apisv1alpha1.NewServiceImportLister(f.Informer().GetIndexer())
90 | }
91 |
--------------------------------------------------------------------------------
/scripts/up.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2020 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | cd $(dirname ${BASH_SOURCE})
18 |
19 | . ./util.sh
20 |
21 | set -e
22 |
23 | kind() {
24 | go -C ../controllers run sigs.k8s.io/kind "$@"
25 | }
26 |
27 | c1=${CLUSTER1:-c1}
28 | c2=${CLUSTER2:-c2}
29 | kubeconfig1=${KUBECONFIG1:-"$PWD/${c1}.kubeconfig"}
30 | kubeconfig2=${KUBECONFIG2:-"$PWD/${c2}.kubeconfig"}
31 | controller_image=${MCS_CONTROLLER_IMAGE:-"mcs-api-controller"}
32 |
33 | k1="kubectl --kubeconfig ${kubeconfig1}"
34 | k2="kubectl --kubeconfig ${kubeconfig2}"
35 |
36 | if [ ! -z "${BUILD_CONTROLLER}" ] || [ -z "$(docker images mcs-api-controller -q)" ]; then
37 | pushd ../
38 | make docker-build
39 | popd
40 | fi
41 |
42 | kind create cluster --name "${c1}" --config "$PWD/${c1}.yaml"
43 | kind create cluster --name "${c2}" --config "$PWD/${c2}.yaml"
44 |
45 | kind get kubeconfig --name "${c1}" > "${kubeconfig1}"
46 | kind get kubeconfig --name "${c2}" > "${kubeconfig2}"
47 |
48 | kind load docker-image "${controller_image}" --name "${c1}"
49 | kind load docker-image "${controller_image}" --name "${c2}"
50 |
51 | echo "Configuring CoreDNS"
52 | function update_coredns() {
53 | kubectl --kubeconfig ${1} apply -f ../config/crd
54 | kubectl --kubeconfig ${1} patch clusterrole system:coredns --type json --patch-file coredns-rbac.json
55 | # Patching Corefile based on Cilium documentation: https://docs.cilium.io/en/latest/network/clustermesh/mcsapi/#prerequisites
56 | kubectl --kubeconfig ${1} get configmap -n kube-system coredns -o yaml | \
57 | sed -e 's/cluster\.local/cluster.local clusterset.local/g' | \
58 | sed -E 's/^(.*)kubernetes(.*)\{/\1kubernetes\2{\n\1 multicluster clusterset.local/' | \
59 | kubectl --kubeconfig ${1} replace -f-
60 | kubectl --kubeconfig ${1} rollout restart deploy -n kube-system coredns
61 | }
62 | update_coredns ${kubeconfig1}
63 | update_coredns ${kubeconfig2}
64 |
65 | function pod_cidrs() {
66 | kubectl --kubeconfig "${1}" get nodes -o jsonpath='{range .items[*]}{.spec.podCIDR}{"\n"}'
67 | }
68 |
69 | function add_routes() {
70 | unset IFS
71 | routes=$(kubectl --kubeconfig ${3} get node ${2} -o jsonpath='ip route add {.spec.podCIDR} via {.status.addresses[?(.type=="InternalIP")].address}')
72 | echo "Connecting cluster ${1} to ${2}"
73 |
74 | IFS=$'\n'
75 | for n in $(kind get nodes --name "${1}"); do
76 | for r in $routes; do
77 | eval "docker exec $n $r"
78 | done
79 | done
80 | unset IFS
81 | }
82 |
83 | waitfor pod_cidrs ${kubeconfig1}
84 | waitfor pod_cidrs ${kubeconfig2}
85 |
86 | echo "Connecting cluster networks..."
87 | add_routes "${c1}" "${c2}-control-plane" "${kubeconfig2}"
88 | add_routes "${c2}" "${c1}-control-plane" "${kubeconfig1}"
89 | echo "Cluster networks connected"
90 |
91 | ${k1} apply -f ../config/rbac
92 | ${k2} apply -f ../config/rbac
93 |
94 | ${k1} create sa mcs-api-controller
95 | ${k1} create clusterrolebinding mcs-api-binding --clusterrole=mcs-derived-service-manager --serviceaccount=default:mcs-api-controller
96 | ${k1} run --image "${controller_image}" --image-pull-policy=Never mcs-api-controller --overrides='{ "spec": { "serviceAccount": "mcs-api-controller" } }'
97 |
98 | ${k2} create sa mcs-api-controller
99 | ${k2} create clusterrolebinding mcs-api-binding --clusterrole=mcs-derived-service-manager --serviceaccount=default:mcs-api-controller
100 | ${k2} run --image "${controller_image}" --image-pull-policy=Never mcs-api-controller --overrides='{ "spec": { "serviceAccount": "mcs-api-controller" } }'
101 |
--------------------------------------------------------------------------------
/demo/demo.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2020 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | # Resolve the absolute path to this script directory.
18 | cd $(dirname "${BASH_SOURCE[0]}")
19 | demo_dir=$(realpath "$(pwd)")
20 | scripts_dir=$(realpath "$(pwd)/../scripts")
21 |
22 | . "${demo_dir}/udemo.sh"
23 | . "${scripts_dir}/util.sh"
24 |
25 | DEMO_AUTO_RUN=true
26 |
27 | kubeconfig1="${KUBECONFIG1:-${scripts_dir}/c1.kubeconfig}"
28 | kubeconfig2="${KUBECONFIG2:-${scripts_dir}/c2.kubeconfig}"
29 |
30 | k1="kubectl --kubeconfig ${kubeconfig1}"
31 | k2="kubectl --kubeconfig ${kubeconfig2}"
32 |
33 | desc "Setup our demo namespace"
34 | run "${k1} create ns demo"
35 | run "${k2} create ns demo"
36 |
37 | c1_pane=$(tmux split-window -h -d -P)
38 |
39 | function cleanup() {
40 | tmux kill-pane -t "$c1_pane"
41 | }
42 | trap cleanup EXIT
43 |
44 | tmux send -t "$c1_pane" "${k1} logs -f mcs-api-controller" Enter
45 |
46 | desc "Create our service in each cluster"
47 | run "${k1} apply -f ${demo_dir}/yaml/dep1.yaml -f ${demo_dir}/yaml/svc.yaml"
48 | run "${k2} apply -f ${demo_dir}/yaml/dep2.yaml -f ${demo_dir}/yaml/svc.yaml"
49 | run "${k1} get endpointslice -n demo"
50 |
51 | desc "Lets look at some requests to the service in cluster 1"
52 | run "${k1} -n demo run -i --rm --restart=Never --image=jeremyot/request:0a40de8 request -- --duration=5s --address=serve.demo.svc.cluster.local"
53 |
54 | desc "Ok, looks normal. Let's import the service from our other cluster"
55 | ep_1=$(${k1} get endpointslice -n demo -l 'kubernetes.io/service-name=serve' --template="{{(index .items 0).metadata.name}}")
56 | ep_2=$(${k2} get endpointslice -n demo -l 'kubernetes.io/service-name=serve' --template="{{(index .items 0).metadata.name}}")
57 |
58 | run "${k1} get endpointslice -n demo ${ep_1} -o yaml | ${demo_dir}/edit-meta --metadata '{name: imported-${ep_1}, namespace: demo, labels: {multicluster.kubernetes.io/service-name: serve}}' > ${demo_dir}/yaml/slice-1.tmp"
59 | run "${k2} get endpointslice -n demo ${ep_2} -o yaml | ${demo_dir}/edit-meta --metadata '{name: imported-${ep_2}, namespace: demo, labels: {multicluster.kubernetes.io/service-name: serve}}' > ${demo_dir}/yaml/slice-2.tmp"
60 | run "${k1} apply -f ${demo_dir}/yaml/serviceimport.yaml -f ${demo_dir}/yaml/slice-1.tmp -f ${demo_dir}/yaml/slice-2.tmp"
61 | run "${k2} apply -f ${demo_dir}/yaml/serviceimport.yaml -f ${demo_dir}/yaml/slice-1.tmp -f ${demo_dir}/yaml/slice-2.tmp"
62 | run "${k1} apply -f ${demo_dir}/yaml/serviceimport-with-vip.yaml -f ${demo_dir}/yaml/slice-1.tmp -f ${demo_dir}/yaml/slice-2.tmp"
63 | run "${k2} apply -f ${demo_dir}/yaml/serviceimport-with-vip.yaml -f ${demo_dir}/yaml/slice-1.tmp -f ${demo_dir}/yaml/slice-2.tmp"
64 |
65 | desc "See what we've created..."
66 | run "${k1} get -n demo serviceimports"
67 | run "${k1} get -n demo endpointslice"
68 | run "${k1} get -n demo service"
69 |
70 | function import_ip() {
71 | ${k1} get serviceimport -n demo -o go-template --template='{{index (index .items 0).spec.ips 0}}'
72 | }
73 |
74 | waitfor import_ip
75 |
76 | vip=$(${k1} get serviceimport -n demo -o go-template --template='{{index (index .items 0).spec.ips 0}}')
77 | desc "Now grab the multi-cluster VIP from the serviceimport..."
78 | run "${k1} get serviceimport -n demo -o go-template --template='{{index (index .items 0).spec.ips 0}}{{\"\n\"}}'"
79 | desc "...and connect to it"
80 | run "${k1} -n demo run -i --rm --restart=Never --image=jeremyot/request:0a40de8 request -- --duration=10s --address=${vip}"
81 | run "${k1} -n demo run -i --rm --restart=Never --image=jeremyot/request:0a40de8 request -- --duration=10s --address=serve.demo.svc.clusterset.local"
82 | desc "We have a multi-cluster service!"
83 | desc "See for yourself"
84 | desc "Cluster 1: kubectl --kubeconfig ${kubeconfig1} -n demo"
85 | desc "Cluster 2: kubectl --kubeconfig ${kubeconfig2} -n demo"
86 | desc "(Enter to exit)"
87 | read -s
88 |
--------------------------------------------------------------------------------
/pkg/client/clientset/versioned/clientset.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | // Code generated by client-gen. DO NOT EDIT.
18 |
19 | package versioned
20 |
21 | import (
22 | fmt "fmt"
23 | http "net/http"
24 |
25 | discovery "k8s.io/client-go/discovery"
26 | rest "k8s.io/client-go/rest"
27 | flowcontrol "k8s.io/client-go/util/flowcontrol"
28 | multiclusterv1alpha1 "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned/typed/apis/v1alpha1"
29 | )
30 |
31 | type Interface interface {
32 | Discovery() discovery.DiscoveryInterface
33 | MulticlusterV1alpha1() multiclusterv1alpha1.MulticlusterV1alpha1Interface
34 | }
35 |
36 | // Clientset contains the clients for groups.
37 | type Clientset struct {
38 | *discovery.DiscoveryClient
39 | multiclusterV1alpha1 *multiclusterv1alpha1.MulticlusterV1alpha1Client
40 | }
41 |
42 | // MulticlusterV1alpha1 retrieves the MulticlusterV1alpha1Client
43 | func (c *Clientset) MulticlusterV1alpha1() multiclusterv1alpha1.MulticlusterV1alpha1Interface {
44 | return c.multiclusterV1alpha1
45 | }
46 |
47 | // Discovery retrieves the DiscoveryClient
48 | func (c *Clientset) Discovery() discovery.DiscoveryInterface {
49 | if c == nil {
50 | return nil
51 | }
52 | return c.DiscoveryClient
53 | }
54 |
55 | // NewForConfig creates a new Clientset for the given config.
56 | // If config's RateLimiter is not set and QPS and Burst are acceptable,
57 | // NewForConfig will generate a rate-limiter in configShallowCopy.
58 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
59 | // where httpClient was generated with rest.HTTPClientFor(c).
60 | func NewForConfig(c *rest.Config) (*Clientset, error) {
61 | configShallowCopy := *c
62 |
63 | if configShallowCopy.UserAgent == "" {
64 | configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent()
65 | }
66 |
67 | // share the transport between all clients
68 | httpClient, err := rest.HTTPClientFor(&configShallowCopy)
69 | if err != nil {
70 | return nil, err
71 | }
72 |
73 | return NewForConfigAndClient(&configShallowCopy, httpClient)
74 | }
75 |
76 | // NewForConfigAndClient creates a new Clientset for the given config and http client.
77 | // Note the http client provided takes precedence over the configured transport values.
78 | // If config's RateLimiter is not set and QPS and Burst are acceptable,
79 | // NewForConfigAndClient will generate a rate-limiter in configShallowCopy.
80 | func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) {
81 | configShallowCopy := *c
82 | if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
83 | if configShallowCopy.Burst <= 0 {
84 | return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0")
85 | }
86 | configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
87 | }
88 |
89 | var cs Clientset
90 | var err error
91 | cs.multiclusterV1alpha1, err = multiclusterv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)
92 | if err != nil {
93 | return nil, err
94 | }
95 |
96 | cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient)
97 | if err != nil {
98 | return nil, err
99 | }
100 | return &cs, nil
101 | }
102 |
103 | // NewForConfigOrDie creates a new Clientset for the given config and
104 | // panics if there is an error in the config.
105 | func NewForConfigOrDie(c *rest.Config) *Clientset {
106 | cs, err := NewForConfig(c)
107 | if err != nil {
108 | panic(err)
109 | }
110 | return cs
111 | }
112 |
113 | // New creates a new Clientset for the given RESTClient.
114 | func New(c rest.Interface) *Clientset {
115 | var cs Clientset
116 | cs.multiclusterV1alpha1 = multiclusterv1alpha1.New(c)
117 |
118 | cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
119 | return &cs
120 | }
121 |
--------------------------------------------------------------------------------
/controllers/endpointslice_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package controllers
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "math/rand"
23 | "time"
24 |
25 | . "github.com/onsi/ginkgo/v2"
26 | . "github.com/onsi/gomega"
27 | discoveryv1 "k8s.io/api/discovery/v1"
28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 | "k8s.io/apimachinery/pkg/types"
30 | "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
31 | )
32 |
33 | var _ = Describe("EndpointSlice", func() {
34 | ctx := context.Background()
35 | Context("should be ignored", func() {
36 | Specify("when not multi-cluster", func() {
37 | Expect(shouldIgnoreEndpointSlice(&discoveryv1.EndpointSlice{
38 | ObjectMeta: metav1.ObjectMeta{
39 | Namespace: testNS,
40 | Name: "no-mc-service",
41 | },
42 | AddressType: discoveryv1.AddressTypeIPv4,
43 | })).To(BeTrue())
44 | })
45 | Specify("when deleted", func() {
46 | Expect(shouldIgnoreEndpointSlice(&discoveryv1.EndpointSlice{
47 | ObjectMeta: metav1.ObjectMeta{
48 | Namespace: testNS,
49 | Name: "deleted",
50 | DeletionTimestamp: &metav1.Time{Time: time.Now()},
51 | },
52 | AddressType: discoveryv1.AddressTypeIPv4,
53 | })).To(BeTrue())
54 | })
55 | })
56 | Context("created with mc label", func() {
57 | var (
58 | serviceName types.NamespacedName
59 | derivedServiceName types.NamespacedName
60 | sliceName types.NamespacedName
61 | epSlice discoveryv1.EndpointSlice
62 | )
63 | BeforeEach(func() {
64 | serviceName = types.NamespacedName{Namespace: testNS, Name: fmt.Sprintf("svc-%v", rand.Uint64())}
65 | derivedServiceName = types.NamespacedName{Namespace: testNS, Name: derivedName(serviceName)}
66 | sliceName = types.NamespacedName{Namespace: testNS, Name: fmt.Sprintf("slice-%v", rand.Uint64())}
67 | epSlice = discoveryv1.EndpointSlice{
68 | ObjectMeta: metav1.ObjectMeta{
69 | Namespace: testNS,
70 | Name: sliceName.Name,
71 | Labels: map[string]string{
72 | v1alpha1.LabelServiceName: serviceName.Name,
73 | },
74 | },
75 | AddressType: discoveryv1.AddressTypeIPv4,
76 | }
77 | Expect(k8s.Create(ctx, &epSlice)).To(Succeed())
78 | })
79 | It("has correct label", func() {
80 | Eventually(func() string {
81 | var eps discoveryv1.EndpointSlice
82 | Expect(k8s.Get(ctx, sliceName, &eps)).Should(Succeed())
83 | return eps.Labels[discoveryv1.LabelServiceName]
84 | }).Should(Equal(derivedServiceName.Name))
85 | })
86 | })
87 | Context("created with wrong label", func() {
88 | var (
89 | serviceName types.NamespacedName
90 | derivedServiceName types.NamespacedName
91 | sliceName types.NamespacedName
92 | epSlice discoveryv1.EndpointSlice
93 | )
94 | BeforeEach(func() {
95 | serviceName = types.NamespacedName{Namespace: testNS, Name: fmt.Sprintf("svc-%v", rand.Uint64())}
96 | derivedServiceName = types.NamespacedName{Namespace: testNS, Name: derivedName(serviceName)}
97 | sliceName = types.NamespacedName{Namespace: testNS, Name: fmt.Sprintf("slice-%v", rand.Uint64())}
98 | epSlice = discoveryv1.EndpointSlice{
99 | ObjectMeta: metav1.ObjectMeta{
100 | Namespace: testNS,
101 | Name: sliceName.Name,
102 | Labels: map[string]string{
103 | v1alpha1.LabelServiceName: serviceName.Name,
104 | discoveryv1.LabelServiceName: serviceName.Name,
105 | },
106 | },
107 | AddressType: discoveryv1.AddressTypeIPv4,
108 | }
109 | Expect(k8s.Create(ctx, &epSlice)).To(Succeed())
110 | })
111 | It("has correct label", func() {
112 | Eventually(func() string {
113 | var eps discoveryv1.EndpointSlice
114 | Expect(k8s.Get(ctx, sliceName, &eps)).Should(Succeed())
115 | return eps.Labels[discoveryv1.LabelServiceName]
116 | }).Should(Equal(derivedServiceName.Name))
117 | })
118 | })
119 | })
120 |
--------------------------------------------------------------------------------
/conformance/connectivity.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2023 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package conformance
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "strings"
23 |
24 | . "github.com/onsi/ginkgo/v2"
25 | . "github.com/onsi/gomega"
26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 | )
28 |
29 | var _ = Describe("", func() {
30 | t := newTestDriver()
31 |
32 | BeforeEach(func() {
33 | t.autoExportService = false
34 | })
35 |
36 | Context("Connectivity to a service that is not exported", func() {
37 | It("should be inaccessible", Label(RequiredLabel), func() {
38 | AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#exporting-services")
39 | By("attempting to access the remote service", func() {
40 | By("issuing a request from all clusters", func() {
41 | command := []string{"sh", "-c", fmt.Sprintf("echo hi | nc %s.%s.svc.clusterset.local 42",
42 | t.helloService.Name, t.namespace)}
43 |
44 | // Run on all clusters
45 | for _, client := range clients {
46 | // Repeat multiple times
47 | for i := 0; i < 20; i++ {
48 | Expect(t.execCmdOnRequestPod(&client, command)).NotTo(ContainSubstring("pod ip"), reportNonConformant(""))
49 | }
50 | }
51 | })
52 | })
53 | })
54 | })
55 |
56 | Context("Connectivity to an exported ClusterIP service", func() {
57 | It("should be accessible through DNS", Label(OptionalLabel, ConnectivityLabel, ClusterIPLabel), func() {
58 | AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#dns")
59 | By("Exporting the service", func() {
60 | // On the "remote" cluster
61 | t.createServiceExport(&clients[0], newHelloServiceExport())
62 | })
63 | By("Issuing a request from all clusters", func() {
64 | // Run on all clusters
65 | command := []string{"sh", "-c", fmt.Sprintf("echo hi | nc %s.%s.svc.clusterset.local 42",
66 | t.helloService.Name, t.namespace)}
67 |
68 | for _, client := range clients {
69 | By(fmt.Sprintf("Executing command %q on cluster %q", strings.Join(command, " "), client.name))
70 |
71 | t.awaitCmdOutputMatches(&client, command, "pod ip", 1, reportNonConformant(""))
72 | }
73 | })
74 | })
75 | })
76 |
77 | Context("Connectivity to a ClusterIP service existing in two clusters but exported from one", func() {
78 | BeforeEach(func() {
79 | requireTwoClusters()
80 | })
81 |
82 | JustBeforeEach(func() {
83 | t.deployHelloService(&clients[1], newHelloService())
84 | })
85 |
86 | It("should only access the exporting cluster", Label(OptionalLabel, ConnectivityLabel, ClusterIPLabel), func() {
87 | AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/blob/master/keps/sig-multicluster/1645-multi-cluster-services-api/README.md#exporting-services")
88 |
89 | By(fmt.Sprintf("Exporting the service on cluster %q", clients[0].name))
90 |
91 | t.createServiceExport(&clients[0], newHelloServiceExport())
92 |
93 | By(fmt.Sprintf("Awaiting service deployment pod IP on cluster %q", clients[0].name))
94 |
95 | servicePodIP := ""
96 |
97 | Eventually(func() string {
98 | pods, err := clients[0].k8s.CoreV1().Pods(t.namespace).List(context.TODO(), metav1.ListOptions{
99 | LabelSelector: metav1.FormatLabelSelector(newHelloDeployment().Spec.Selector),
100 | })
101 | Expect(err).ToNot(HaveOccurred())
102 |
103 | if len(pods.Items) > 0 {
104 | servicePodIP = pods.Items[0].Status.PodIP
105 | }
106 |
107 | return servicePodIP
108 | }, 20, 1).ShouldNot(BeEmpty(), "Service deployment pod was not allocated an IP")
109 |
110 | By(fmt.Sprintf("Retrieved service deployment pod IP %q", servicePodIP))
111 |
112 | command := []string{"sh", "-c", fmt.Sprintf("echo hi | nc %s.%s.svc.clusterset.local 42",
113 | t.helloService.Name, t.namespace)}
114 |
115 | for _, client := range clients {
116 | By(fmt.Sprintf("Executing command %q on cluster %q", strings.Join(command, " "), client.name))
117 |
118 | t.awaitCmdOutputMatches(&client, command, servicePodIP, 10, reportNonConformant(""))
119 | }
120 | })
121 | })
122 | })
123 |
--------------------------------------------------------------------------------
/conformance/endpoint_slice.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package conformance
18 |
19 | import (
20 | "fmt"
21 | "time"
22 |
23 | . "github.com/onsi/ginkgo/v2"
24 | . "github.com/onsi/gomega"
25 | discoveryv1 "k8s.io/api/discovery/v1"
26 | apierrors "k8s.io/apimachinery/pkg/api/errors"
27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 | "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
29 | )
30 |
31 | // K8sEndpointSliceManagedByName is the name used for endpoint slices managed by the Kubernetes controller
32 | const K8sEndpointSliceManagedByName = "endpointslice-controller.k8s.io"
33 |
34 | var _ = Describe("", Label(OptionalLabel, EndpointSliceLabel), func() {
35 | t := newTestDriver()
36 |
37 | Specify("Exporting a service should create an MCS EndpointSlice in the service's namespace in each cluster with the "+
38 | "required MCS labels. Unexporting should delete the EndpointSlice.", func() {
39 | AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#using-endpointslice-objects-to-track-endpoints")
40 |
41 | endpointSlices := make([]*discoveryv1.EndpointSlice, len(clients))
42 |
43 | for i, client := range clients {
44 | eps := t.awaitMCSEndpointSlice(&client, discoveryv1.AddressTypeIPv4, nil, reportNonConformant(fmt.Sprintf(
45 | "an MCS EndpointSlice was not found on cluster %q. An MCS EndpointSlice is identified by the presence "+
46 | "of the required MCS labels (%q and %q). "+
47 | "If the MCS implementation does not use MCS EndpointSlices, you can specify a Ginkgo label filter using "+
48 | "the %q label where appropriate to skip this test.",
49 | client.name, v1alpha1.LabelServiceName, v1alpha1.LabelSourceCluster, EndpointSliceLabel)))
50 |
51 | endpointSlices[i] = eps
52 |
53 | Expect(eps.Labels).To(HaveKeyWithValue(v1alpha1.LabelServiceName, t.helloService.Name),
54 | reportNonConformant(fmt.Sprintf("the MCS EndpointSlice %q does not contain the %q label referencing the service name",
55 | eps.Name, v1alpha1.LabelServiceName)))
56 |
57 | Expect(eps.Labels).To(HaveKey(discoveryv1.LabelManagedBy),
58 | reportNonConformant(fmt.Sprintf("the MCS EndpointSlice %q does not contain the %q label",
59 | eps.Name, discoveryv1.LabelManagedBy)))
60 |
61 | if !skipVerifyEndpointSliceManagedBy {
62 | Expect(eps.Labels[discoveryv1.LabelManagedBy]).ToNot(Equal(K8sEndpointSliceManagedByName),
63 | reportNonConformant(fmt.Sprintf("the MCS EndpointSlice's %q label must not reference %q",
64 | discoveryv1.LabelManagedBy, K8sEndpointSliceManagedByName)))
65 | }
66 | }
67 |
68 | t.deleteServiceExport(&clients[0])
69 |
70 | for i, client := range clients {
71 | Eventually(func() bool {
72 | _, err := client.k8s.DiscoveryV1().EndpointSlices(t.namespace).Get(ctx, endpointSlices[i].Name, metav1.GetOptions{})
73 | return apierrors.IsNotFound(err)
74 | }, 20*time.Second, 100*time.Millisecond).Should(BeTrue(),
75 | reportNonConformant(fmt.Sprintf("the EndpointSlice was not deleted on unexport from cluster %d", i+1)))
76 | }
77 | })
78 | })
79 |
80 | func (t *testDriver) awaitMCSEndpointSlice(c *clusterClients, addressType discoveryv1.AddressType,
81 | verify func(Gomega, *discoveryv1.EndpointSlice), desc ...any) *discoveryv1.EndpointSlice {
82 | var endpointSlice *discoveryv1.EndpointSlice
83 |
84 | hasLabel := func(eps *discoveryv1.EndpointSlice, label string) bool {
85 | _, exists := eps.Labels[label]
86 | return exists
87 | }
88 |
89 | Eventually(func(g Gomega) {
90 | list, err := c.k8s.DiscoveryV1().EndpointSlices(t.namespace).List(ctx, metav1.ListOptions{})
91 | g.Expect(err).ToNot(HaveOccurred(), "Error retrieving EndpointSlices")
92 |
93 | endpointSlice = nil
94 |
95 | for i := range list.Items {
96 | eps := &list.Items[i]
97 |
98 | if hasLabel(eps, v1alpha1.LabelServiceName) && hasLabel(eps, v1alpha1.LabelSourceCluster) && eps.AddressType == addressType {
99 | endpointSlice = eps
100 |
101 | if verify != nil {
102 | verify(g, endpointSlice)
103 | }
104 | }
105 | }
106 |
107 | g.Expect(endpointSlice).ToNot(BeNil(), desc...)
108 |
109 | // The final run succeeded so cancel any prior non-conformance reported.
110 | cancelNonConformanceReport()
111 | }).Within(20 * time.Second).ProbeEvery(100 * time.Millisecond).Should(Succeed())
112 |
113 | return endpointSlice
114 | }
115 |
--------------------------------------------------------------------------------
/conformance/k8s_objects.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2023 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package conformance
18 |
19 | import (
20 | appsv1 "k8s.io/api/apps/v1"
21 | corev1 "k8s.io/api/core/v1"
22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23 | "k8s.io/utils/ptr"
24 | "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
25 | )
26 |
27 | const helloServiceName = "hello"
28 |
29 | func newHelloService() *corev1.Service {
30 | return &corev1.Service{
31 | ObjectMeta: metav1.ObjectMeta{
32 | Name: helloServiceName,
33 | Annotations: map[string]string{"dummy-svc": "dummy"},
34 | Labels: map[string]string{"dummy-svc": "dummy"},
35 | },
36 | Spec: corev1.ServiceSpec{
37 | Selector: map[string]string{
38 | "app": helloServiceName,
39 | },
40 | Ports: []corev1.ServicePort{
41 | {
42 | Name: "tcp",
43 | Port: 42,
44 | Protocol: corev1.ProtocolTCP,
45 | },
46 | {
47 | Name: "udp",
48 | Port: 42,
49 | Protocol: corev1.ProtocolUDP,
50 | },
51 | },
52 | SessionAffinity: corev1.ServiceAffinityClientIP,
53 | SessionAffinityConfig: &corev1.SessionAffinityConfig{
54 | ClientIP: &corev1.ClientIPConfig{TimeoutSeconds: ptr.To(int32(10))},
55 | },
56 | },
57 | }
58 | }
59 |
60 | func newHelloServiceExport() *v1alpha1.ServiceExport {
61 | return &v1alpha1.ServiceExport{
62 | ObjectMeta: metav1.ObjectMeta{
63 | Name: helloServiceName,
64 | Annotations: map[string]string{"dummy-svcexport": "dummy"},
65 | Labels: map[string]string{"dummy-svcexport": "dummy"},
66 | },
67 | }
68 | }
69 |
70 | func podContainers() []corev1.Container {
71 | return []corev1.Container{
72 | {
73 | Name: "hello-tcp",
74 | Image: "alpine/socat:1.7.4.4",
75 | Args: []string{"-v", "-v", "TCP-LISTEN:42,crlf,reuseaddr,fork", "SYSTEM:echo pod ip $(MY_POD_IP)"},
76 | Env: []corev1.EnvVar{
77 | {
78 | Name: "MY_POD_IP",
79 | ValueFrom: &corev1.EnvVarSource{
80 | FieldRef: &corev1.ObjectFieldSelector{
81 | FieldPath: "status.podIP",
82 | },
83 | },
84 | },
85 | },
86 | },
87 | {
88 | Name: "hello-udp",
89 | Image: "alpine/socat:1.7.4.4",
90 | Args: []string{"-v", "-v", "UDP-LISTEN:42,crlf,reuseaddr,fork", "SYSTEM:echo pod ip $(MY_POD_IP)"},
91 | Env: []corev1.EnvVar{
92 | {
93 | Name: "MY_POD_IP",
94 | ValueFrom: &corev1.EnvVarSource{
95 | FieldRef: &corev1.ObjectFieldSelector{
96 | FieldPath: "status.podIP",
97 | },
98 | },
99 | },
100 | },
101 | },
102 | }
103 | }
104 |
105 | func newHelloDeployment() *appsv1.Deployment {
106 | return &appsv1.Deployment{
107 | ObjectMeta: metav1.ObjectMeta{
108 | Name: helloServiceName,
109 | },
110 | Spec: appsv1.DeploymentSpec{
111 | Replicas: ptr.To(int32(1)),
112 | Selector: &metav1.LabelSelector{
113 | MatchLabels: map[string]string{
114 | "app": helloServiceName,
115 | },
116 | },
117 | Template: corev1.PodTemplateSpec{
118 | ObjectMeta: metav1.ObjectMeta{
119 | Labels: map[string]string{"app": helloServiceName},
120 | },
121 | Spec: corev1.PodSpec{
122 | Containers: podContainers(),
123 | },
124 | },
125 | },
126 | }
127 | }
128 |
129 | func newStatefulSet(replicas int) *appsv1.StatefulSet {
130 | return &appsv1.StatefulSet{
131 | ObjectMeta: metav1.ObjectMeta{
132 | Name: helloServiceName + "-ss",
133 | },
134 | Spec: appsv1.StatefulSetSpec{
135 | Selector: &metav1.LabelSelector{
136 | MatchLabels: map[string]string{
137 | "app": helloServiceName,
138 | },
139 | },
140 | ServiceName: helloServiceName,
141 | Replicas: ptr.To(int32(replicas)),
142 | Template: corev1.PodTemplateSpec{
143 | ObjectMeta: metav1.ObjectMeta{
144 | Labels: map[string]string{
145 | "app": helloServiceName,
146 | },
147 | },
148 | Spec: corev1.PodSpec{
149 | Containers: podContainers(),
150 | RestartPolicy: corev1.RestartPolicyAlways,
151 | },
152 | },
153 | },
154 | }
155 | }
156 |
157 | func newRequestPod() *corev1.Pod {
158 | return &corev1.Pod{
159 | ObjectMeta: metav1.ObjectMeta{
160 | Name: "request",
161 | Labels: map[string]string{"app": "request"},
162 | },
163 | Spec: corev1.PodSpec{
164 | Containers: []corev1.Container{
165 | {
166 | Name: "request",
167 | Image: "busybox",
168 | Args: []string{"/bin/sh", "-ec", "while :; do echo '.'; sleep 5 ; done"},
169 | },
170 | },
171 | },
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/controllers/serviceimport.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package controllers
18 |
19 | import (
20 | "context"
21 |
22 | "github.com/go-logr/logr"
23 | v1 "k8s.io/api/core/v1"
24 | apierrors "k8s.io/apimachinery/pkg/api/errors"
25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 | "k8s.io/apimachinery/pkg/types"
27 | ctrl "sigs.k8s.io/controller-runtime"
28 | "sigs.k8s.io/controller-runtime/pkg/client"
29 | "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
30 | )
31 |
32 | // ServiceImportReconciler reconciles a ServiceImport object
33 | type ServiceImportReconciler struct {
34 | client.Client
35 | Log logr.Logger
36 | }
37 |
38 | // +kubebuilder:rbac:groups=multicluster.x-k8s.io,resources=serviceimports,verbs=get;list;watch;update;patch
39 |
40 | func servicePorts(svcImport *v1alpha1.ServiceImport) []v1.ServicePort {
41 | ports := make([]v1.ServicePort, len(svcImport.Spec.Ports))
42 | for i, p := range svcImport.Spec.Ports {
43 | ports[i] = v1.ServicePort{
44 | Name: p.Name,
45 | Protocol: p.Protocol,
46 | Port: p.Port,
47 | AppProtocol: p.AppProtocol,
48 | }
49 | }
50 | return ports
51 | }
52 |
53 | func shouldIgnoreImport(svcImport *v1alpha1.ServiceImport) bool {
54 | if svcImport.DeletionTimestamp != nil {
55 | return true
56 | }
57 | if svcImport.Spec.Type != v1alpha1.ClusterSetIP {
58 | return true
59 | }
60 | return false
61 | }
62 |
63 | // +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch
64 | // +kubebuilder:rbac:groups=core,resources=services/status,verbs=get;list;watch;create;update;patch
65 |
66 | // Reconcile the changes.
67 | func (r *ServiceImportReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
68 | serviceName := derivedName(req.NamespacedName)
69 | log := r.Log.WithValues("serviceimport", req.NamespacedName, "derived", serviceName)
70 | var svcImport v1alpha1.ServiceImport
71 | if err := r.Client.Get(ctx, req.NamespacedName, &svcImport); err != nil {
72 | return ctrl.Result{}, client.IgnoreNotFound(err)
73 | }
74 | if shouldIgnoreImport(&svcImport) {
75 | return ctrl.Result{}, nil
76 | }
77 |
78 | // Ensure the existence of the derived service
79 | var svc v1.Service
80 | if svcImport.Annotations[DerivedServiceAnnotation] == "" {
81 | if svcImport.Annotations == nil {
82 | svcImport.Annotations = map[string]string{}
83 | }
84 | svcImport.Annotations[DerivedServiceAnnotation] = derivedName(req.NamespacedName)
85 | if err := r.Client.Update(ctx, &svcImport); err != nil {
86 | return ctrl.Result{}, err
87 | }
88 | log.Info("added annotation", DerivedServiceAnnotation, svcImport.Annotations[DerivedServiceAnnotation])
89 | return ctrl.Result{}, nil
90 | }
91 | if err := r.Client.Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: svcImport.Annotations[DerivedServiceAnnotation]}, &svc); err == nil {
92 | return ctrl.Result{}, nil
93 | } else if !apierrors.IsNotFound(err) {
94 | return ctrl.Result{}, err
95 | }
96 |
97 | svc = v1.Service{
98 | ObjectMeta: metav1.ObjectMeta{
99 | Namespace: req.Namespace,
100 | Name: svcImport.Annotations[DerivedServiceAnnotation],
101 | OwnerReferences: []metav1.OwnerReference{
102 | {
103 | Name: req.Name,
104 | Kind: serviceImportKind,
105 | APIVersion: v1alpha1.GroupVersion.String(),
106 | UID: svcImport.UID,
107 | },
108 | },
109 | },
110 | Spec: v1.ServiceSpec{
111 | Type: v1.ServiceTypeClusterIP,
112 | Ports: servicePorts(&svcImport),
113 | },
114 | }
115 | if err := r.Client.Create(ctx, &svc); err != nil {
116 | return ctrl.Result{}, err
117 | }
118 | log.Info("created service")
119 |
120 | if len(svcImport.Spec.IPs) == 0 {
121 | return ctrl.Result{}, nil
122 | }
123 |
124 | // update loadbalanacer status with provided clustersetIPs
125 | ingress := []v1.LoadBalancerIngress{}
126 | for _, ip := range svcImport.Spec.IPs {
127 | ingress = append(ingress, v1.LoadBalancerIngress{
128 | IP: ip,
129 | })
130 | }
131 |
132 | svc.Status = v1.ServiceStatus{
133 | LoadBalancer: v1.LoadBalancerStatus{
134 | Ingress: ingress,
135 | },
136 | }
137 |
138 | if err := r.Client.Status().Update(ctx, &svc); err != nil {
139 | return ctrl.Result{}, err
140 | }
141 |
142 | return ctrl.Result{}, nil
143 | }
144 |
145 | // SetupWithManager wires up the controller.
146 | func (r *ServiceImportReconciler) SetupWithManager(mgr ctrl.Manager) error {
147 | return ctrl.NewControllerManagedBy(mgr).For(&v1alpha1.ServiceImport{}).Complete(r)
148 | }
149 |
--------------------------------------------------------------------------------
/e2e/e2e_suite_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package e2etest
18 |
19 | import (
20 | "bytes"
21 | "context"
22 | "flag"
23 | "math/rand"
24 | "os"
25 | "strconv"
26 | "testing"
27 |
28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 | "k8s.io/apimachinery/pkg/labels"
30 | "k8s.io/client-go/kubernetes/scheme"
31 | restclient "k8s.io/client-go/rest"
32 | "k8s.io/client-go/tools/remotecommand"
33 |
34 | . "github.com/onsi/ginkgo/v2"
35 | . "github.com/onsi/gomega"
36 | v1 "k8s.io/api/core/v1"
37 | discoveryv1 "k8s.io/api/discovery/v1"
38 | "k8s.io/client-go/kubernetes"
39 | "k8s.io/client-go/tools/clientcmd"
40 | "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
41 | mcsclient "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned"
42 | )
43 |
44 | var (
45 | kubeconfig1 = flag.String("kubeconfig1", os.Getenv("KUBECONFIG1"), "The path to a kubeconfig for cluster 1")
46 | kubeconfig2 = flag.String("kubeconfig2", os.Getenv("KUBECONFIG2"), "The path to a kubeconfig for cluster 2")
47 | noTearDown = flag.Bool("no-tear-down", tryParseBool(os.Getenv("NO_TEAR_DOWN")), "Don't tear down after test (useful for debugging failures).")
48 | cluster1 clusterClients
49 | cluster2 clusterClients
50 | restcfg1, _ = clientcmd.BuildConfigFromFlags("", *kubeconfig1)
51 | //restcfg2, _ = clientcmd.BuildConfigFromFlags("", *kubeconfig2)
52 | )
53 |
54 | func tryParseBool(s string) bool {
55 | b, _ := strconv.ParseBool(s)
56 | return b
57 | }
58 |
59 | type clusterClients struct {
60 | k8s kubernetes.Interface
61 | mcs mcsclient.Interface
62 | }
63 |
64 | func TestE2E(t *testing.T) {
65 | flag.Parse()
66 | RegisterFailHandler(Fail)
67 | RunSpecs(t, "E2E Suite")
68 | }
69 |
70 | var _ = BeforeSuite(func() {
71 | rand.Seed(GinkgoRandomSeed())
72 |
73 | Expect(*kubeconfig1).ToNot(BeEmpty(), "either --kubeconfig1 or KUBECONFIG1 must be set")
74 | Expect(*kubeconfig2).ToNot(BeEmpty(), "either --kubeconfig2 or KUBECONFIG2 must be set")
75 |
76 | restcfg1, err := clientcmd.BuildConfigFromFlags("", *kubeconfig1)
77 | Expect(err).ToNot(HaveOccurred())
78 | restcfg2, err := clientcmd.BuildConfigFromFlags("", *kubeconfig2)
79 | Expect(err).ToNot(HaveOccurred())
80 |
81 | cluster1 = clusterClients{
82 | k8s: kubernetes.NewForConfigOrDie(restcfg1),
83 | mcs: mcsclient.NewForConfigOrDie(restcfg1),
84 | }
85 | cluster2 = clusterClients{
86 | k8s: kubernetes.NewForConfigOrDie(restcfg2),
87 | mcs: mcsclient.NewForConfigOrDie(restcfg2),
88 | }
89 | })
90 |
91 | func execCmd(k8s kubernetes.Interface, config *restclient.Config, podName string, podNamespace string, command []string) ([]byte, []byte, error) {
92 | req := k8s.CoreV1().RESTClient().Post().Resource("pods").Name(podName).Namespace(podNamespace).SubResource("exec")
93 | req.VersionedParams(&v1.PodExecOptions{
94 | Command: command,
95 | Stdin: false,
96 | Stdout: true,
97 | Stderr: true,
98 | TTY: true,
99 | }, scheme.ParameterCodec)
100 | exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
101 | if err != nil {
102 | return []byte{}, []byte{}, err
103 | }
104 | var stdout, stderr bytes.Buffer
105 | err = exec.Stream(remotecommand.StreamOptions{
106 | Stdin: nil,
107 | Stdout: &stdout,
108 | Stderr: &stderr,
109 | })
110 | if err != nil {
111 | return []byte{}, []byte{}, err
112 | }
113 | return stdout.Bytes(), stderr.Bytes(), nil
114 | }
115 |
116 | func exportService(ctx context.Context, fromCluster, toCluster clusterClients, namespace string, svcName string) {
117 | _, err := fromCluster.mcs.MulticlusterV1alpha1().ServiceExports(namespace).Create(ctx, &v1alpha1.ServiceExport{
118 | ObjectMeta: metav1.ObjectMeta{
119 | Name: svcName,
120 | },
121 | }, metav1.CreateOptions{})
122 | Expect(err).ToNot(HaveOccurred())
123 | var slices *discoveryv1.EndpointSliceList
124 | Eventually(func() int {
125 | eps := 0
126 | slices, err = fromCluster.k8s.DiscoveryV1().EndpointSlices(namespace).List(ctx, metav1.ListOptions{
127 | LabelSelector: labels.Set{discoveryv1.LabelServiceName: svcName}.AsSelector().String(),
128 | })
129 | Expect(err).ToNot(HaveOccurred())
130 | for _, s := range slices.Items {
131 | eps += len(s.Endpoints)
132 | }
133 | return eps
134 | }, 30).Should(Equal(1))
135 | importedSlice := slices.Items[0] // This direct indexing is ok because we just asserted above that there is exactly one element here
136 | importedSlice.ObjectMeta = metav1.ObjectMeta{
137 | GenerateName: svcName + "-",
138 | Labels: map[string]string{
139 | v1alpha1.LabelServiceName: svcName,
140 | },
141 | }
142 |
143 | createdSlice, err := toCluster.k8s.DiscoveryV1().EndpointSlices(namespace).Create(ctx, &importedSlice, metav1.CreateOptions{})
144 | Expect(err).ToNot(HaveOccurred())
145 |
146 | Eventually(func() string {
147 | updatedSlice, err := toCluster.k8s.DiscoveryV1().EndpointSlices(namespace).Get(ctx, createdSlice.Name, metav1.GetOptions{})
148 | Expect(err).ToNot(HaveOccurred())
149 | return updatedSlice.Labels[discoveryv1.LabelServiceName]
150 | }).ShouldNot(BeEmpty())
151 | }
152 |
--------------------------------------------------------------------------------
/conformance/clusterip_service_dns.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2023 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package conformance
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "regexp"
23 | "strconv"
24 | "strings"
25 |
26 | . "github.com/onsi/ginkgo/v2"
27 | . "github.com/onsi/gomega"
28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 | "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
30 | )
31 |
32 | var _ = Describe("", Label(OptionalLabel, DNSLabel, ClusterIPLabel), func() {
33 | t := newTestDriver()
34 |
35 | Specify("A DNS lookup of the ..svc.clusterset.local domain for a ClusterIP service should resolve to the "+
36 | "clusterset IP", func() {
37 | AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#dns")
38 |
39 | By("Retrieving ServiceImport")
40 |
41 | serviceImports := []*v1alpha1.ServiceImport{}
42 | for _, client := range clients {
43 | serviceImport := t.awaitServiceImport(&client, t.helloService.Name, false,
44 | func(g Gomega, serviceImport *v1alpha1.ServiceImport) {
45 | g.Expect(serviceImport.Spec.IPs).ToNot(BeEmpty(), "ServiceImport on cluster %q does not contain an IP", client.name)
46 | })
47 | serviceImports = append(serviceImports, serviceImport)
48 | }
49 |
50 | command := []string{"sh", "-c", fmt.Sprintf("nslookup %s.%s.svc.clusterset.local", t.helloService.Name, t.namespace)}
51 | for i, client := range clients {
52 | clusterSetIP := serviceImports[i].Spec.IPs[0]
53 | By(fmt.Sprintf("Found ServiceImport on cluster %q with clusterset IP %q", client.name, clusterSetIP))
54 | By(fmt.Sprintf("Executing command %q on cluster %q", strings.Join(command, " "), client.name))
55 |
56 | t.awaitCmdOutputMatches(&client, command, clusterSetIP, 1, reportNonConformant(""))
57 | }
58 | })
59 |
60 | Specify("A DNS SRV query of the ..svc.clusterset.local domain for a ClusterIP service should return valid SRV "+
61 | "records", func() {
62 | AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#dns")
63 |
64 | domainName := fmt.Sprintf("%s.%s.svc.clusterset.local", t.helloService.Name, t.namespace)
65 |
66 | for _, client := range clients {
67 | srvRecs := t.expectSRVRecords(&client, domainName)
68 |
69 | expSRVRecs := []srvRecord{{
70 | port: t.helloService.Spec.Ports[0].Port,
71 | domainName: domainName,
72 | }}
73 |
74 | Expect(srvRecs).To(Equal(expSRVRecs), reportNonConformant(
75 | fmt.Sprintf("Received SRV records %v do not match the expected records %v", srvRecs, expSRVRecs)))
76 | }
77 | })
78 |
79 | Specify("DNS lookups of the ..svc.cluster.local domain for a ClusterIP service should only resolve "+
80 | "local services", func() {
81 | AddReportEntry(SpecRefReportEntry, "https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/1645-multi-cluster-services-api#dns")
82 |
83 | By(fmt.Sprintf("Retrieving local Service on cluster %q", clients[0].name))
84 |
85 | var resolvedIP string
86 |
87 | Eventually(func() string {
88 | svc, err := clients[0].k8s.CoreV1().Services(t.namespace).Get(context.TODO(), t.helloService.Name, metav1.GetOptions{})
89 | Expect(err).ToNot(HaveOccurred(), "Error retrieving the local Service")
90 |
91 | resolvedIP = svc.Spec.ClusterIP
92 |
93 | return resolvedIP
94 | }, 20, 1).ShouldNot(BeEmpty(), "The service was not assigned a cluster IP")
95 |
96 | By(fmt.Sprintf("Found local Service cluster IP %q", resolvedIP))
97 |
98 | command := []string{"sh", "-c", fmt.Sprintf("nslookup %s.%s.svc.cluster.local", t.helloService.Name, t.namespace)}
99 |
100 | By(fmt.Sprintf("Executing command %q on cluster %q", strings.Join(command, " "), clients[0].name))
101 |
102 | t.awaitCmdOutputMatches(&clients[0], command, resolvedIP, 1, reportNonConformant(""))
103 | })
104 | })
105 |
106 | func (t *testDriver) expectSRVRecords(c *clusterClients, domainName string) []srvRecord {
107 | command := []string{"sh", "-c", "nslookup -type=SRV " + domainName}
108 |
109 | By(fmt.Sprintf("Executing command %q on cluster %q", strings.Join(command, " "), c.name))
110 |
111 | var srvRecs []srvRecord
112 |
113 | Eventually(func() []srvRecord {
114 | srvRecs = parseSRVRecords(t.execCmdOnRequestPod(c, command))
115 | return srvRecs
116 | }, 20, 1).ShouldNot(BeEmpty(), reportNonConformant(""))
117 |
118 | return srvRecs
119 | }
120 |
121 | // Match SRV records from nslookup of the form:
122 | //
123 | // hello.mcs-conformance-1686874467.svc.clusterset.local service = 0 50 42 hello.mcs-conformance-1686874467.svc.clusterset.local
124 | //
125 | // to extract the port and target domain name (the last two tokens)
126 | var srvRecordRegEx = regexp.MustCompile(`.*=\s*\d*\s*\d*\s*(\d*)\s*([a-zA-Z0-9-.]*)`)
127 |
128 | type srvRecord struct {
129 | port int32
130 | domainName string
131 | }
132 |
133 | func (s srvRecord) String() string {
134 | return fmt.Sprintf("port:%d, domainName:%q", s.port, s.domainName)
135 | }
136 |
137 | func parseSRVRecords(str string) []srvRecord {
138 | var recs []srvRecord
139 |
140 | matches := srvRecordRegEx.FindAllStringSubmatch(str, -1)
141 | for i := range matches {
142 | // First match at index 0 is the full text that was matched; index 1 is the port and index 2 is the domain name.
143 | port, _ := strconv.ParseInt(matches[i][1], 10, 32)
144 | recs = append(recs, srvRecord{
145 | port: int32(port),
146 | domainName: matches[i][2],
147 | })
148 | }
149 |
150 | return recs
151 | }
152 |
--------------------------------------------------------------------------------
/controllers/serviceimport_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2020 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package controllers
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "math/rand"
23 | "time"
24 |
25 | . "github.com/onsi/ginkgo/v2"
26 | . "github.com/onsi/gomega"
27 | v1 "k8s.io/api/core/v1"
28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 | "k8s.io/apimachinery/pkg/types"
30 | "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
31 | )
32 |
33 | var _ = Describe("ServiceImport", func() {
34 | var (
35 | serviceImport v1alpha1.ServiceImport
36 | serviceName types.NamespacedName
37 | derivedServiceName types.NamespacedName
38 | )
39 | ctx := context.Background()
40 | Context("should be ignored", func() {
41 | Specify("when headless", func() {
42 | Expect(shouldIgnoreImport(&v1alpha1.ServiceImport{
43 | ObjectMeta: metav1.ObjectMeta{
44 | Namespace: testNS,
45 | Name: "headless",
46 | },
47 | Spec: v1alpha1.ServiceImportSpec{
48 | Type: v1alpha1.Headless,
49 | Ports: []v1alpha1.ServicePort{
50 | {Port: 80},
51 | },
52 | },
53 | })).To(BeTrue())
54 | })
55 | Specify("when deleted", func() {
56 | Expect(shouldIgnoreImport(&v1alpha1.ServiceImport{
57 | ObjectMeta: metav1.ObjectMeta{
58 | Namespace: testNS,
59 | Name: "deleted",
60 | DeletionTimestamp: &metav1.Time{Time: time.Now()},
61 | },
62 | Spec: v1alpha1.ServiceImportSpec{
63 | Type: v1alpha1.ClusterSetIP,
64 | Ports: []v1alpha1.ServicePort{
65 | {Port: 80},
66 | },
67 | },
68 | })).To(BeTrue())
69 | })
70 | })
71 | Context("created", func() {
72 | BeforeEach(func() {
73 | serviceName = types.NamespacedName{Namespace: testNS, Name: fmt.Sprintf("svc-%v", rand.Uint64())}
74 | derivedServiceName = types.NamespacedName{Namespace: testNS, Name: derivedName(serviceName)}
75 | serviceImport = v1alpha1.ServiceImport{
76 | ObjectMeta: metav1.ObjectMeta{
77 | Namespace: testNS,
78 | Name: serviceName.Name,
79 | },
80 | Spec: v1alpha1.ServiceImportSpec{
81 | Type: v1alpha1.ClusterSetIP,
82 | Ports: []v1alpha1.ServicePort{
83 | {Port: 80},
84 | },
85 | },
86 | }
87 | Expect(k8s.Create(ctx, &serviceImport)).To(Succeed())
88 | })
89 | It("has derived service annotation", func() {
90 | Eventually(func() string {
91 | var s v1alpha1.ServiceImport
92 | Expect(k8s.Get(ctx, serviceName, &s)).To(Succeed())
93 | return s.Annotations[DerivedServiceAnnotation]
94 | }, 10).Should(Equal(derivedName(serviceName)))
95 | }, 10)
96 | It("has derived service IP", func() {
97 | var s v1alpha1.ServiceImport
98 | Eventually(func() string {
99 | Expect(k8s.Get(ctx, serviceName, &s)).To(Succeed())
100 | if len(s.Spec.IPs) > 0 {
101 | return s.Spec.IPs[0]
102 | }
103 | return ""
104 | }, 10).ShouldNot(BeEmpty())
105 | }, 15)
106 | It("created derived service", func() {
107 | var s v1.Service
108 | Eventually(func() error {
109 | return k8s.Get(ctx, derivedServiceName, &s)
110 | }, 10).Should(Succeed())
111 | Expect(len(s.OwnerReferences)).To(Equal(1))
112 | Expect(s.OwnerReferences[0].UID).To(Equal(serviceImport.UID))
113 | }, 15)
114 | It("removes derived service", func() {
115 | var s v1.Service
116 | Eventually(func() error {
117 | return k8s.Get(ctx, derivedServiceName, &s)
118 | }, 10).Should(Succeed())
119 | var imp v1alpha1.ServiceImport
120 | Expect(k8s.Get(ctx, serviceName, &imp)).To(Succeed())
121 | Expect(k8s.Delete(ctx, &imp)).To(Succeed())
122 | Eventually(func() error {
123 | return k8s.Get(ctx, derivedServiceName, &s)
124 | }, 15).ShouldNot(Succeed())
125 | }, 15)
126 | })
127 | Context("created with IP", func() {
128 | BeforeEach(func() {
129 | serviceName = types.NamespacedName{Namespace: testNS, Name: fmt.Sprintf("svc-%v", rand.Uint64())}
130 | derivedServiceName = types.NamespacedName{Namespace: testNS, Name: derivedName(serviceName)}
131 | serviceImport = v1alpha1.ServiceImport{
132 | ObjectMeta: metav1.ObjectMeta{
133 | Namespace: testNS,
134 | Name: serviceName.Name,
135 | },
136 | Spec: v1alpha1.ServiceImportSpec{
137 | Type: v1alpha1.ClusterSetIP,
138 | Ports: []v1alpha1.ServicePort{
139 | {Port: 80},
140 | },
141 | },
142 | }
143 | Expect(k8s.Create(ctx, &serviceImport)).To(Succeed())
144 | })
145 | It("updates derived service IP", func() {
146 | var svcImport v1alpha1.ServiceImport
147 | var s v1.Service
148 | Eventually(func() error {
149 | return k8s.Get(ctx, derivedServiceName, &s)
150 | }, 10).Should(Succeed())
151 | Eventually(func() string {
152 | Expect(k8s.Get(ctx, serviceName, &svcImport)).To(Succeed())
153 | if len(svcImport.Spec.IPs) > 0 {
154 | return svcImport.Spec.IPs[0]
155 | }
156 | return ""
157 | }, 10).Should(Equal(s.Spec.ClusterIP))
158 | }, 15)
159 | })
160 | Context("created with existing clustersetIP", func() {
161 | BeforeEach(func() {
162 | serviceName = types.NamespacedName{Namespace: testNS, Name: fmt.Sprintf("svc-%v", rand.Uint64())}
163 | derivedServiceName = types.NamespacedName{Namespace: testNS, Name: derivedName(serviceName)}
164 | serviceImport = v1alpha1.ServiceImport{
165 | ObjectMeta: metav1.ObjectMeta{
166 | Namespace: testNS,
167 | Name: serviceName.Name,
168 | },
169 | Spec: v1alpha1.ServiceImportSpec{
170 | Type: v1alpha1.ClusterSetIP,
171 | Ports: []v1alpha1.ServicePort{
172 | {Port: 80},
173 | },
174 | IPs: []string{"10.42.42.42"},
175 | },
176 | }
177 | Expect(k8s.Create(ctx, &serviceImport)).To(Succeed())
178 | })
179 | It("updates service loadbalancer status with service import IPs", func() {
180 | var svcImport v1alpha1.ServiceImport
181 | var s v1.Service
182 | Eventually(func() error {
183 | return k8s.Get(ctx, derivedServiceName, &s)
184 | }, 10).Should(Succeed())
185 | Eventually(func() string {
186 | Expect(k8s.Get(ctx, serviceName, &svcImport)).To(Succeed())
187 | if len(svcImport.Spec.IPs) > 0 {
188 | return svcImport.Spec.IPs[0]
189 | }
190 | return ""
191 | }, 10).Should(Equal(s.Status.LoadBalancer.Ingress[0].IP))
192 | }, 15)
193 | })
194 | })
195 |
--------------------------------------------------------------------------------
/hack/boilerplate/boilerplate.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2015 The Kubernetes Authors.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | from __future__ import print_function
18 |
19 | import argparse
20 | import difflib
21 | import glob
22 | import json
23 | import mmap
24 | import os
25 | import re
26 | import sys
27 | from datetime import date
28 |
29 | parser = argparse.ArgumentParser()
30 | parser.add_argument(
31 | "filenames",
32 | help="list of files to check, all files if unspecified",
33 | nargs='*')
34 |
35 | rootdir = os.path.dirname(__file__) + "/../../"
36 | rootdir = os.path.abspath(rootdir)
37 | parser.add_argument(
38 | "--rootdir", default=rootdir, help="root directory to examine")
39 |
40 | default_boilerplate_dir = os.path.join(rootdir, "hack/boilerplate")
41 | parser.add_argument(
42 | "--boilerplate-dir", default=default_boilerplate_dir)
43 |
44 | parser.add_argument(
45 | "-v", "--verbose",
46 | help="give verbose output regarding why a file does not pass",
47 | action="store_true")
48 |
49 | args = parser.parse_args()
50 |
51 | verbose_out = sys.stderr if args.verbose else open("/dev/null", "w")
52 |
53 |
54 | def get_refs():
55 | refs = {}
56 |
57 | for path in glob.glob(os.path.join(args.boilerplate_dir, "boilerplate.*.txt")):
58 | extension = os.path.basename(path).split(".")[1]
59 |
60 | ref_file = open(path, 'r')
61 | ref = ref_file.read().splitlines()
62 | ref_file.close()
63 | refs[extension] = ref
64 |
65 | return refs
66 |
67 |
68 | def file_passes(filename, refs, regexs):
69 | try:
70 | f = open(filename, 'r')
71 | except Exception as exc:
72 | print("Unable to open %s: %s" % (filename, exc), file=verbose_out)
73 | return False
74 |
75 | data = f.read()
76 | f.close()
77 |
78 | basename = os.path.basename(filename)
79 | extension = file_extension(filename)
80 | if extension != "":
81 | ref = refs[extension]
82 | else:
83 | ref = refs[basename]
84 |
85 | # remove build tags from the top of Go files
86 | if extension == "go":
87 | p = regexs["go_build_constraints"]
88 | (data, found) = p.subn("", data, 1)
89 |
90 | # remove shebang from the top of shell files
91 | if extension == "sh":
92 | p = regexs["shebang"]
93 | (data, found) = p.subn("", data, 1)
94 |
95 | data = data.splitlines()
96 |
97 | # if our test file is smaller than the reference it surely fails!
98 | if len(ref) > len(data):
99 | print('File %s smaller than reference (%d < %d)' %
100 | (filename, len(data), len(ref)),
101 | file=verbose_out)
102 | return False
103 |
104 | # trim our file to the same number of lines as the reference file
105 | data = data[:len(ref)]
106 |
107 | p = regexs["year"]
108 | for d in data:
109 | if p.search(d):
110 | print('File %s is missing the year' % filename, file=verbose_out)
111 | return False
112 |
113 | # Replace all occurrences of the regex "CURRENT_YEAR|...|2016|2015|2014" with "YEAR"
114 | p = regexs["date"]
115 | for i, d in enumerate(data):
116 | (data[i], found) = p.subn('YEAR', d)
117 | if found != 0:
118 | break
119 |
120 | # if we don't match the reference at this point, fail
121 | if ref != data:
122 | print("Header in %s does not match reference, diff:" %
123 | filename, file=verbose_out)
124 | if args.verbose:
125 | print(file=verbose_out)
126 | for line in difflib.unified_diff(ref, data, 'reference', filename, lineterm=''):
127 | print(line, file=verbose_out)
128 | print(file=verbose_out)
129 | return False
130 |
131 | return True
132 |
133 |
134 | def file_extension(filename):
135 | return os.path.splitext(filename)[1].split(".")[-1].lower()
136 |
137 |
138 | skipped_dirs = [
139 | '.git',
140 | "vendor",
141 | "test/e2e/framework/framework.go",
142 | "images"
143 | ]
144 |
145 |
146 | def normalize_files(files):
147 | newfiles = []
148 | for pathname in files:
149 | if any(x in pathname for x in skipped_dirs):
150 | continue
151 | newfiles.append(pathname)
152 | for i, pathname in enumerate(newfiles):
153 | if not os.path.isabs(pathname):
154 | newfiles[i] = os.path.join(args.rootdir, pathname)
155 | return newfiles
156 |
157 |
158 | def get_files(extensions):
159 | files = []
160 | if len(args.filenames) > 0:
161 | files = args.filenames
162 | else:
163 | for root, dirs, walkfiles in os.walk(args.rootdir):
164 | # don't visit certain dirs. This is just a performance improvement
165 | # as we would prune these later in normalize_files(). But doing it
166 | # cuts down the amount of filesystem walking we do and cuts down
167 | # the size of the file list
168 | for d in skipped_dirs:
169 | if d in dirs:
170 | dirs.remove(d)
171 |
172 | for name in walkfiles:
173 | pathname = os.path.join(root, name)
174 | files.append(pathname)
175 |
176 | files = normalize_files(files)
177 | outfiles = []
178 | for pathname in files:
179 | basename = os.path.basename(pathname)
180 | extension = file_extension(pathname)
181 | if extension in extensions or basename in extensions:
182 | outfiles.append(pathname)
183 | return outfiles
184 |
185 |
186 | def get_regexs():
187 | regexs = {}
188 | # Search for "YEAR" which exists in the boilerplate, but shouldn't in the real thing
189 | regexs["year"] = re.compile('YEAR')
190 | # dates can be 2014, 2015, 2016, ..., CURRENT_YEAR, company holder names can be anything
191 | years = range(2014, date.today().year + 1)
192 | regexs["date"] = re.compile(
193 | '(%s)' % "|".join(map(lambda l: str(l), years)))
194 | # strip // +build \n\n build constraints
195 | regexs["go_build_constraints"] = re.compile(
196 | r"^(// ?(go:|\+)build.*\n)+\n", re.MULTILINE)
197 | # strip #!.* from shell scripts
198 | regexs["shebang"] = re.compile(r"^(#!.*\n)\n*", re.MULTILINE)
199 | return regexs
200 |
201 |
202 | def main():
203 | regexs = get_regexs()
204 | refs = get_refs()
205 | filenames = get_files(refs.keys())
206 |
207 | for filename in filenames:
208 | if not file_passes(filename, refs, regexs):
209 | print(filename, file=sys.stdout)
210 |
211 | return 0
212 |
213 |
214 | if __name__ == "__main__":
215 | sys.exit(main())
216 |
--------------------------------------------------------------------------------
/config/crd/multicluster.x-k8s.io_serviceexports.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2020 The Kubernetes Authors.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | apiVersion: apiextensions.k8s.io/v1
15 | kind: CustomResourceDefinition
16 | metadata:
17 | name: serviceexports.multicluster.x-k8s.io
18 | labels:
19 | multicluster.x-k8s.io/release-version: "v0.3.0"
20 | # The revision is updated on each CRD change and reset back to 0 on every new version.
21 | # It can be used together with the version label when installing those CRDs
22 | # and prevent any downgrades.
23 | multicluster.x-k8s.io/crd-schema-revision: "1"
24 | spec:
25 | group: multicluster.x-k8s.io
26 | scope: Namespaced
27 | names:
28 | plural: serviceexports
29 | singular: serviceexport
30 | kind: ServiceExport
31 | shortNames:
32 | - svcex
33 | - svcexport
34 | versions:
35 | - name: v1alpha1
36 | served: true
37 | storage: true
38 | subresources:
39 | status: {}
40 | additionalPrinterColumns:
41 | - name: Age
42 | type: date
43 | jsonPath: .metadata.creationTimestamp
44 | "schema":
45 | "openAPIV3Schema":
46 | description: |-
47 | ServiceExport declares that the Service with the same name and namespace
48 | as this export should be consumable from other clusters.
49 | type: object
50 | properties:
51 | apiVersion:
52 | description: |-
53 | APIVersion defines the versioned schema of this representation of an object.
54 | Servers should convert recognized schemas to the latest internal value, and
55 | may reject unrecognized values.
56 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
57 | type: string
58 | kind:
59 | description: |-
60 | Kind is a string value representing the REST resource this object represents.
61 | Servers may infer this from the endpoint the client submits requests to.
62 | Cannot be updated.
63 | In CamelCase.
64 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
65 | type: string
66 | metadata:
67 | type: object
68 | spec:
69 | description: spec defines the behavior of a ServiceExport.
70 | type: object
71 | properties:
72 | exportedAnnotations:
73 | description: exportedAnnotations describes the annotations exported. It is optional for implementation.
74 | type: object
75 | additionalProperties:
76 | type: string
77 | exportedLabels:
78 | description: exportedLabels describes the labels exported. It is optional for implementation.
79 | type: object
80 | additionalProperties:
81 | type: string
82 | status:
83 | description: |-
84 | status describes the current state of an exported service.
85 | Service configuration comes from the Service that had the same
86 | name and namespace as this ServiceExport.
87 | Populated by the multi-cluster service implementation's controller.
88 | type: object
89 | properties:
90 | conditions:
91 | type: array
92 | items:
93 | description: Condition contains details for one aspect of the current state of this API Resource.
94 | type: object
95 | required:
96 | - lastTransitionTime
97 | - message
98 | - reason
99 | - status
100 | - type
101 | properties:
102 | lastTransitionTime:
103 | description: |-
104 | lastTransitionTime is the last time the condition transitioned from one status to another.
105 | This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
106 | type: string
107 | format: date-time
108 | message:
109 | description: |-
110 | message is a human readable message indicating details about the transition.
111 | This may be an empty string.
112 | type: string
113 | maxLength: 32768
114 | observedGeneration:
115 | description: |-
116 | observedGeneration represents the .metadata.generation that the condition was set based upon.
117 | For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
118 | with respect to the current state of the instance.
119 | type: integer
120 | format: int64
121 | minimum: 0
122 | reason:
123 | description: |-
124 | reason contains a programmatic identifier indicating the reason for the condition's last transition.
125 | Producers of specific condition types may define expected values and meanings for this field,
126 | and whether the values are considered a guaranteed API.
127 | The value should be a CamelCase string.
128 | This field may not be empty.
129 | type: string
130 | maxLength: 1024
131 | minLength: 1
132 | pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
133 | status:
134 | description: status of the condition, one of True, False, Unknown.
135 | type: string
136 | enum:
137 | - "True"
138 | - "False"
139 | - Unknown
140 | type:
141 | description: type of condition in CamelCase or in foo.example.com/CamelCase.
142 | type: string
143 | maxLength: 316
144 | 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])$
145 | x-kubernetes-list-map-keys:
146 | - type
147 | x-kubernetes-list-type: map
148 |
--------------------------------------------------------------------------------
/e2e/localserviceimpact_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2024 The Kubernetes Authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package e2etest
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "math/rand"
23 | "strings"
24 | "time"
25 |
26 | . "github.com/onsi/ginkgo/v2"
27 | . "github.com/onsi/gomega"
28 | appsv1 "k8s.io/api/apps/v1"
29 | v1 "k8s.io/api/core/v1"
30 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 | )
32 |
33 | var _ = Describe("Local service not impacted", func() {
34 | helloDeployment := appsv1.Deployment{
35 | ObjectMeta: metav1.ObjectMeta{
36 | Name: "hello",
37 | },
38 | Spec: appsv1.DeploymentSpec{
39 | Replicas: &replicaCount,
40 | Selector: &metav1.LabelSelector{
41 | MatchLabels: map[string]string{
42 | "app": "hello",
43 | },
44 | },
45 | Template: v1.PodTemplateSpec{
46 | ObjectMeta: metav1.ObjectMeta{
47 | Labels: map[string]string{"app": "hello"},
48 | },
49 | Spec: v1.PodSpec{
50 | Containers: []v1.Container{
51 | {
52 | Name: "hello-tcp",
53 | Image: "alpine/socat:1.7.4.4",
54 | Args: []string{"-v", "-v", "TCP-LISTEN:42,crlf,reuseaddr,fork", "SYSTEM:echo $(CLUSTER_ID)"},
55 | Env: []v1.EnvVar{
56 | {
57 | Name: "CLUSTER_ID",
58 | ValueFrom: &v1.EnvVarSource{
59 | ConfigMapKeyRef: &v1.ConfigMapKeySelector{
60 | LocalObjectReference: v1.LocalObjectReference{
61 | Name: "cluster-info",
62 | },
63 | Key: "clusterID",
64 | },
65 | },
66 | },
67 | },
68 | },
69 | },
70 | },
71 | },
72 | },
73 | }
74 |
75 | var (
76 | namespace string
77 |
78 | ctx = context.Background()
79 | reqPod *v1.Pod
80 | )
81 |
82 | BeforeEach(func() {
83 | namespace = fmt.Sprintf("mcse2e-conformance-%v", rand.Uint32())
84 | _, err := cluster1.k8s.CoreV1().Namespaces().Create(ctx, &v1.Namespace{
85 | ObjectMeta: metav1.ObjectMeta{Name: namespace},
86 | }, metav1.CreateOptions{})
87 | Expect(err).ToNot(HaveOccurred())
88 | _, err = cluster2.k8s.CoreV1().Namespaces().Create(ctx, &v1.Namespace{
89 | ObjectMeta: metav1.ObjectMeta{Name: namespace},
90 | }, metav1.CreateOptions{})
91 | Expect(err).ToNot(HaveOccurred())
92 | _, err = cluster1.k8s.CoreV1().ConfigMaps(namespace).Create(ctx, &v1.ConfigMap{
93 | ObjectMeta: metav1.ObjectMeta{
94 | Name: "cluster-info",
95 | },
96 | Data: map[string]string{
97 | "clusterID": "cluster1",
98 | },
99 | }, metav1.CreateOptions{})
100 | Expect(err).NotTo(HaveOccurred())
101 | _, err = cluster2.k8s.CoreV1().ConfigMaps(namespace).Create(ctx, &v1.ConfigMap{
102 | ObjectMeta: metav1.ObjectMeta{
103 | Name: "cluster-info",
104 | },
105 | Data: map[string]string{
106 | "clusterID": "cluster2",
107 | },
108 | }, metav1.CreateOptions{})
109 | Expect(err).NotTo(HaveOccurred())
110 | pod := requestPod
111 | _, err = cluster1.k8s.CoreV1().Pods(namespace).Create(ctx, &pod, metav1.CreateOptions{})
112 | Expect(err).ToNot(HaveOccurred())
113 | dep := helloDeployment
114 | _, err = cluster1.k8s.AppsV1().Deployments(namespace).Create(ctx, &dep, metav1.CreateOptions{})
115 | Expect(err).ToNot(HaveOccurred())
116 | _, err = cluster2.k8s.AppsV1().Deployments(namespace).Create(ctx, &dep, metav1.CreateOptions{})
117 | Expect(err).ToNot(HaveOccurred())
118 | svc := helloService
119 | _, err = cluster1.k8s.CoreV1().Services(namespace).Create(ctx, &svc, metav1.CreateOptions{})
120 | Expect(err).ToNot(HaveOccurred())
121 | _, err = cluster2.k8s.CoreV1().Services(namespace).Create(ctx, &svc, metav1.CreateOptions{})
122 | Expect(err).ToNot(HaveOccurred())
123 | _, err = cluster1.mcs.MulticlusterV1alpha1().ServiceImports(namespace).Create(ctx, &helloServiceImport, metav1.CreateOptions{})
124 | Expect(err).ToNot(HaveOccurred())
125 | deploymentAvailable := func(clients clusterClients) func(Gomega) {
126 | return func(g Gomega) {
127 | deployment, err := clients.k8s.AppsV1().Deployments(namespace).Get(ctx, dep.Name, metav1.GetOptions{})
128 | g.Expect(err).NotTo(HaveOccurred())
129 | g.Expect(deployment.Status.Conditions).To(ContainElement(Satisfy(func(cond appsv1.DeploymentCondition) bool {
130 | return cond.Type == appsv1.DeploymentAvailable &&
131 | cond.Status == v1.ConditionTrue
132 | })))
133 | }
134 | }
135 | Eventually(deploymentAvailable(cluster1), 30).Should(Succeed())
136 | Eventually(deploymentAvailable(cluster2), 30).Should(Succeed())
137 |
138 | exportService(ctx, cluster2, cluster1, namespace, svc.Name)
139 |
140 | Eventually(func() []string {
141 | svcImport, err := cluster1.mcs.MulticlusterV1alpha1().ServiceImports(namespace).Get(ctx, helloServiceImport.Name, metav1.GetOptions{})
142 | Expect(err).ToNot(HaveOccurred())
143 | return svcImport.Spec.IPs
144 | }).ShouldNot(BeEmpty())
145 | reqPod, err = cluster1.k8s.CoreV1().Pods(namespace).Get(ctx, requestPod.Name, metav1.GetOptions{})
146 | Expect(err).ToNot(HaveOccurred())
147 | By("Created all in " + namespace)
148 | })
149 | AfterEach(func() {
150 | if *noTearDown {
151 | By(fmt.Sprintf("Skipping teardown. Test namespace %q", namespace))
152 | By(fmt.Sprintf("Cluster 1: kubectl --kubeconfig %q -n %q", *kubeconfig1, namespace))
153 | By(fmt.Sprintf("Cluster 2: kubectl --kubeconfig %q -n %q", *kubeconfig2, namespace))
154 | return
155 | }
156 | Expect(cluster1.k8s.CoreV1().Namespaces().Delete(ctx, namespace, metav1.DeleteOptions{})).To(Succeed())
157 | Expect(cluster2.k8s.CoreV1().Namespaces().Delete(ctx, namespace, metav1.DeleteOptions{})).To(Succeed())
158 | })
159 | Specify("DNS resolves as expected", func() {
160 | checkAllClustersReachable := func(command []string, clusterIDs ...string) {
161 | clusters := map[string]int{}
162 | Eventually(func(g Gomega) {
163 | stdout, _, err := execCmd(cluster1.k8s, restcfg1, reqPod.Name, reqPod.Namespace, command)
164 | g.Expect(err).ToNot(HaveOccurred())
165 | clusterID := strings.TrimSpace(string(stdout))
166 | g.Expect(clusterID).To(BeElementOf(clusterIDs))
167 | clusters[clusterID]++
168 | }).MustPassRepeatedly(20).Within(time.Second * 10).Should(Succeed())
169 | Expect(clusters).To(HaveEach(Not(BeZero())))
170 | }
171 |
172 | By("verifying a local, unexported service is reachable via cluster.local")
173 | command := []string{"sh", "-c", fmt.Sprintf("echo hi | nc %s.%s.svc.cluster.local 42", helloService.Name, namespace)}
174 | checkAllClustersReachable(command, "cluster1")
175 |
176 | By("verifying a remote, exported service is reachable via clusterset.local")
177 | command = []string{"sh", "-c", fmt.Sprintf("echo hi | nc %s.%s.svc.clusterset.local 42", helloServiceImport.Name, namespace)}
178 | checkAllClustersReachable(command, "cluster2")
179 |
180 | By("exporting the service from cluster1")
181 | exportService(ctx, cluster1, cluster1, namespace, helloService.Name)
182 |
183 | By("verifying a service exported from both local and remote clusters is reachable via clusterset.local")
184 | checkAllClustersReachable(command, "cluster1", "cluster2")
185 |
186 | By("verifying a local, exported service is reachable via cluster.local")
187 | command = []string{"sh", "-c", fmt.Sprintf("echo hi | nc %s.%s.svc.cluster.local 42", helloService.Name, namespace)}
188 | checkAllClustersReachable(command, "cluster1")
189 | })
190 | })
191 |
--------------------------------------------------------------------------------