├── config
├── prometheus
│ ├── kustomization.yaml
│ └── monitor.yaml
├── certmanager
│ ├── kustomization.yaml
│ ├── kustomizeconfig.yaml
│ └── certificate.yaml
├── webhook
│ ├── kustomization.yaml
│ ├── service.yaml
│ ├── manifests.yaml
│ └── kustomizeconfig.yaml
├── rbac
│ ├── service_account.yaml
│ ├── auth_proxy_client_clusterrole.yaml
│ ├── role_binding.yaml
│ ├── auth_proxy_role_binding.yaml
│ ├── leader_election_role_binding.yaml
│ ├── auth_proxy_role.yaml
│ ├── auth_proxy_service.yaml
│ ├── orbit_viewer_role.yaml
│ ├── serviceroute_viewer_role.yaml
│ ├── orbit_editor_role.yaml
│ ├── serviceroute_editor_role.yaml
│ ├── leader_election_role.yaml
│ ├── kustomization.yaml
│ └── role.yaml
├── samples
│ ├── network_v1alpha1_orbit.yaml
│ └── network_v1alpha1_serviceroute.yaml
├── crd
│ ├── patches
│ │ ├── cainjection_in_orbits.yaml
│ │ ├── cainjection_in_serviceroutes.yaml
│ │ ├── webhook_in_orbits.yaml
│ │ └── webhook_in_serviceroutes.yaml
│ ├── kustomizeconfig.yaml
│ ├── kustomization.yaml
│ └── bases
│ │ ├── network.kubeorbit.io_orbits.yaml
│ │ └── network.kubeorbit.io_serviceroutes.yaml
├── manager
│ ├── controller_manager_config.yaml
│ ├── kustomization.yaml
│ └── manager.yaml
└── default
│ ├── manager_config_patch.yaml
│ ├── manager_webhook_patch.yaml
│ ├── webhookcainjection_patch.yaml
│ ├── manager_auth_proxy_patch.yaml
│ └── kustomization.yaml
├── api
├── v1
│ ├── constant.go
│ └── pod_webhook.go
└── v1alpha1
│ ├── groupversion_info.go
│ ├── orbit_types.go
│ ├── serviceroute_types.go
│ └── zz_generated.deepcopy.go
├── .dockerignore
├── cmd
├── Makefile
└── orbitctl.go
├── CONTRIBUTING.md
├── .gitignore
├── hack
└── boilerplate.go.txt
├── PROJECT
├── pkg
├── cli
│ ├── util
│ │ └── network.go
│ ├── command
│ │ ├── uninstall.go
│ │ └── forward.go
│ ├── core
│ │ ├── portforward.go
│ │ ├── uninstall.go
│ │ ├── channel.go
│ │ ├── proxy_manager.go
│ │ └── forward.go
│ ├── logger
│ │ └── logger.go
│ └── client
│ │ └── kubernetes.go
└── controllers
│ ├── suite_test.go
│ ├── orbit_controller.go
│ └── serviceroute_controller.go
├── Dockerfile
├── README.md
├── go.mod
├── main.go
├── Makefile
└── LICENSE
/config/prometheus/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - monitor.yaml
3 |
--------------------------------------------------------------------------------
/api/v1/constant.go:
--------------------------------------------------------------------------------
1 | package v1
2 |
3 | const (
4 | KUBEORBIT_CHANNEL_LABEL = "version"
5 | )
6 |
--------------------------------------------------------------------------------
/config/certmanager/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - certificate.yaml
3 |
4 | configurations:
5 | - kustomizeconfig.yaml
6 |
--------------------------------------------------------------------------------
/config/webhook/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - manifests.yaml
3 | - service.yaml
4 |
5 | configurations:
6 | - kustomizeconfig.yaml
7 |
--------------------------------------------------------------------------------
/config/rbac/service_account.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: controller-manager
5 | namespace: system
6 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
2 | # Ignore build and test binaries.
3 | bin/
4 | testbin/
5 |
--------------------------------------------------------------------------------
/cmd/Makefile:
--------------------------------------------------------------------------------
1 | cli:
2 | GOARCH=amd64 GOOS=linux go build -o "orbitctl-linux" .
3 | GOARCH=amd64 GOOS=darwin go build -o "orbitctl-darwin" .
4 | GOARCH=amd64 GOOS=windows go build -o "orbitctl.exe" .
--------------------------------------------------------------------------------
/config/rbac/auth_proxy_client_clusterrole.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRole
3 | metadata:
4 | name: metrics-reader
5 | rules:
6 | - nonResourceURLs:
7 | - "/metrics"
8 | verbs:
9 | - get
10 |
--------------------------------------------------------------------------------
/config/webhook/service.yaml:
--------------------------------------------------------------------------------
1 |
2 | apiVersion: v1
3 | kind: Service
4 | metadata:
5 | name: webhook-service
6 | namespace: system
7 | spec:
8 | ports:
9 | - port: 443
10 | protocol: TCP
11 | targetPort: 9443
12 | selector:
13 | control-plane: controller-manager
14 |
--------------------------------------------------------------------------------
/config/samples/network_v1alpha1_orbit.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: network.kubeorbit.io/v1alpha1
2 | kind: Orbit
3 | metadata:
4 | name: orbit-sample
5 | namespace: tc-apps
6 | spec:
7 | # Add fields here
8 | provider: istio
9 | trafficRules:
10 | headers:
11 | version: "v1" #Each channel needs to specify a set of key-value pairs
12 |
--------------------------------------------------------------------------------
/config/crd/patches/cainjection_in_orbits.yaml:
--------------------------------------------------------------------------------
1 | # The following patch adds a directive for certmanager to inject CA into the CRD
2 | apiVersion: apiextensions.k8s.io/v1
3 | kind: CustomResourceDefinition
4 | metadata:
5 | annotations:
6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
7 | name: orbits.network.kubeorbit.io
8 |
--------------------------------------------------------------------------------
/config/rbac/role_binding.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRoleBinding
3 | metadata:
4 | name: manager-rolebinding
5 | roleRef:
6 | apiGroup: rbac.authorization.k8s.io
7 | kind: ClusterRole
8 | name: manager-role
9 | subjects:
10 | - kind: ServiceAccount
11 | name: controller-manager
12 | namespace: system
13 |
--------------------------------------------------------------------------------
/config/manager/controller_manager_config.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1
2 | kind: ControllerManagerConfig
3 | health:
4 | healthProbeBindAddress: :8081
5 | metrics:
6 | bindAddress: 127.0.0.1:8080
7 | webhook:
8 | port: 9443
9 | leaderElection:
10 | leaderElect: true
11 | resourceName: 35a4e702.network.kubeorbit.io
12 |
--------------------------------------------------------------------------------
/config/rbac/auth_proxy_role_binding.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRoleBinding
3 | metadata:
4 | name: proxy-rolebinding
5 | roleRef:
6 | apiGroup: rbac.authorization.k8s.io
7 | kind: ClusterRole
8 | name: proxy-role
9 | subjects:
10 | - kind: ServiceAccount
11 | name: controller-manager
12 | namespace: system
13 |
--------------------------------------------------------------------------------
/config/crd/patches/cainjection_in_serviceroutes.yaml:
--------------------------------------------------------------------------------
1 | # The following patch adds a directive for certmanager to inject CA into the CRD
2 | apiVersion: apiextensions.k8s.io/v1
3 | kind: CustomResourceDefinition
4 | metadata:
5 | annotations:
6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
7 | name: serviceroutes.network.kubeorbit.io
8 |
--------------------------------------------------------------------------------
/config/rbac/leader_election_role_binding.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: RoleBinding
3 | metadata:
4 | name: leader-election-rolebinding
5 | roleRef:
6 | apiGroup: rbac.authorization.k8s.io
7 | kind: Role
8 | name: leader-election-role
9 | subjects:
10 | - kind: ServiceAccount
11 | name: controller-manager
12 | namespace: system
13 |
--------------------------------------------------------------------------------
/config/rbac/auth_proxy_role.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRole
3 | metadata:
4 | name: proxy-role
5 | rules:
6 | - apiGroups:
7 | - authentication.k8s.io
8 | resources:
9 | - tokenreviews
10 | verbs:
11 | - create
12 | - apiGroups:
13 | - authorization.k8s.io
14 | resources:
15 | - subjectaccessreviews
16 | verbs:
17 | - create
18 |
--------------------------------------------------------------------------------
/config/rbac/auth_proxy_service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | labels:
5 | control-plane: controller-manager
6 | name: controller-manager-metrics-service
7 | namespace: system
8 | spec:
9 | ports:
10 | - name: https
11 | port: 8443
12 | protocol: TCP
13 | targetPort: https
14 | selector:
15 | control-plane: controller-manager
16 |
--------------------------------------------------------------------------------
/config/manager/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - manager.yaml
3 |
4 | generatorOptions:
5 | disableNameSuffixHash: true
6 |
7 | configMapGenerator:
8 | - files:
9 | - controller_manager_config.yaml
10 | name: manager-config
11 | apiVersion: kustomize.config.k8s.io/v1beta1
12 | kind: Kustomization
13 | images:
14 | - name: controller
15 | newName: controller
16 | newTag: latest
17 |
--------------------------------------------------------------------------------
/config/samples/network_v1alpha1_serviceroute.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: network.kubeorbit.io/v1alpha1
2 | kind: ServiceRoute
3 | metadata:
4 | name: serviceroute-sample
5 | namespace: tc-apps
6 | spec:
7 | name: pod-svc
8 | trafficRoutes:
9 | routes:
10 | - name: v1
11 | labels:
12 | version: v1
13 | headers:
14 | version:
15 | exact: v1
16 | default:
17 | version: base
--------------------------------------------------------------------------------
/config/rbac/orbit_viewer_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions for end users to view orbits.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: orbit-viewer-role
6 | rules:
7 | - apiGroups:
8 | - network.kubeorbit.io
9 | resources:
10 | - orbits
11 | verbs:
12 | - get
13 | - list
14 | - watch
15 | - apiGroups:
16 | - network.kubeorbit.io
17 | resources:
18 | - orbits/status
19 | verbs:
20 | - get
21 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute
2 |
3 | First off, Thanks for taking the time to contribute! ❤️
4 |
5 | ## Getting Started
6 |
7 | * Make sure you have a GitHub account.
8 | * Fork the repository on GitHub.
9 |
10 | ## Submitting Changes
11 |
12 | * Write a [good commit message](https://www.conventionalcommits.org/en/v1.0.0/)
13 | * Push your changes to a feature branch in your fork of the repository.
14 | * Submit a pull request to the repository in the teamcode-inc organization.
--------------------------------------------------------------------------------
/config/rbac/serviceroute_viewer_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions for end users to view serviceroutes.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: serviceroute-viewer-role
6 | rules:
7 | - apiGroups:
8 | - network.kubeorbit.io
9 | resources:
10 | - serviceroutes
11 | verbs:
12 | - get
13 | - list
14 | - watch
15 | - apiGroups:
16 | - network.kubeorbit.io
17 | resources:
18 | - serviceroutes/status
19 | verbs:
20 | - get
21 |
--------------------------------------------------------------------------------
/config/certmanager/kustomizeconfig.yaml:
--------------------------------------------------------------------------------
1 | # This configuration is for teaching kustomize how to update name ref and var substitution
2 | nameReference:
3 | - kind: Issuer
4 | group: cert-manager.io
5 | fieldSpecs:
6 | - kind: Certificate
7 | group: cert-manager.io
8 | path: spec/issuerRef/name
9 |
10 | varReference:
11 | - kind: Certificate
12 | group: cert-manager.io
13 | path: spec/commonName
14 | - kind: Certificate
15 | group: cert-manager.io
16 | path: spec/dnsNames
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Binaries for programs and plugins
3 | *.exe
4 | *.exe~
5 | *.dll
6 | *.so
7 | *.dylib
8 | bin
9 | testbin/*
10 |
11 | # Test binary, build with `go test -c`
12 | *.test
13 |
14 | # Output of the go coverage tool, specifically when used with LiteIDE
15 | *.out
16 |
17 | # Kubernetes Generated files - skip generated files, except for vendored files
18 |
19 | !vendor/**/zz_generated.*
20 |
21 | # editor and IDE paraphernalia
22 | .idea
23 | .vscode
24 | *.swp
25 | *.swo
26 | *~
27 |
--------------------------------------------------------------------------------
/config/crd/patches/webhook_in_orbits.yaml:
--------------------------------------------------------------------------------
1 | # The following patch enables a conversion webhook for the CRD
2 | apiVersion: apiextensions.k8s.io/v1
3 | kind: CustomResourceDefinition
4 | metadata:
5 | name: orbits.network.kubeorbit.io
6 | spec:
7 | conversion:
8 | strategy: Webhook
9 | webhook:
10 | clientConfig:
11 | service:
12 | namespace: system
13 | name: webhook-service
14 | path: /convert
15 | conversionReviewVersions:
16 | - v1
17 |
--------------------------------------------------------------------------------
/config/rbac/orbit_editor_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions for end users to edit orbits.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: orbit-editor-role
6 | rules:
7 | - apiGroups:
8 | - network.kubeorbit.io
9 | resources:
10 | - orbits
11 | verbs:
12 | - create
13 | - delete
14 | - get
15 | - list
16 | - patch
17 | - update
18 | - watch
19 | - apiGroups:
20 | - network.kubeorbit.io
21 | resources:
22 | - orbits/status
23 | verbs:
24 | - get
25 |
--------------------------------------------------------------------------------
/config/crd/patches/webhook_in_serviceroutes.yaml:
--------------------------------------------------------------------------------
1 | # The following patch enables a conversion webhook for the CRD
2 | apiVersion: apiextensions.k8s.io/v1
3 | kind: CustomResourceDefinition
4 | metadata:
5 | name: serviceroutes.network.kubeorbit.io
6 | spec:
7 | conversion:
8 | strategy: Webhook
9 | webhook:
10 | clientConfig:
11 | service:
12 | namespace: system
13 | name: webhook-service
14 | path: /convert
15 | conversionReviewVersions:
16 | - v1
17 |
--------------------------------------------------------------------------------
/config/rbac/serviceroute_editor_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions for end users to edit serviceroutes.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: ClusterRole
4 | metadata:
5 | name: serviceroute-editor-role
6 | rules:
7 | - apiGroups:
8 | - network.kubeorbit.io
9 | resources:
10 | - serviceroutes
11 | verbs:
12 | - create
13 | - delete
14 | - get
15 | - list
16 | - patch
17 | - update
18 | - watch
19 | - apiGroups:
20 | - network.kubeorbit.io
21 | resources:
22 | - serviceroutes/status
23 | verbs:
24 | - get
25 |
--------------------------------------------------------------------------------
/config/default/manager_config_patch.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: controller-manager
5 | namespace: system
6 | spec:
7 | template:
8 | spec:
9 | containers:
10 | - name: manager
11 | args:
12 | - "--config=controller_manager_config.yaml"
13 | volumeMounts:
14 | - name: manager-config
15 | mountPath: /controller_manager_config.yaml
16 | subPath: controller_manager_config.yaml
17 | volumes:
18 | - name: manager-config
19 | configMap:
20 | name: manager-config
21 |
--------------------------------------------------------------------------------
/config/prometheus/monitor.yaml:
--------------------------------------------------------------------------------
1 |
2 | # Prometheus Monitor Service (Metrics)
3 | apiVersion: monitoring.coreos.com/v1
4 | kind: ServiceMonitor
5 | metadata:
6 | labels:
7 | control-plane: controller-manager
8 | name: controller-manager-metrics-monitor
9 | namespace: system
10 | spec:
11 | endpoints:
12 | - path: /metrics
13 | port: https
14 | scheme: https
15 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
16 | tlsConfig:
17 | insecureSkipVerify: true
18 | selector:
19 | matchLabels:
20 | control-plane: controller-manager
21 |
--------------------------------------------------------------------------------
/config/crd/kustomizeconfig.yaml:
--------------------------------------------------------------------------------
1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD
2 | nameReference:
3 | - kind: Service
4 | version: v1
5 | fieldSpecs:
6 | - kind: CustomResourceDefinition
7 | version: v1
8 | group: apiextensions.k8s.io
9 | path: spec/conversion/webhook/clientConfig/service/name
10 |
11 | namespace:
12 | - kind: CustomResourceDefinition
13 | version: v1
14 | group: apiextensions.k8s.io
15 | path: spec/conversion/webhook/clientConfig/service/namespace
16 | create: false
17 |
18 | varReference:
19 | - path: metadata/annotations
20 |
--------------------------------------------------------------------------------
/hack/boilerplate.go.txt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode 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 | */
--------------------------------------------------------------------------------
/config/default/manager_webhook_patch.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: controller-manager
5 | namespace: system
6 | spec:
7 | template:
8 | spec:
9 | containers:
10 | - name: manager
11 | ports:
12 | - containerPort: 9443
13 | name: webhook-server
14 | protocol: TCP
15 | volumeMounts:
16 | - mountPath: /tmp/k8s-webhook-server/serving-certs
17 | name: cert
18 | readOnly: true
19 | volumes:
20 | - name: cert
21 | secret:
22 | defaultMode: 420
23 | secretName: webhook-server-cert
24 |
--------------------------------------------------------------------------------
/config/webhook/manifests.yaml:
--------------------------------------------------------------------------------
1 |
2 | ---
3 | apiVersion: admissionregistration.k8s.io/v1
4 | kind: MutatingWebhookConfiguration
5 | metadata:
6 | creationTimestamp: null
7 | name: mutating-webhook-configuration
8 | webhooks:
9 | - admissionReviewVersions:
10 | - v1
11 | clientConfig:
12 | service:
13 | name: webhook-service
14 | namespace: system
15 | path: /mutate-core-v1-pod
16 | failurePolicy: Fail
17 | name: mpod.kb.io
18 | rules:
19 | - apiGroups:
20 | - ""
21 | apiVersions:
22 | - v1
23 | operations:
24 | - CREATE
25 | - UPDATE
26 | resources:
27 | - pods
28 | sideEffects: None
29 |
--------------------------------------------------------------------------------
/config/rbac/leader_election_role.yaml:
--------------------------------------------------------------------------------
1 | # permissions to do leader election.
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: Role
4 | metadata:
5 | name: leader-election-role
6 | rules:
7 | - apiGroups:
8 | - ""
9 | resources:
10 | - configmaps
11 | verbs:
12 | - get
13 | - list
14 | - watch
15 | - create
16 | - update
17 | - patch
18 | - delete
19 | - apiGroups:
20 | - coordination.k8s.io
21 | resources:
22 | - leases
23 | verbs:
24 | - get
25 | - list
26 | - watch
27 | - create
28 | - update
29 | - patch
30 | - delete
31 | - apiGroups:
32 | - ""
33 | resources:
34 | - events
35 | verbs:
36 | - create
37 | - patch
38 |
--------------------------------------------------------------------------------
/config/default/webhookcainjection_patch.yaml:
--------------------------------------------------------------------------------
1 | # This patch add annotation to admission webhook config and
2 | # the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize.
3 | apiVersion: admissionregistration.k8s.io/v1
4 | kind: MutatingWebhookConfiguration
5 | metadata:
6 | name: mutating-webhook-configuration
7 | annotations:
8 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
9 | #---
10 | #apiVersion: admissionregistration.k8s.io/v1
11 | #kind: ValidatingWebhookConfiguration
12 | #metadata:
13 | # name: validating-webhook-configuration
14 | # annotations:
15 | # cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
--------------------------------------------------------------------------------
/PROJECT:
--------------------------------------------------------------------------------
1 | domain: kubeorbit.io
2 | layout:
3 | - go.kubebuilder.io/v3
4 | projectName: orbit
5 | repo: kubeorbit.io
6 | resources:
7 | - api:
8 | crdVersion: v1
9 | namespaced: true
10 | controller: true
11 | domain: kubeorbit.io
12 | group: network
13 | kind: Orbit
14 | path: kubeorbit.io/api/v1alpha1
15 | version: v1alpha1
16 | - api:
17 | crdVersion: v1
18 | namespaced: true
19 | controller: true
20 | domain: kubeorbit.io
21 | group: network
22 | kind: ServiceRoute
23 | path: kubeorbit.io/api/v1alpha1
24 | version: v1alpha1
25 | - group: core
26 | kind: Pod
27 | path: k8s.io/api/core/v1
28 | version: v1
29 | webhooks:
30 | defaulting: true
31 | webhookVersion: v1
32 | version: "3"
33 |
--------------------------------------------------------------------------------
/config/rbac/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | # All RBAC will be applied under this service account in
3 | # the deployment namespace. You may comment out this resource
4 | # if your manager will use a service account that exists at
5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding
6 | # subjects if changing service account names.
7 | - service_account.yaml
8 | - role.yaml
9 | - role_binding.yaml
10 | - leader_election_role.yaml
11 | - leader_election_role_binding.yaml
12 | # Comment the following 4 lines if you want to disable
13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy)
14 | # which protects your /metrics endpoint.
15 | - auth_proxy_service.yaml
16 | - auth_proxy_role.yaml
17 | - auth_proxy_role_binding.yaml
18 | - auth_proxy_client_clusterrole.yaml
19 |
--------------------------------------------------------------------------------
/pkg/cli/util/network.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode authors.
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | package util
14 |
15 | import (
16 | "net"
17 | )
18 |
19 | func IsAddrAvailable(addr string) bool {
20 | conn, err := net.Dial("tcp", addr)
21 | if err != nil {
22 | return false
23 | } else {
24 | conn.Close()
25 | return true
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Build the manager binary
2 | FROM golang:1.17 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 | # cache deps before building and copying source so that we don't need to re-download as much
9 | # and so that source changes don't invalidate our downloaded layer
10 | RUN go mod download
11 |
12 | # Copy the go source
13 | COPY main.go main.go
14 | COPY api/ api/
15 | COPY pkg/controllers/ controllers/
16 |
17 | # Build
18 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go
19 |
20 | # Use distroless as minimal base image to package the manager binary
21 | # Refer to https://github.com/GoogleContainerTools/distroless for more details
22 | FROM gcr.io/distroless/static:nonroot
23 | WORKDIR /
24 | COPY --from=builder /workspace/manager .
25 | USER 65532:65532
26 |
27 | ENTRYPOINT ["/manager"]
28 |
--------------------------------------------------------------------------------
/config/webhook/kustomizeconfig.yaml:
--------------------------------------------------------------------------------
1 | # the following config is for teaching kustomize where to look at when substituting vars.
2 | # It requires kustomize v2.1.0 or newer to work properly.
3 | nameReference:
4 | - kind: Service
5 | version: v1
6 | fieldSpecs:
7 | - kind: MutatingWebhookConfiguration
8 | group: admissionregistration.k8s.io
9 | path: webhooks/clientConfig/service/name
10 | # - kind: ValidatingWebhookConfiguration
11 | # group: admissionregistration.k8s.io
12 | # path: webhooks/clientConfig/service/name
13 |
14 | namespace:
15 | - kind: MutatingWebhookConfiguration
16 | group: admissionregistration.k8s.io
17 | path: webhooks/clientConfig/service/namespace
18 | create: true
19 | #- kind: ValidatingWebhookConfiguration
20 | # group: admissionregistration.k8s.io
21 | # path: webhooks/clientConfig/service/namespace
22 | # create: true
23 |
24 | varReference:
25 | - path: metadata/annotations
26 |
--------------------------------------------------------------------------------
/config/default/manager_auth_proxy_patch.yaml:
--------------------------------------------------------------------------------
1 | # This patch inject a sidecar container which is a HTTP proxy for the
2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews.
3 | apiVersion: apps/v1
4 | kind: Deployment
5 | metadata:
6 | name: controller-manager
7 | namespace: system
8 | spec:
9 | template:
10 | spec:
11 | containers:
12 | - name: kube-rbac-proxy
13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0
14 | args:
15 | - "--secure-listen-address=0.0.0.0:8443"
16 | - "--upstream=http://127.0.0.1:8080/"
17 | - "--logtostderr=true"
18 | - "--v=10"
19 | ports:
20 | - containerPort: 8443
21 | protocol: TCP
22 | name: https
23 | - name: manager
24 | args:
25 | - "--health-probe-bind-address=:8081"
26 | - "--metrics-bind-address=127.0.0.1:8080"
27 | - "--leader-elect"
28 |
--------------------------------------------------------------------------------
/config/certmanager/certificate.yaml:
--------------------------------------------------------------------------------
1 | # The following manifests contain a self-signed issuer CR and a certificate CR.
2 | # More document can be found at https://docs.cert-manager.io
3 | # WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes.
4 | apiVersion: cert-manager.io/v1
5 | kind: Issuer
6 | metadata:
7 | name: selfsigned-issuer
8 | namespace: system
9 | spec:
10 | selfSigned: {}
11 | ---
12 | apiVersion: cert-manager.io/v1
13 | kind: Certificate
14 | metadata:
15 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml
16 | namespace: system
17 | spec:
18 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize
19 | dnsNames:
20 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc
21 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local
22 | issuerRef:
23 | kind: Issuer
24 | name: selfsigned-issuer
25 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize
26 |
--------------------------------------------------------------------------------
/config/crd/kustomization.yaml:
--------------------------------------------------------------------------------
1 | # This kustomization.yaml is not intended to be run by itself,
2 | # since it depends on service name and namespace that are out of this kustomize package.
3 | # It should be run by config/default
4 | resources:
5 | - bases/network.kubeorbit.io_orbits.yaml
6 | - bases/network.kubeorbit.io_serviceroutes.yaml
7 | #+kubebuilder:scaffold:crdkustomizeresource
8 |
9 | patchesStrategicMerge:
10 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
11 | # patches here are for enabling the conversion webhook for each CRD
12 | - patches/webhook_in_orbits.yaml
13 | - patches/webhook_in_serviceroutes.yaml
14 | #+kubebuilder:scaffold:crdkustomizewebhookpatch
15 |
16 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
17 | # patches here are for enabling the CA injection for each CRD
18 | - patches/cainjection_in_orbits.yaml
19 | - patches/cainjection_in_serviceroutes.yaml
20 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch
21 |
22 | # the following config is for teaching kustomize how to do kustomization for CRDs.
23 | configurations:
24 | - kustomizeconfig.yaml
25 |
--------------------------------------------------------------------------------
/cmd/orbitctl.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode authors.
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | package main
14 |
15 | import (
16 | "github.com/spf13/cobra"
17 | "kubeorbit.io/pkg/cli/command"
18 | )
19 |
20 | var rootCmd = &cobra.Command{
21 | Use: "orbitctl",
22 | Long: `Orbitctl can forward traffic intended for a service in-clsuter to your local workload`,
23 | CompletionOptions: cobra.CompletionOptions{
24 | DisableDefaultCmd: true,
25 | },
26 | Example: "orbitctl forward --deployment depolyment-a --namespace ns-a --containerPort 8080 --localPort 8080",
27 | Version: "0.2.0",
28 | }
29 |
30 | func init() {
31 | rootCmd.AddCommand(command.ForwardCommand())
32 | rootCmd.AddCommand(command.UninstallCommand())
33 | }
34 |
35 | func main() {
36 | cobra.CheckErr(rootCmd.Execute())
37 | }
38 |
--------------------------------------------------------------------------------
/pkg/cli/command/uninstall.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode authors.
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | package command
14 |
15 | import (
16 | "github.com/spf13/cobra"
17 | "kubeorbit.io/pkg/cli/client"
18 | "kubeorbit.io/pkg/cli/core"
19 | )
20 |
21 | func UninstallCommand() *cobra.Command {
22 | request := &core.UninstallRequest{}
23 | cmd := &cobra.Command{
24 | Use: "uninstall",
25 | Long: `Uninstall orbit agent and resources`,
26 | Run: func(cmd *cobra.Command, args []string) {
27 | err := core.Uninstall(request)
28 | if err != nil {
29 | cmd.PrintErr(err)
30 | }
31 | },
32 | }
33 | cmd.Flags().StringVarP(&request.Namespace, "namespace", "n", client.GetDefaultNamespace(), "Namespace for uninstall")
34 | cmd.Flags().StringVar(&request.DeploymentName, "deployment", "", "Deployment name for uninstall")
35 | return cmd
36 | }
37 |
--------------------------------------------------------------------------------
/api/v1alpha1/groupversion_info.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode 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 network v1alpha1 API group
18 | //+kubebuilder:object:generate=true
19 | //+groupName=network.kubeorbit.io
20 | package v1alpha1
21 |
22 | import (
23 | "k8s.io/apimachinery/pkg/runtime/schema"
24 | "sigs.k8s.io/controller-runtime/pkg/scheme"
25 | )
26 |
27 | var (
28 | // GroupVersion is group version used to register these objects
29 | GroupVersion = schema.GroupVersion{Group: "network.kubeorbit.io", Version: "v1alpha1"}
30 |
31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme
32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
33 |
34 | // AddToScheme adds the types in this group-version to the given scheme.
35 | AddToScheme = SchemeBuilder.AddToScheme
36 | )
37 |
--------------------------------------------------------------------------------
/pkg/cli/core/portforward.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode authors.
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | package core
14 |
15 | import (
16 | "fmt"
17 | "k8s.io/client-go/tools/portforward"
18 | "k8s.io/client-go/transport/spdy"
19 | "kubeorbit.io/pkg/cli/client"
20 | "net/http"
21 | )
22 |
23 | func portForward(namespace, podName string, localPort, remotePort int, stop chan struct{}) error {
24 | req := client.KubeClient().CoreV1().RESTClient().Post().
25 | Resource("pods").
26 | Namespace(namespace).
27 | Name(podName).
28 | SubResource("portforward")
29 |
30 | transport, upgrader, err := spdy.RoundTripperFor(client.KubeConfig())
31 | if err != nil {
32 | return err
33 | }
34 | dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, http.MethodPost, req.URL())
35 | ports := []string{fmt.Sprintf("%d:%d", localPort, remotePort)}
36 | ready := make(chan struct{})
37 | fw, err := portforward.New(dialer, ports, stop, ready, nil, nil)
38 | if err != nil {
39 | return err
40 | }
41 | return fw.ForwardPorts()
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/cli/command/forward.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode authors.
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | package command
14 |
15 | import (
16 | "github.com/spf13/cobra"
17 | "kubeorbit.io/pkg/cli/client"
18 | "kubeorbit.io/pkg/cli/core"
19 | )
20 |
21 | func ForwardCommand() *cobra.Command {
22 | request := &core.ForwardRequest{}
23 | cmd := &cobra.Command{
24 | Use: "forward",
25 | Long: `Forward a deployment to local`,
26 | Run: func(cmd *cobra.Command, args []string) {
27 | err := core.Forward(request)
28 | if err != nil {
29 | cmd.PrintErr(err)
30 | }
31 | },
32 | }
33 | cmd.Flags().StringVarP(&request.Namespace, "namespace", "n", client.GetDefaultNamespace(), "Namespace for forwarding")
34 | cmd.Flags().StringVar(&request.DeploymentName, "deployment", "", "Deployment Name")
35 | cmd.Flags().IntVar(&request.LocalPort, "localPort", 0, "Local Port")
36 | cmd.Flags().IntVar(&request.ContainerPort, "containerPort", 0, "Container Port")
37 | cmd.MarkFlagRequired("deployment")
38 | cmd.MarkFlagRequired("localPort")
39 | cmd.MarkFlagRequired("containerPort")
40 | return cmd
41 | }
42 |
--------------------------------------------------------------------------------
/config/rbac/role.yaml:
--------------------------------------------------------------------------------
1 |
2 | ---
3 | apiVersion: rbac.authorization.k8s.io/v1
4 | kind: ClusterRole
5 | metadata:
6 | creationTimestamp: null
7 | name: manager-role
8 | rules:
9 | - apiGroups:
10 | - network.kubeorbit.io
11 | resources:
12 | - orbits
13 | verbs:
14 | - create
15 | - delete
16 | - get
17 | - list
18 | - patch
19 | - update
20 | - watch
21 | - apiGroups:
22 | - network.kubeorbit.io
23 | resources:
24 | - orbits/finalizers
25 | verbs:
26 | - update
27 | - apiGroups:
28 | - network.kubeorbit.io
29 | resources:
30 | - orbits/status
31 | verbs:
32 | - get
33 | - patch
34 | - update
35 | - apiGroups:
36 | - network.kubeorbit.io
37 | resources:
38 | - serviceroutes
39 | verbs:
40 | - create
41 | - delete
42 | - get
43 | - list
44 | - patch
45 | - update
46 | - watch
47 | - apiGroups:
48 | - network.kubeorbit.io
49 | resources:
50 | - serviceroutes/finalizers
51 | verbs:
52 | - update
53 | - apiGroups:
54 | - network.kubeorbit.io
55 | resources:
56 | - serviceroutes/status
57 | verbs:
58 | - get
59 | - patch
60 | - update
61 | - apiGroups:
62 | - networking.istio.io
63 | resources:
64 | - destinationrules
65 | verbs:
66 | - create
67 | - delete
68 | - get
69 | - list
70 | - patch
71 | - update
72 | - watch
73 | - apiGroups:
74 | - networking.istio.io
75 | resources:
76 | - envoyfilters
77 | verbs:
78 | - create
79 | - delete
80 | - get
81 | - list
82 | - patch
83 | - update
84 | - watch
85 | - apiGroups:
86 | - networking.istio.io
87 | resources:
88 | - virtualservices
89 | verbs:
90 | - create
91 | - delete
92 | - get
93 | - list
94 | - patch
95 | - update
96 | - watch
97 |
--------------------------------------------------------------------------------
/config/manager/manager.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Namespace
3 | metadata:
4 | labels:
5 | control-plane: controller-manager
6 | name: system
7 | ---
8 | apiVersion: apps/v1
9 | kind: Deployment
10 | metadata:
11 | name: controller-manager
12 | namespace: system
13 | labels:
14 | control-plane: controller-manager
15 | spec:
16 | selector:
17 | matchLabels:
18 | control-plane: controller-manager
19 | replicas: 1
20 | template:
21 | metadata:
22 | annotations:
23 | kubectl.kubernetes.io/default-container: manager
24 | labels:
25 | control-plane: controller-manager
26 | spec:
27 | securityContext:
28 | runAsNonRoot: true
29 | containers:
30 | - command:
31 | - /manager
32 | args:
33 | - --leader-elect
34 | image: teamcode2021/kubeorbit:v0.1.1-v1alpha1
35 | imagePullPolicy: Always
36 | name: manager
37 | securityContext:
38 | allowPrivilegeEscalation: false
39 | livenessProbe:
40 | httpGet:
41 | path: /healthz
42 | port: 8081
43 | initialDelaySeconds: 15
44 | periodSeconds: 20
45 | readinessProbe:
46 | httpGet:
47 | path: /readyz
48 | port: 8081
49 | initialDelaySeconds: 5
50 | periodSeconds: 10
51 | # TODO(user): Configure the resources accordingly based on the project requirements.
52 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
53 | resources:
54 | limits:
55 | cpu: 500m
56 | memory: 128Mi
57 | requests:
58 | cpu: 10m
59 | memory: 64Mi
60 | serviceAccountName: controller-manager
61 | terminationGracePeriodSeconds: 10
62 |
--------------------------------------------------------------------------------
/pkg/cli/logger/logger.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode authors.
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | package logger
14 |
15 | import (
16 | "bytes"
17 | "github.com/sirupsen/logrus"
18 | "log"
19 | "os"
20 | )
21 |
22 | type LogFormat struct {
23 | }
24 |
25 | func (f *LogFormat) Format(entry *logrus.Entry) ([]byte, error) {
26 | var b *bytes.Buffer
27 |
28 | if entry.Buffer != nil {
29 | b = entry.Buffer
30 | } else {
31 | b = &bytes.Buffer{}
32 | }
33 |
34 | b.WriteString(entry.Message)
35 | b.WriteByte('\n')
36 | return b.Bytes(), nil
37 | }
38 |
39 | func init() {
40 | formatter := LogFormat{}
41 | logrus.SetFormatter(&formatter)
42 | log.SetOutput(os.Stdout)
43 | logrus.SetLevel(logrus.InfoLevel)
44 | }
45 |
46 | func Debug(args ...interface{}) {
47 | logrus.Debug(args...)
48 | }
49 |
50 | func Debugf(format string, args ...interface{}) {
51 | logrus.Debugf(format, args...)
52 | }
53 |
54 | func Info(args ...interface{}) {
55 | logrus.Info(args...)
56 | }
57 |
58 | func Infof(format string, args ...interface{}) {
59 | logrus.Infof(format, args...)
60 | }
61 |
62 | func Warn(args ...interface{}) {
63 | logrus.Warn(args...)
64 | }
65 |
66 | func Warnf(format string, args ...interface{}) {
67 | logrus.Warnf(format, args...)
68 | }
69 |
70 | func Error(args ...interface{}) {
71 | logrus.Error(args...)
72 | }
73 |
74 | func Errorf(format string, args ...interface{}) {
75 | logrus.Errorf(format, args...)
76 | }
77 |
78 | func Fatal(args ...interface{}) {
79 | logrus.Fatal(args...)
80 | }
81 |
82 | func Fatalf(format string, args ...interface{}) {
83 | logrus.Fatalf(format, args...)
84 | }
85 |
--------------------------------------------------------------------------------
/api/v1alpha1/orbit_types.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package v1alpha1
18 |
19 | import (
20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21 | )
22 |
23 | type TrafficRulesSpec struct {
24 | Headers map[string]string `json:"headers,omitempty"`
25 | }
26 |
27 | // OrbitSpec defines the desired state of Orbit
28 | type OrbitSpec struct {
29 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
30 | // Important: Run "make" to regenerate code after modifying this file
31 |
32 | MeshProvider string `json:"provider"`
33 | TrafficRules TrafficRulesSpec `json:"trafficRules"`
34 | }
35 |
36 | // OrbitStatus defines the observed state of Orbit
37 | type OrbitStatus struct {
38 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
39 | // Important: Run "make" to regenerate code after modifying this file
40 | Status string `json:"Status"`
41 | }
42 |
43 | //+kubebuilder:object:root=true
44 | //+kubebuilder:subresource:status
45 |
46 | // Orbit is the Schema for the orbits API
47 | type Orbit struct {
48 | metav1.TypeMeta `json:",inline"`
49 | metav1.ObjectMeta `json:"metadata,omitempty"`
50 |
51 | Spec OrbitSpec `json:"spec,omitempty"`
52 | Status OrbitStatus `json:"status,omitempty"`
53 | }
54 |
55 | //+kubebuilder:object:root=true
56 | // OrbitList contains a list of Orbit
57 | type OrbitList struct {
58 | metav1.TypeMeta `json:",inline"`
59 | metav1.ListMeta `json:"metadata,omitempty"`
60 | Items []Orbit `json:"items"`
61 | }
62 |
63 | func init() {
64 | SchemeBuilder.Register(&Orbit{}, &OrbitList{})
65 | }
66 |
--------------------------------------------------------------------------------
/pkg/cli/client/kubernetes.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode authors.
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | package client
14 |
15 | import (
16 | "k8s.io/client-go/kubernetes"
17 | "k8s.io/client-go/rest"
18 | "k8s.io/client-go/tools/clientcmd"
19 | log "kubeorbit.io/pkg/cli/logger"
20 |
21 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
22 | // to ensure that exec-entrypoint and run can make use of them.
23 | _ "k8s.io/client-go/plugin/pkg/client/auth"
24 | )
25 |
26 | var kubeConfig *rest.Config
27 | var kubeClient *kubernetes.Clientset
28 |
29 | func init() {
30 | clusterConfig, err := newClusterConfig()
31 | kubeConfig = clusterConfig
32 | if err != nil {
33 | log.Fatalf("error loading kubeconfig: %v", err)
34 | }
35 | clientSet, err := kubernetes.NewForConfig(clusterConfig)
36 | if err != nil {
37 | log.Fatalf("error loading kubeconfig: %v", err)
38 | }
39 | kubeClient = clientSet
40 | }
41 |
42 | func KubeConfig() *rest.Config {
43 | return kubeConfig
44 | }
45 |
46 | func KubeClient() *kubernetes.Clientset {
47 | return kubeClient
48 | }
49 |
50 | func newClusterConfig() (*rest.Config, error) {
51 | var cfg *rest.Config
52 | var err error
53 | loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
54 | cfg, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}).ClientConfig()
55 | if err != nil {
56 | return nil, err
57 | }
58 |
59 | cfg.QPS = 100
60 | cfg.Burst = 100
61 |
62 | return cfg, nil
63 | }
64 |
65 | // getContextDefaultNamespace returns the default namespace for the current context
66 | func GetDefaultNamespace() string {
67 | clientCfg, err := clientcmd.NewDefaultClientConfigLoadingRules().Load()
68 | if err != nil {
69 | log.Fatalf("error getting default namespace: %v", err)
70 | }
71 |
72 | namespace := clientCfg.Contexts[clientCfg.CurrentContext].Namespace
73 |
74 | if namespace == "" {
75 | namespace = "default"
76 | }
77 | return namespace
78 | }
79 |
--------------------------------------------------------------------------------
/pkg/controllers/suite_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode 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 | "path/filepath"
21 | "testing"
22 |
23 | . "github.com/onsi/ginkgo"
24 | . "github.com/onsi/gomega"
25 | "k8s.io/client-go/kubernetes/scheme"
26 | "k8s.io/client-go/rest"
27 | "sigs.k8s.io/controller-runtime/pkg/client"
28 | "sigs.k8s.io/controller-runtime/pkg/envtest"
29 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer"
30 | logf "sigs.k8s.io/controller-runtime/pkg/log"
31 | "sigs.k8s.io/controller-runtime/pkg/log/zap"
32 |
33 | networkv1alpha1 "kubeorbit.io/api/v1alpha1"
34 | //+kubebuilder:scaffold:imports
35 | )
36 |
37 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to
38 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
39 |
40 | var cfg *rest.Config
41 | var k8sClient client.Client
42 | var testEnv *envtest.Environment
43 |
44 | func TestAPIs(t *testing.T) {
45 | RegisterFailHandler(Fail)
46 |
47 | RunSpecsWithDefaultAndCustomReporters(t,
48 | "Controller Suite",
49 | []Reporter{printer.NewlineReporter{}})
50 | }
51 |
52 | var _ = BeforeSuite(func() {
53 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
54 |
55 | By("bootstrapping test environment")
56 | testEnv = &envtest.Environment{
57 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
58 | ErrorIfCRDPathMissing: true,
59 | }
60 |
61 | cfg, err := testEnv.Start()
62 | Expect(err).NotTo(HaveOccurred())
63 | Expect(cfg).NotTo(BeNil())
64 |
65 | err = networkv1alpha1.AddToScheme(scheme.Scheme)
66 | Expect(err).NotTo(HaveOccurred())
67 |
68 | //+kubebuilder:scaffold:scheme
69 |
70 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
71 | Expect(err).NotTo(HaveOccurred())
72 | Expect(k8sClient).NotTo(BeNil())
73 |
74 | }, 60)
75 |
76 | var _ = AfterSuite(func() {
77 | By("tearing down the test environment")
78 | err := testEnv.Stop()
79 | Expect(err).NotTo(HaveOccurred())
80 | })
81 |
--------------------------------------------------------------------------------
/config/crd/bases/network.kubeorbit.io_orbits.yaml:
--------------------------------------------------------------------------------
1 |
2 | ---
3 | apiVersion: apiextensions.k8s.io/v1
4 | kind: CustomResourceDefinition
5 | metadata:
6 | annotations:
7 | controller-gen.kubebuilder.io/version: v0.7.0
8 | creationTimestamp: null
9 | name: orbits.network.kubeorbit.io
10 | spec:
11 | group: network.kubeorbit.io
12 | names:
13 | kind: Orbit
14 | listKind: OrbitList
15 | plural: orbits
16 | singular: orbit
17 | scope: Namespaced
18 | versions:
19 | - name: v1alpha1
20 | schema:
21 | openAPIV3Schema:
22 | description: Orbit is the Schema for the orbits API
23 | properties:
24 | apiVersion:
25 | description: 'APIVersion defines the versioned schema of this representation
26 | of an object. Servers should convert recognized schemas to the latest
27 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
28 | type: string
29 | kind:
30 | description: 'Kind is a string value representing the REST resource this
31 | object represents. Servers may infer this from the endpoint the client
32 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
33 | type: string
34 | metadata:
35 | type: object
36 | spec:
37 | description: OrbitSpec defines the desired state of Orbit
38 | properties:
39 | provider:
40 | type: string
41 | trafficRules:
42 | properties:
43 | headers:
44 | additionalProperties:
45 | type: string
46 | type: object
47 | type: object
48 | required:
49 | - provider
50 | - trafficRules
51 | type: object
52 | status:
53 | description: OrbitStatus defines the observed state of Orbit
54 | properties:
55 | Status:
56 | description: 'INSERT ADDITIONAL STATUS FIELD - define observed state
57 | of cluster Important: Run "make" to regenerate code after modifying
58 | this file'
59 | type: string
60 | required:
61 | - Status
62 | type: object
63 | type: object
64 | served: true
65 | storage: true
66 | subresources:
67 | status: {}
68 | status:
69 | acceptedNames:
70 | kind: ""
71 | plural: ""
72 | conditions: []
73 | storedVersions: []
74 |
--------------------------------------------------------------------------------
/config/default/kustomization.yaml:
--------------------------------------------------------------------------------
1 | # Adds namespace to all resources.
2 | namespace: kubeorbit-system
3 |
4 | # Value of this field is prepended to the
5 | # names of all resources, e.g. a deployment named
6 | # "wordpress" becomes "alices-wordpress".
7 | # Note that it should also match with the prefix (text before '-') of the namespace
8 | # field above.
9 | namePrefix: kubeorbit-
10 |
11 | # Labels to add to all resources and selectors.
12 | #commonLabels:
13 | # someName: someValue
14 |
15 | bases:
16 | - ../crd
17 | - ../rbac
18 | - ../manager
19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
20 | # crd/kustomization.yaml
21 | - ../webhook
22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
23 | - ../certmanager
24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
25 | #- ../prometheus
26 |
27 | patchesStrategicMerge:
28 | # Protect the /metrics endpoint by putting it behind auth.
29 | # If you want your controller-manager to expose the /metrics
30 | # endpoint w/o any authn/z, please comment the following line.
31 | - manager_auth_proxy_patch.yaml
32 |
33 | # Mount the controller config file for loading manager configurations
34 | # through a ComponentConfig type
35 | #- manager_config_patch.yaml
36 |
37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
38 | # crd/kustomization.yaml
39 | - manager_webhook_patch.yaml
40 |
41 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.
42 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
43 | # 'CERTMANAGER' needs to be enabled to use ca injection
44 | - webhookcainjection_patch.yaml
45 |
46 | # the following config is for teaching kustomize how to do var substitution
47 | vars:
48 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
49 | - name: CERTIFICATE_NAMESPACE # namespace of the certificate CR
50 | objref:
51 | kind: Certificate
52 | group: cert-manager.io
53 | version: v1
54 | name: serving-cert # this name should match the one in certificate.yaml
55 | fieldref:
56 | fieldpath: metadata.namespace
57 | - name: CERTIFICATE_NAME
58 | objref:
59 | kind: Certificate
60 | group: cert-manager.io
61 | version: v1
62 | name: serving-cert # this name should match the one in certificate.yaml
63 | - name: SERVICE_NAMESPACE # namespace of the service
64 | objref:
65 | kind: Service
66 | version: v1
67 | name: webhook-service
68 | fieldref:
69 | fieldpath: metadata.namespace
70 | - name: SERVICE_NAME
71 | objref:
72 | kind: Service
73 | version: v1
74 | name: webhook-service
75 |
--------------------------------------------------------------------------------
/api/v1/pod_webhook.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package v1
18 |
19 | import (
20 | "context"
21 | "encoding/json"
22 | corev1 "k8s.io/api/core/v1"
23 | "net/http"
24 | "sigs.k8s.io/controller-runtime/pkg/client"
25 | logf "sigs.k8s.io/controller-runtime/pkg/log"
26 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
27 | )
28 |
29 | var podlog = logf.Log.WithName("pod-resource")
30 |
31 | // +kubebuilder:webhook:path=/mutate-core-v1-pod,mutating=true,failurePolicy=fail,groups=core,resources=pods,verbs=create;update,versions=v1,admissionReviewVersions=v1,sideEffects=none,name=mpod.kb.io
32 |
33 | type PodLabelMutate struct {
34 | Client client.Client
35 | decoder *admission.Decoder
36 | }
37 |
38 | func NewPodSideCarMutate(c client.Client) admission.Handler {
39 | return &PodLabelMutate{Client: c}
40 | }
41 |
42 | const (
43 | istioProxyName = "istio-proxy"
44 | channelEnv = "ORBIT_CHANNEL_TAG"
45 | )
46 |
47 | // PodLabelMutate injects a key-value pair to istio-proxy sidecar if a specific label exists.
48 | func (v *PodLabelMutate) Handle(ctx context.Context, req admission.Request) admission.Response {
49 | pod := &corev1.Pod{}
50 |
51 | err := v.decoder.Decode(req, pod)
52 | if err != nil {
53 | return admission.Errored(http.StatusBadRequest, err)
54 | }
55 |
56 | tag := ""
57 | if val, ok := pod.Labels[KUBEORBIT_CHANNEL_LABEL]; ok {
58 | tag = val
59 | }
60 |
61 | sidecarIndex := -1
62 | for k, container := range pod.Spec.Containers {
63 | if container.Name == istioProxyName {
64 | sidecarIndex = k
65 | }
66 | }
67 |
68 | if tag != "" && sidecarIndex > 0 {
69 | pod.Spec.Containers[sidecarIndex].Env = append(
70 | pod.Spec.Containers[sidecarIndex].Env,
71 | corev1.EnvVar{
72 | Name: channelEnv,
73 | Value: tag,
74 | })
75 | }
76 |
77 | marshaledPod, err := json.Marshal(pod)
78 | if err != nil {
79 | return admission.Errored(http.StatusInternalServerError, err)
80 | }
81 |
82 | return admission.PatchResponseFromRaw(req.Object.Raw, marshaledPod)
83 | }
84 |
85 | // PodLabelMutate implements admission.DecoderInjector.
86 | // A decoder will be automatically injected.
87 |
88 | // InjectDecoder injects the decoder.
89 | func (v *PodLabelMutate) InjectDecoder(d *admission.Decoder) error {
90 | v.decoder = d
91 | return nil
92 | }
93 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Like KubeOrbit idea? ⭐ Give us a GitHub Star! ⭐
12 |
13 | 
14 | [](https://github.com/teamcode-inc/kubeorbit/blob/master/LICENSE)
15 | [](https://discord.gg/5XaTS9VArf)
16 |
17 |
18 | **KubeOrbit** is an open-source tool that turns easy apps testing&debuging on Kubernetes in a new way. Our KubeOrbit is meant to create a channel automatically. You can *test* your *cloud-native* applications through this channel in a *hands-free* style.
19 |
20 | It solves the following problems during integration tests:
21 | - Under limited resource and restricted environment, developers in a team may be blocked by others who are testing their own functionalities, and it slows down the development progress.
22 | - On the other hand, an unstable feature being deployed to a microservice may cause entire system crash.
23 |
24 |
25 |
26 |
27 |
28 | ## Features
29 | From now on, stop testing your application in local infra naively. Also, no more endless efforts in managing various cloud-based test environments.
30 | - **KubeOrbit CLI**: just using one command, forward the traffic from in-cluster service to local service in a flash, no matter your service discovery is based on Eureka, Consul, Nacos or Kubernetes SVC.
31 | - **Protocol support**: various protocols based on Layer-7 are supported. HTTP, gRPC, Thrift, Dubbo ...
32 | - **Workload label**: Label your workload by creating a new channel so that your request can be directed to the appropriate workload replica. This enables you to collaborate with your colleagues to test and debug the same feature..
33 |
34 |
35 | ## Getting Started
36 | With the following tutorials:
37 |
38 | **KubeOrbit CLI**:
39 | * [Getting started](https://www.kubeorbit.io/docs/getting-started)
40 | * [How to build](https://www.kubeorbit.io/docs/how-to-build)
41 |
42 |
43 | ## Contributing
44 | We're a warm and welcoming community of open source contributors. Please join. All types of contributions are welcome. Be sure to read our [Contributing Guide](./CONTRIBUTING.md) before submitting a Pull Request to the project.
45 |
46 | ## Community
47 | #### Discord
48 |
49 | Join the [KubeOrbit Discord channel](https://discord.gg/5XaTS9VArf) to chat with KubeOrbit developers and other users. This is a good place to learn about KubeOrbit, ask questions, and share your experiences.
50 |
51 | ## License
52 | The KubeOrbit user space components are licensed under the [Apache License, Version 2.0](./LICENSE).
53 |
--------------------------------------------------------------------------------
/pkg/cli/core/uninstall.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode authors.
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | package core
14 |
15 | import (
16 | "context"
17 | apps "k8s.io/api/apps/v1"
18 | meta "k8s.io/apimachinery/pkg/apis/meta/v1"
19 | "k8s.io/apimachinery/pkg/labels"
20 | "kubeorbit.io/pkg/cli/client"
21 | log "kubeorbit.io/pkg/cli/logger"
22 | )
23 |
24 | type UninstallRequest struct {
25 | Namespace string
26 | DeploymentName string
27 | }
28 |
29 | func Uninstall(r *UninstallRequest) error {
30 | if r.DeploymentName != "" {
31 | // single deployment
32 | deployment, err := client.KubeClient().AppsV1().Deployments(r.Namespace).Get(context.TODO(), r.DeploymentName, meta.GetOptions{})
33 | if err != nil {
34 | return err
35 | }
36 | err = uninstallDeployment(deployment)
37 | if err != nil {
38 | return err
39 | }
40 | } else {
41 | // entire namespace
42 | deployments, err := client.KubeClient().AppsV1().Deployments(r.Namespace).List(context.TODO(), meta.ListOptions{
43 | LabelSelector: labels.Set(map[string]string{ProxyLabel: "true"}).AsSelector().String(),
44 | })
45 | if err != nil {
46 | return err
47 | }
48 | for _, deployment := range deployments.Items {
49 | err := uninstallDeployment(&deployment)
50 | if err != nil {
51 | return err
52 | }
53 | }
54 | }
55 | log.Infof("workload uninstallation successful")
56 | return nil
57 | }
58 |
59 | func revertDeployment(deployment *apps.Deployment) {
60 | if deployment.Labels[ProxyLabel] != "true" {
61 | return
62 | }
63 | deployment.Spec.Template.Spec.InitContainers = filterNoneProxyInitContainers(deployment.Spec.Template.Spec.InitContainers)
64 | deployment.Spec.Template.Spec.Containers = filterNoneProxyContainers(deployment.Spec.Template.Spec.Containers)
65 | desireReplicas := getDesiredReplicas()
66 | delete(deployment.ObjectMeta.Labels, ProxyLabel)
67 | delete(deployment.ObjectMeta.Labels, ReplicasLabel)
68 | delete(deployment.Spec.Template.ObjectMeta.Labels, ProxyLabel)
69 | delete(deployment.Spec.Template.ObjectMeta.Labels, ProxyId)
70 | deployment.ObjectMeta.ResourceVersion = ""
71 | deployment.Spec.Replicas = &desireReplicas
72 | deployment.Status = apps.DeploymentStatus{}
73 | }
74 |
75 | func uninstallDeployment(deployment *apps.Deployment) error {
76 | if deployment.Labels[ProxyLabel] != "true" {
77 | return nil
78 | }
79 | revertDeployment(deployment)
80 | err := client.KubeClient().AppsV1().Deployments(deployment.Namespace).Delete(context.TODO(), deployment.Name, meta.DeleteOptions{})
81 | if err != nil {
82 | return err
83 | }
84 | _, err = client.KubeClient().AppsV1().Deployments(deployment.Namespace).Create(context.TODO(), deployment, meta.CreateOptions{})
85 | if err != nil {
86 | return err
87 | }
88 | return nil
89 | }
90 |
--------------------------------------------------------------------------------
/api/v1alpha1/serviceroute_types.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode authors.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 |
17 | package v1alpha1
18 |
19 | import (
20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21 | )
22 |
23 | type TrafficRouteSpec struct {
24 | TrafficSubset []*Subset `json:"routes"`
25 | Default map[string]string `json:"default"`
26 | }
27 |
28 | type HTTPMatchRequest struct {
29 | Headers map[string]*StringMatch `json:"headers,omitempty"`
30 | }
31 |
32 | type Subset struct {
33 | // Name of the subset. The service name and the subset name can
34 | // be used for traffic splitting in a route rule.
35 | Name string `json:"name,omitempty"`
36 | // Labels apply a filter over the endpoints of a service in the
37 | // service registry. See route rules for examples of usage.
38 | Labels map[string]string `json:"labels,omitempty"`
39 | Headers map[string]*StringMatch `json:"headers,omitempty"`
40 | }
41 |
42 | // ServiceRouteSpec defines the desired state of ServiceRoute
43 | type ServiceRouteSpec struct {
44 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
45 | // Important: Run "make" to regenerate code after modifying this file
46 | TrafficRoutes TrafficRouteSpec `json:"trafficRoutes"`
47 | Name string `json:"name"`
48 | }
49 |
50 | // ServiceRouteStatus defines the observed state of ServiceRoute
51 | type ServiceRouteStatus struct {
52 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
53 | // Important: Run "make" to regenerate code after modifying this file
54 | }
55 |
56 | //+kubebuilder:object:root=true
57 | //+kubebuilder:subresource:status
58 |
59 | // ServiceRoute is the Schema for the serviceroutes API
60 | type ServiceRoute struct {
61 | metav1.TypeMeta `json:",inline"`
62 | metav1.ObjectMeta `json:"metadata,omitempty"`
63 |
64 | Spec ServiceRouteSpec `json:"spec,omitempty"`
65 | Status ServiceRouteStatus `json:"status,omitempty"`
66 | }
67 |
68 | //+kubebuilder:object:root=true
69 |
70 | // ServiceRouteList contains a list of ServiceRoute
71 | type ServiceRouteList struct {
72 | metav1.TypeMeta `json:",inline"`
73 | metav1.ListMeta `json:"metadata,omitempty"`
74 | Items []ServiceRoute `json:"items"`
75 | }
76 |
77 | func (c *ServiceRoute) GetServiceName() (serviceName string) {
78 | if c.Spec.Name != "" {
79 | serviceName = c.Spec.Name
80 | }
81 | return
82 | }
83 |
84 | type StringMatch struct {
85 | // Specified exactly one of the fields below.
86 |
87 | // exact string match
88 | Exact string `json:"exact,omitempty"`
89 |
90 | // prefix-based match
91 | Prefix string `json:"prefix,omitempty"`
92 |
93 | // suffix-based match.
94 | Suffix string `json:"suffix,omitempty"`
95 |
96 | // ECMAscript style regex-based match
97 | Regex string `json:"regex,omitempty"`
98 | }
99 |
100 | func init() {
101 | SchemeBuilder.Register(&ServiceRoute{}, &ServiceRouteList{})
102 | }
103 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module kubeorbit.io
2 |
3 | go 1.17
4 |
5 | require (
6 | github.com/go-logr/logr v1.2.0
7 | github.com/gogo/protobuf v1.3.2
8 | github.com/google/go-cmp v0.5.5
9 | github.com/onsi/ginkgo v1.16.5
10 | github.com/onsi/gomega v1.17.0
11 | github.com/satori/go.uuid v1.2.0
12 | github.com/sirupsen/logrus v1.8.1
13 | github.com/spf13/cobra v1.2.1
14 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
15 | istio.io/api v0.0.0-20220113014359-2bcfbc334255
16 | istio.io/client-go v1.12.1
17 | k8s.io/api v0.23.0
18 | k8s.io/apimachinery v0.23.0
19 | k8s.io/client-go v0.23.0
20 | sigs.k8s.io/controller-runtime v0.11.0
21 | )
22 |
23 | require (
24 | cloud.google.com/go v0.81.0 // indirect
25 | github.com/Azure/go-autorest v14.2.0+incompatible // indirect
26 | github.com/Azure/go-autorest/autorest v0.11.18 // indirect
27 | github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect
28 | github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
29 | github.com/Azure/go-autorest/logger v0.2.1 // indirect
30 | github.com/Azure/go-autorest/tracing v0.6.0 // indirect
31 | github.com/beorn7/perks v1.0.1 // indirect
32 | github.com/cespare/xxhash/v2 v2.1.1 // indirect
33 | github.com/davecgh/go-spew v1.1.1 // indirect
34 | github.com/evanphx/json-patch v4.12.0+incompatible // indirect
35 | github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
36 | github.com/fsnotify/fsnotify v1.5.1 // indirect
37 | github.com/go-logr/zapr v1.2.0 // indirect
38 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
39 | github.com/golang/protobuf v1.5.2 // indirect
40 | github.com/google/gofuzz v1.1.0 // indirect
41 | github.com/google/uuid v1.1.2 // indirect
42 | github.com/googleapis/gnostic v0.5.5 // indirect
43 | github.com/imdario/mergo v0.3.12 // indirect
44 | github.com/inconshreveable/mousetrap v1.0.0 // indirect
45 | github.com/json-iterator/go v1.1.12 // indirect
46 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
47 | github.com/moby/spdystream v0.2.0 // 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/nxadm/tail v1.4.8 // indirect
51 | github.com/pkg/errors v0.9.1 // indirect
52 | github.com/prometheus/client_golang v1.11.0 // indirect
53 | github.com/prometheus/client_model v0.2.0 // indirect
54 | github.com/prometheus/common v0.28.0 // indirect
55 | github.com/prometheus/procfs v0.6.0 // indirect
56 | github.com/spf13/pflag v1.0.5 // indirect
57 | go.uber.org/atomic v1.7.0 // indirect
58 | go.uber.org/multierr v1.6.0 // indirect
59 | go.uber.org/zap v1.19.1 // indirect
60 | golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect
61 | golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
62 | golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8 // indirect
63 | golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
64 | golang.org/x/text v0.3.7 // indirect
65 | golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
66 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
67 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
68 | google.golang.org/appengine v1.6.7 // indirect
69 | google.golang.org/protobuf v1.27.1 // indirect
70 | gopkg.in/inf.v0 v0.9.1 // indirect
71 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
72 | gopkg.in/yaml.v2 v2.4.0 // indirect
73 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
74 | istio.io/gogo-genproto v0.0.0-20211208193508-5ab4acc9eb1e // indirect
75 | k8s.io/apiextensions-apiserver v0.23.0 // indirect
76 | k8s.io/component-base v0.23.0 // indirect
77 | k8s.io/klog/v2 v2.30.0 // indirect
78 | k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
79 | k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect
80 | sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
81 | sigs.k8s.io/structured-merge-diff/v4 v4.2.0 // indirect
82 | sigs.k8s.io/yaml v1.3.0 // indirect
83 | )
84 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode 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 | v1 "kubeorbit.io/api/v1"
22 | controllers "kubeorbit.io/pkg/controllers"
23 | "os"
24 | "sigs.k8s.io/controller-runtime/pkg/webhook"
25 |
26 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
27 | // to ensure that exec-entrypoint and run can make use of them.
28 | _ "k8s.io/client-go/plugin/pkg/client/auth"
29 |
30 | istiov1 "istio.io/client-go/pkg/apis/networking/v1alpha3"
31 | "k8s.io/apimachinery/pkg/runtime"
32 | utilruntime "k8s.io/apimachinery/pkg/util/runtime"
33 | clientgoscheme "k8s.io/client-go/kubernetes/scheme"
34 | ctrl "sigs.k8s.io/controller-runtime"
35 | "sigs.k8s.io/controller-runtime/pkg/healthz"
36 | "sigs.k8s.io/controller-runtime/pkg/log/zap"
37 |
38 | orbitv1alpha1 "kubeorbit.io/api/v1alpha1"
39 | routev1alpha1 "kubeorbit.io/api/v1alpha1"
40 | //+kubebuilder:scaffold:imports
41 | )
42 |
43 | var (
44 | scheme = runtime.NewScheme()
45 | setupLog = ctrl.Log.WithName("setup")
46 | )
47 |
48 | func init() {
49 | utilruntime.Must(clientgoscheme.AddToScheme(scheme))
50 | utilruntime.Must(orbitv1alpha1.AddToScheme(scheme))
51 | utilruntime.Must(routev1alpha1.AddToScheme(scheme))
52 | utilruntime.Must(istiov1.AddToScheme(scheme))
53 | //+kubebuilder:scaffold:scheme
54 | }
55 |
56 | func main() {
57 | var metricsAddr string
58 | var enableLeaderElection bool
59 | var probeAddr string
60 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
61 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
62 | flag.BoolVar(&enableLeaderElection, "leader-elect", false,
63 | "Enable leader election for controller manager. "+
64 | "Enabling this will ensure there is only one active controller manager.")
65 | opts := zap.Options{
66 | Development: true,
67 | }
68 | opts.BindFlags(flag.CommandLine)
69 | flag.Parse()
70 |
71 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
72 |
73 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
74 | Scheme: scheme,
75 | MetricsBindAddress: metricsAddr,
76 | Port: 9443,
77 | HealthProbeBindAddress: probeAddr,
78 | LeaderElection: enableLeaderElection,
79 | LeaderElectionID: "35a4e702.network.kubeorbit.io",
80 | })
81 | if err != nil {
82 | setupLog.Error(err, "unable to start manager")
83 | os.Exit(1)
84 | }
85 |
86 | if err = (&controllers.OrbitReconciler{
87 | Client: mgr.GetClient(),
88 | Scheme: mgr.GetScheme(),
89 | Log: mgr.GetLogger(),
90 | }).SetupWithManager(mgr); err != nil {
91 | setupLog.Error(err, "unable to create controller", "controller", "Orbit")
92 | os.Exit(1)
93 | }
94 | if err = (&controllers.ServiceRouteReconciler{
95 | Client: mgr.GetClient(),
96 | Scheme: mgr.GetScheme(),
97 | Log: mgr.GetLogger(),
98 | }).SetupWithManager(mgr); err != nil {
99 | setupLog.Error(err, "unable to create controller", "controller", "ServiceRoute")
100 | os.Exit(1)
101 | }
102 |
103 | mgr.GetWebhookServer().Register("/mutate-core-v1-pod", &webhook.Admission{Handler: v1.NewPodSideCarMutate(mgr.GetClient())})
104 |
105 | //+kubebuilder:scaffold:builder
106 |
107 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
108 | setupLog.Error(err, "unable to set up health check")
109 | os.Exit(1)
110 | }
111 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
112 | setupLog.Error(err, "unable to set up ready check")
113 | os.Exit(1)
114 | }
115 |
116 | setupLog.Info("starting manager")
117 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
118 | setupLog.Error(err, "problem running manager")
119 | os.Exit(1)
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/pkg/cli/core/channel.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode authors.
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | package core
14 |
15 | import (
16 | "fmt"
17 | "golang.org/x/crypto/ssh"
18 | "io"
19 | "k8s.io/apimachinery/pkg/util/rand"
20 | log "kubeorbit.io/pkg/cli/logger"
21 | "kubeorbit.io/pkg/cli/util"
22 | "net"
23 | "sync"
24 | "time"
25 | )
26 |
27 | func connectSSHServer(address, privateKey string) (*ssh.Client, error) {
28 | signer, err := ssh.ParsePrivateKey([]byte(privateKey))
29 | if err != nil {
30 | return nil, err
31 | }
32 | client, err := ssh.Dial("tcp", address, &ssh.ClientConfig{
33 | User: "root",
34 | Auth: []ssh.AuthMethod{
35 | ssh.PublicKeys(signer),
36 | },
37 | HostKeyCallback: ssh.InsecureIgnoreHostKey(),
38 | })
39 | if err != nil {
40 | return nil, err
41 | }
42 | return client, nil
43 | }
44 |
45 | func findAvailablePort() (int, error) {
46 | for i := 0; i < 10; i++ {
47 | port := rand.Intn(65535-1024) + 1024
48 | if !util.IsAddrAvailable(fmt.Sprintf(":%d", port)) {
49 | return port, nil
50 | }
51 | }
52 | return 0, fmt.Errorf("cannot find available port")
53 | }
54 |
55 | type ChannelListener struct {
56 | Namespace string
57 | PodName string
58 | PrivateKey string
59 | LocalPort int
60 | }
61 |
62 | func (c *ChannelListener) ForwardToLocal() error {
63 | stop := make(chan struct{})
64 | sshForwardPort, err := findAvailablePort()
65 | go func() {
66 | err = portForward(c.Namespace, c.PodName, sshForwardPort, ProxySSHPort, stop)
67 | if err != nil {
68 | log.Errorf("port forward err: %v", err)
69 | }
70 | }()
71 | sshForwardAddress := fmt.Sprintf(":%d", sshForwardPort)
72 | localPortAddress := fmt.Sprintf(":%d", c.LocalPort)
73 | containerProxyAddress := fmt.Sprintf("0.0.0.0:%d", ProxyPort)
74 | err = waitForAddress(sshForwardAddress, 30*time.Second)
75 | if err != nil {
76 | return err
77 | }
78 | log.Infof("ssh forwarded localPort %d to remotePort %d", sshForwardPort, ProxySSHPort)
79 | sshClient, err := connectSSHServer(sshForwardAddress, c.PrivateKey)
80 | if err != nil {
81 | return err
82 | }
83 | listener, err := sshClient.Listen("tcp", containerProxyAddress)
84 | if err != nil {
85 | return err
86 | }
87 | log.Infof("channel connected, you can start testing your service")
88 | for {
89 | sshConn, err := listener.Accept()
90 | if err != nil {
91 | log.Errorf("connection err %v", err)
92 | break
93 | }
94 | localConn, err := net.Dial("tcp", localPortAddress)
95 | if err != nil {
96 | sshConn.Close()
97 | log.Errorf("connection err %v", err)
98 | break
99 | }
100 | go func() {
101 | var wg sync.WaitGroup
102 | wg.Add(1)
103 | go func() {
104 | defer func() {
105 | if r := recover(); r != nil {
106 | wg.Done()
107 | }
108 | }()
109 | io.Copy(sshConn, localConn)
110 | wg.Done()
111 | sshConn.Close()
112 | }()
113 | wg.Add(1)
114 | go func() {
115 | defer func() {
116 | if r := recover(); r != nil {
117 | wg.Done()
118 | }
119 | }()
120 | io.Copy(localConn, sshConn)
121 | wg.Done()
122 | localConn.Close()
123 | }()
124 | wg.Wait()
125 | }()
126 | }
127 | return nil
128 | }
129 |
130 | func waitForAddress(address string, timeOut time.Duration) error {
131 | var depChan = make(chan struct{})
132 | var wg sync.WaitGroup
133 | wg.Add(1)
134 | go func() {
135 | go func(address string) {
136 | defer wg.Done()
137 | for {
138 | conn, err := net.Dial("tcp", address)
139 | if err == nil {
140 | conn.Close()
141 | return
142 | }
143 | time.Sleep(1 * time.Second)
144 | }
145 | }(address)
146 | wg.Wait()
147 | close(depChan)
148 | }()
149 |
150 | select {
151 | case <-depChan:
152 | return nil
153 | case <-time.After(timeOut):
154 | return fmt.Errorf("address %s aren't ready in %d", address, timeOut)
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/config/crd/bases/network.kubeorbit.io_serviceroutes.yaml:
--------------------------------------------------------------------------------
1 |
2 | ---
3 | apiVersion: apiextensions.k8s.io/v1
4 | kind: CustomResourceDefinition
5 | metadata:
6 | annotations:
7 | controller-gen.kubebuilder.io/version: v0.7.0
8 | creationTimestamp: null
9 | name: serviceroutes.network.kubeorbit.io
10 | spec:
11 | group: network.kubeorbit.io
12 | names:
13 | kind: ServiceRoute
14 | listKind: ServiceRouteList
15 | plural: serviceroutes
16 | singular: serviceroute
17 | scope: Namespaced
18 | versions:
19 | - name: v1alpha1
20 | schema:
21 | openAPIV3Schema:
22 | description: ServiceRoute is the Schema for the serviceroutes API
23 | properties:
24 | apiVersion:
25 | description: 'APIVersion defines the versioned schema of this representation
26 | of an object. Servers should convert recognized schemas to the latest
27 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
28 | type: string
29 | kind:
30 | description: 'Kind is a string value representing the REST resource this
31 | object represents. Servers may infer this from the endpoint the client
32 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
33 | type: string
34 | metadata:
35 | type: object
36 | spec:
37 | description: ServiceRouteSpec defines the desired state of ServiceRoute
38 | properties:
39 | name:
40 | type: string
41 | trafficRoutes:
42 | description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
43 | Important: Run "make" to regenerate code after modifying this file'
44 | properties:
45 | default:
46 | additionalProperties:
47 | type: string
48 | type: object
49 | routes:
50 | items:
51 | properties:
52 | headers:
53 | additionalProperties:
54 | properties:
55 | exact:
56 | description: exact string match
57 | type: string
58 | prefix:
59 | description: prefix-based match
60 | type: string
61 | regex:
62 | description: ECMAscript style regex-based match
63 | type: string
64 | suffix:
65 | description: suffix-based match.
66 | type: string
67 | type: object
68 | type: object
69 | labels:
70 | additionalProperties:
71 | type: string
72 | description: Labels apply a filter over the endpoints of
73 | a service in the service registry. See route rules for
74 | examples of usage.
75 | type: object
76 | name:
77 | description: Name of the subset. The service name and the
78 | subset name can be used for traffic splitting in a route
79 | rule.
80 | type: string
81 | type: object
82 | type: array
83 | required:
84 | - default
85 | - routes
86 | type: object
87 | required:
88 | - name
89 | - trafficRoutes
90 | type: object
91 | status:
92 | description: ServiceRouteStatus defines the observed state of ServiceRoute
93 | type: object
94 | type: object
95 | served: true
96 | storage: true
97 | subresources:
98 | status: {}
99 | status:
100 | acceptedNames:
101 | kind: ""
102 | plural: ""
103 | conditions: []
104 | storedVersions: []
105 |
--------------------------------------------------------------------------------
/pkg/cli/core/proxy_manager.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode authors.
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | package core
14 |
15 | import (
16 | "crypto/md5"
17 | "encoding/base64"
18 | "encoding/hex"
19 | uuid "github.com/satori/go.uuid"
20 | core "k8s.io/api/core/v1"
21 | "k8s.io/apimachinery/pkg/api/resource"
22 | "k8s.io/apimachinery/pkg/util/intstr"
23 | "strconv"
24 | )
25 |
26 | const (
27 | ProxyInitContainer = "kubeorbit-proxy-init"
28 | ProxyContainer = "kubeorbit-proxy"
29 | ProxyLabel = "kubeorbit.io/proxy-forward"
30 | ProxyId = "kubeorbit.io/proxy-id"
31 | ReplicasLabel = "kubeorbit.io/workload-actual-replicas"
32 | ProxySSHPort = 2222
33 | ProxyPort = 18201
34 | )
35 |
36 | func constructInitContainer(containerPort int) core.Container {
37 | privileged := true
38 | runAsUser := int64(0)
39 | runAsGroup := int64(0)
40 | return core.Container{
41 | Name: ProxyInitContainer,
42 | Image: "soarinferret/iptablesproxy:latest",
43 | ImagePullPolicy: core.PullIfNotPresent,
44 | Command: []string{"iptables"},
45 | Args: []string{
46 | "-t",
47 | "nat",
48 | "-A",
49 | "PREROUTING",
50 | "-p",
51 | "tcp",
52 | "--dport",
53 | //TODO forward all port by default
54 | strconv.Itoa(containerPort),
55 | "-j",
56 | "REDIRECT",
57 | "--to-ports",
58 | strconv.Itoa(ProxyPort),
59 | },
60 | Resources: core.ResourceRequirements{
61 | Limits: core.ResourceList{
62 | core.ResourceCPU: resource.MustParse("100m"),
63 | core.ResourceMemory: resource.MustParse("50Mi"),
64 | },
65 | Requests: core.ResourceList{
66 | core.ResourceCPU: resource.MustParse("10m"),
67 | core.ResourceMemory: resource.MustParse("10Mi"),
68 | },
69 | },
70 | SecurityContext: &core.SecurityContext{
71 | Capabilities: &core.Capabilities{
72 | Add: []core.Capability{
73 | "NET_ADMIN",
74 | "NET_RAW",
75 | },
76 | Drop: []core.Capability{
77 | "ALL",
78 | },
79 | },
80 | Privileged: &privileged,
81 | RunAsUser: &runAsUser,
82 | RunAsGroup: &runAsGroup,
83 | },
84 | }
85 | }
86 |
87 | func constructProxyContainer(RSAPublicKey string) core.Container {
88 | return core.Container{
89 | Name: ProxyContainer,
90 | Image: "teamcode2021/orbit-proxy:latest",
91 | ImagePullPolicy: core.PullIfNotPresent,
92 | Resources: core.ResourceRequirements{
93 | Limits: core.ResourceList{
94 | core.ResourceCPU: resource.MustParse("100m"),
95 | core.ResourceMemory: resource.MustParse("50Mi"),
96 | },
97 | Requests: core.ResourceList{
98 | core.ResourceCPU: resource.MustParse("10m"),
99 | core.ResourceMemory: resource.MustParse("10Mi"),
100 | },
101 | },
102 | Env: []core.EnvVar{
103 | {
104 | Name: "RSAPublicKey",
105 | Value: base64.StdEncoding.EncodeToString([]byte(RSAPublicKey)),
106 | },
107 | },
108 | Ports: []core.ContainerPort{
109 | {
110 | ContainerPort: ProxySSHPort,
111 | },
112 | {
113 | ContainerPort: ProxyPort,
114 | },
115 | },
116 | LivenessProbe: &core.Probe{
117 | ProbeHandler: core.ProbeHandler{
118 | TCPSocket: &core.TCPSocketAction{
119 | Port: intstr.IntOrString{
120 | IntVal: ProxySSHPort,
121 | },
122 | },
123 | },
124 | },
125 | }
126 | }
127 |
128 | func isProxyInitContainer(containerName string) bool {
129 | if containerName == ProxyInitContainer {
130 | return true
131 | }
132 | return false
133 | }
134 |
135 | func filterNoneProxyInitContainers(containers []core.Container) []core.Container {
136 | var initContainers []core.Container
137 | for _, container := range containers {
138 | if container.Name != ProxyInitContainer {
139 | initContainers = append(initContainers, container)
140 | }
141 | }
142 | return initContainers
143 | }
144 |
145 | func filterNoneProxyContainers(containers []core.Container) []core.Container {
146 | var initContainers []core.Container
147 | for _, container := range containers {
148 | if container.Name != ProxyContainer {
149 | initContainers = append(initContainers, container)
150 | }
151 | }
152 | return initContainers
153 | }
154 |
155 | func getDesiredReplicas() int32 {
156 | return 1
157 | }
158 |
159 | func generateProxyId() string {
160 | uuid := uuid.NewV4()
161 | hash := md5.Sum(uuid.Bytes())
162 | return hex.EncodeToString(hash[:])[8:16]
163 | }
164 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | # Image URL to use all building/pushing image targets
3 | IMG ?= controller:latest
4 | # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
5 | ENVTEST_K8S_VERSION = 1.22
6 |
7 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
8 | ifeq (,$(shell go env GOBIN))
9 | GOBIN=$(shell go env GOPATH)/bin
10 | else
11 | GOBIN=$(shell go env GOBIN)
12 | endif
13 |
14 | # Setting SHELL to bash allows bash commands to be executed by recipes.
15 | # This is a requirement for 'setup-envtest.sh' in the test target.
16 | # Options are set to exit when a recipe line exits non-zero or a piped command fails.
17 | SHELL = /usr/bin/env bash -o pipefail
18 | .SHELLFLAGS = -ec
19 |
20 | .PHONY: all
21 | all: build
22 |
23 | ##@ General
24 |
25 | # The help target prints out all targets with their descriptions organized
26 | # beneath their categories. The categories are represented by '##@' and the
27 | # target descriptions by '##'. The awk commands is responsible for reading the
28 | # entire set of makefiles included in this invocation, looking for lines of the
29 | # file as xyz: ## something, and then pretty-format the target and help. Then,
30 | # if there's a line with ##@ something, that gets pretty-printed as a category.
31 | # More info on the usage of ANSI control characters for terminal formatting:
32 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
33 | # More info on the awk command:
34 | # http://linuxcommand.org/lc3_adv_awk.php
35 |
36 | .PHONY: help
37 | help: ## Display this help.
38 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
39 |
40 | ##@ Development
41 |
42 | .PHONY: manifests
43 | manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
44 | $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
45 |
46 | .PHONY: generate
47 | generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
48 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
49 |
50 | .PHONY: fmt
51 | fmt: ## Run go fmt against code.
52 | go fmt ./...
53 |
54 | .PHONY: vet
55 | vet: ## Run go vet against code.
56 | go vet ./...
57 |
58 | .PHONY: test
59 | test: manifests generate fmt vet envtest ## Run tests.
60 | KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out
61 |
62 | ##@ Build
63 |
64 | .PHONY: build
65 | build: generate fmt vet ## Build manager binary.
66 | go build -o bin/manager main.go
67 |
68 | .PHONY: run
69 | run: manifests generate fmt vet ## Run a controller from your host.
70 | go run ./main.go
71 |
72 | .PHONY: docker-build
73 | docker-build: test ## Build docker image with the manager.
74 | docker build -t ${IMG} .
75 |
76 | .PHONY: docker-push
77 | docker-push: ## Push docker image with the manager.
78 | docker push ${IMG}
79 |
80 | ##@ Deployment
81 |
82 | ifndef ignore-not-found
83 | ignore-not-found = false
84 | endif
85 |
86 | .PHONY: install
87 | install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
88 | $(KUSTOMIZE) build config/crd | kubectl apply -f -
89 |
90 | .PHONY: uninstall
91 | uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
92 | $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
93 |
94 | .PHONY: deploy
95 | deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
96 | cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
97 | $(KUSTOMIZE) build config/default | kubectl apply -f -
98 |
99 | .PHONY: undeploy
100 | undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
101 | $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f -
102 |
103 | CONTROLLER_GEN = $(shell pwd)/bin/controller-gen
104 | .PHONY: controller-gen
105 | controller-gen: ## Download controller-gen locally if necessary.
106 | $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.7.0)
107 |
108 | KUSTOMIZE = $(shell pwd)/bin/kustomize
109 | .PHONY: kustomize
110 | kustomize: ## Download kustomize locally if necessary.
111 | $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7)
112 |
113 | ENVTEST = $(shell pwd)/bin/setup-envtest
114 | .PHONY: envtest
115 | envtest: ## Download envtest-setup locally if necessary.
116 | $(call go-get-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest)
117 |
118 | # go-get-tool will 'go get' any package $2 and install it to $1.
119 | PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
120 | define go-get-tool
121 | @[ -f $(1) ] || { \
122 | set -e ;\
123 | TMP_DIR=$$(mktemp -d) ;\
124 | cd $$TMP_DIR ;\
125 | go mod init tmp ;\
126 | echo "Downloading $(2)" ;\
127 | GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\
128 | rm -rf $$TMP_DIR ;\
129 | }
130 | endef
131 |
--------------------------------------------------------------------------------
/pkg/cli/core/forward.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode authors.
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 | http://www.apache.org/licenses/LICENSE-2.0
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | package core
14 |
15 | import (
16 | "context"
17 | "crypto/rand"
18 | "crypto/rsa"
19 | "crypto/x509"
20 | "encoding/pem"
21 | "fmt"
22 | "golang.org/x/crypto/ssh"
23 | apps "k8s.io/api/apps/v1"
24 | core "k8s.io/api/core/v1"
25 | meta "k8s.io/apimachinery/pkg/apis/meta/v1"
26 | "k8s.io/apimachinery/pkg/labels"
27 | "kubeorbit.io/pkg/cli/client"
28 | log "kubeorbit.io/pkg/cli/logger"
29 | "kubeorbit.io/pkg/cli/util"
30 | "os"
31 | "os/signal"
32 | "strconv"
33 | "strings"
34 | "syscall"
35 | )
36 |
37 | func init() {
38 | setupCloseHandler()
39 | }
40 |
41 | var namespace, deploymentName string
42 |
43 | func setupCloseHandler() {
44 | c := make(chan os.Signal, 1)
45 | signal.Notify(c, os.Interrupt, syscall.SIGTERM)
46 | go func() {
47 | <-c
48 | if namespace != "" && deploymentName != "" {
49 | log.Infof("uninstall forward workload %s", deploymentName)
50 | Uninstall(&UninstallRequest{
51 | Namespace: namespace,
52 | DeploymentName: deploymentName,
53 | })
54 | }
55 | os.Exit(0)
56 | }()
57 | }
58 |
59 | type ForwardRequest struct {
60 | DeploymentName string
61 | Namespace string
62 | LocalPort int
63 | ContainerPort int
64 | }
65 |
66 | type Forwarder struct {
67 | DeploymentName string
68 | Namespace string
69 | LocalPort int
70 | ContainerPort int
71 | ProxyId string
72 | PublicKey string
73 | PrivateKey string
74 | }
75 |
76 | func Forward(r *ForwardRequest) error {
77 | if !util.IsAddrAvailable(fmt.Sprintf(":%d", r.LocalPort)) {
78 | return fmt.Errorf("local service is not running at %d, please start your service first", r.LocalPort)
79 | }
80 | deployment, err := client.KubeClient().AppsV1().Deployments(r.Namespace).Get(context.TODO(), r.DeploymentName, meta.GetOptions{})
81 | if err != nil {
82 | return err
83 | }
84 | for _, container := range deployment.Spec.Template.Spec.InitContainers {
85 | if isProxyInitContainer(container.Name) {
86 | return fmt.Errorf("deployment %s has already forwarded", r.DeploymentName)
87 | }
88 | }
89 |
90 | forwarder := r.newForwarder()
91 | forwarder.wrapDeployment(deployment)
92 |
93 | err = client.KubeClient().AppsV1().Deployments(forwarder.Namespace).Delete(context.TODO(), forwarder.DeploymentName, meta.DeleteOptions{})
94 | if err != nil {
95 | return err
96 | }
97 | _, err = client.KubeClient().AppsV1().Deployments(forwarder.Namespace).Create(context.TODO(), deployment, meta.CreateOptions{})
98 | if err != nil {
99 | return err
100 | }
101 | namespace = forwarder.Namespace
102 | deploymentName = forwarder.DeploymentName
103 | log.Infof("workload %s recreated", deploymentName)
104 | defer func() {
105 | Uninstall(&UninstallRequest{
106 | Namespace: namespace,
107 | DeploymentName: deploymentName,
108 | })
109 | }()
110 | watch, _ := client.KubeClient().CoreV1().Pods(forwarder.Namespace).Watch(context.TODO(), meta.ListOptions{
111 | LabelSelector: labels.Set(map[string]string{ProxyId: forwarder.ProxyId}).AsSelector().String(),
112 | })
113 | for {
114 | event := <-watch.ResultChan()
115 | pod, ok := event.Object.(*core.Pod)
116 | if !ok {
117 | break
118 | }
119 |
120 | if pod.Status.Phase == core.PodRunning {
121 | for _, containerStatus := range pod.Status.ContainerStatuses {
122 | if containerStatus.Name == ProxyContainer && containerStatus.State.Running != nil {
123 | channel := &ChannelListener{
124 | Namespace: forwarder.Namespace,
125 | PodName: pod.Name,
126 | PrivateKey: forwarder.PrivateKey,
127 | LocalPort: forwarder.LocalPort,
128 | }
129 | err := channel.ForwardToLocal()
130 | if err != nil {
131 | return err
132 | }
133 | break
134 | }
135 | }
136 | }
137 | }
138 | return nil
139 | }
140 |
141 | func (r *ForwardRequest) newForwarder() *Forwarder {
142 | proxyId := generateProxyId()
143 | publicKey, privateKey, err := makeSSHKeyPair()
144 | if err != nil {
145 | log.Errorf("generate ssh key error")
146 | return nil
147 | }
148 | return &Forwarder{
149 | DeploymentName: r.DeploymentName,
150 | Namespace: r.Namespace,
151 | LocalPort: r.LocalPort,
152 | ContainerPort: r.ContainerPort,
153 | ProxyId: proxyId,
154 | PublicKey: publicKey,
155 | PrivateKey: privateKey,
156 | }
157 | }
158 |
159 | func (f *Forwarder) wrapDeployment(deployment *apps.Deployment) {
160 | actualReplicas := deployment.Spec.Replicas
161 | desireReplicas := getDesiredReplicas()
162 | initContainers := append(deployment.Spec.Template.Spec.InitContainers, constructInitContainer(f.ContainerPort))
163 | containers := append(deployment.Spec.Template.Spec.Containers, constructProxyContainer(f.PublicKey))
164 | deployment.Spec.Replicas = &desireReplicas
165 | deployment.Spec.Template.Spec.InitContainers = initContainers
166 | deployment.Spec.Template.Spec.Containers = containers
167 | deployment.ObjectMeta.Labels[ProxyLabel] = "true"
168 | deployment.ObjectMeta.Labels[ReplicasLabel] = strconv.Itoa(int(*actualReplicas))
169 | deployment.ObjectMeta.ResourceVersion = ""
170 | deployment.Spec.Template.ObjectMeta.Labels[ProxyLabel] = "true"
171 | deployment.Spec.Template.ObjectMeta.Labels[ProxyId] = f.ProxyId
172 | deployment.Status = apps.DeploymentStatus{}
173 | }
174 |
175 | func makeSSHKeyPair() (string, string, error) {
176 | privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
177 | if err != nil {
178 | return "", "", err
179 | }
180 |
181 | // generate and write private key as PEM
182 | var privateKeyBuf strings.Builder
183 |
184 | privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}
185 | if err := pem.Encode(&privateKeyBuf, privateKeyPEM); err != nil {
186 | return "", "", err
187 | }
188 |
189 | // generate and write public key
190 | pub, err := ssh.NewPublicKey(&privateKey.PublicKey)
191 | if err != nil {
192 | return "", "", err
193 | }
194 |
195 | var pubKeyBuf strings.Builder
196 | pubKeyBuf.Write(ssh.MarshalAuthorizedKey(pub))
197 |
198 | return pubKeyBuf.String(), privateKeyBuf.String(), nil
199 | }
200 |
--------------------------------------------------------------------------------
/pkg/controllers/orbit_controller.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode 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 | "github.com/google/go-cmp/cmp"
23 | "k8s.io/apimachinery/pkg/api/errors"
24 | "k8s.io/apimachinery/pkg/runtime/schema"
25 | "log"
26 |
27 | "github.com/go-logr/logr"
28 | "github.com/gogo/protobuf/types"
29 | "istio.io/api/networking/v1alpha3"
30 | istiov1 "istio.io/client-go/pkg/apis/networking/v1alpha3"
31 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32 | "k8s.io/apimachinery/pkg/runtime"
33 | orbitv1alpha1 "kubeorbit.io/api/v1alpha1"
34 | ctrl "sigs.k8s.io/controller-runtime"
35 | "sigs.k8s.io/controller-runtime/pkg/client"
36 | )
37 |
38 | // OrbitReconciler reconciles a Orbit object
39 | type OrbitReconciler struct {
40 | client.Client
41 | Scheme *runtime.Scheme
42 | Log logr.Logger
43 | }
44 |
45 | //+kubebuilder:rbac:groups=network.kubeorbit.io,resources=orbits,verbs=get;list;watch;create;update;patch;delete
46 | //+kubebuilder:rbac:groups=network.kubeorbit.io,resources=orbits/status,verbs=get;update;patch
47 | //+kubebuilder:rbac:groups=network.kubeorbit.io,resources=orbits/finalizers,verbs=update
48 | //+kubebuilder:rbac:groups=networking.istio.io,resources=envoyfilters,verbs=get;list;watch;create;update;patch;delete
49 |
50 | // Reconcile is part of the main kubernetes reconciliation loop which aims to
51 | // move the current state of the cluster closer to the desired state.
52 | // TODO(user): Modify the Reconcile function to compare the state specified by
53 | // the Orbit object against the actual cluster state, and then
54 | // perform operations to make the cluster state reflect the state specified by
55 | // the user.
56 | //
57 | // For more details, check Reconcile and its Result here:
58 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile
59 | func (r *OrbitReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
60 | _ = r.Log.WithValues("orbit", req.NamespacedName)
61 | obj := &orbitv1alpha1.Orbit{}
62 |
63 | if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
64 | log.Println(err, "unable to fetch object")
65 | } else {
66 | if err := r.reconcileEnvoyFilter(obj, req); err != nil {
67 | return ctrl.Result{}, fmt.Errorf("reconcileEnvoyFilter failed: %w", err)
68 | }
69 | }
70 |
71 | return ctrl.Result{}, nil
72 | }
73 |
74 | func (r *OrbitReconciler) reconcileEnvoyFilter(orbit *orbitv1alpha1.Orbit, req ctrl.Request) error {
75 | envoyName := orbit.Name
76 | outboundSpec, err := generateOutboudValue(orbit)
77 | if err != nil {
78 | return fmt.Errorf("failed to generate outbound proxy: %w", err)
79 | }
80 |
81 | newSpec := buildHttpFilter(outboundSpec)
82 | envoyFilter := &istiov1.EnvoyFilter{
83 | ObjectMeta: metav1.ObjectMeta{
84 | Name: envoyName,
85 | Namespace: orbit.Namespace,
86 | OwnerReferences: []metav1.OwnerReference{
87 | *metav1.NewControllerRef(orbit, schema.GroupVersionKind{
88 | Group: orbit.GroupVersionKind().Group,
89 | Version: orbit.GroupVersionKind().Version,
90 | Kind: orbit.Kind,
91 | }),
92 | },
93 | },
94 | Spec: newSpec,
95 | }
96 |
97 | err = r.Get(context.TODO(), req.NamespacedName, envoyFilter)
98 | if errors.IsNotFound(err) {
99 | err = r.Create(context.TODO(), envoyFilter)
100 | if err != nil {
101 | return fmt.Errorf("EnvoyFilter %s.%s create error: %w", envoyName, orbit.Namespace, err)
102 | }
103 | r.Log.WithValues("orbit", fmt.Sprintf("%s.%s", orbit.Name, orbit.Namespace)).
104 | Info("EnvoyFilter created", envoyFilter.GetName(), orbit.Namespace)
105 | return nil
106 | } else if err != nil {
107 | return fmt.Errorf("EnvoyFilter %s.%s get query error: %w", envoyName, orbit.Namespace, err)
108 | }
109 |
110 | if envoyFilter != nil {
111 | if diff := cmp.Diff(newSpec, envoyFilter.Spec); diff != "" {
112 | clone := envoyFilter.DeepCopy()
113 | clone.Spec = newSpec
114 | err = r.Update(context.TODO(), clone)
115 | if err != nil {
116 | return fmt.Errorf("EnvoyFilter %s.%s update error: %w", envoyName, orbit.Namespace, err)
117 | }
118 | r.Log.WithValues("orbit", fmt.Sprintf("%s.%s", orbit.Name, orbit.Namespace)).
119 | Info("EnvoyFilter updated", envoyFilter.GetName(), orbit.Namespace)
120 | }
121 | }
122 |
123 | return nil
124 | }
125 |
126 | func generateOutboudValue(orbit *orbitv1alpha1.Orbit) (*types.Struct, error) {
127 | var out = &types.Struct{}
128 |
129 | out.Fields = map[string]*types.Value{}
130 | out.Fields["@type"] = &types.Value{Kind: &types.Value_StringValue{
131 | StringValue: "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua",
132 | }}
133 |
134 | headerKey := ""
135 | for k, _ := range orbit.Spec.TrafficRules.Headers {
136 | headerKey = k
137 | }
138 | out.Fields["inlineCode"] = &types.Value{Kind: &types.Value_StringValue{
139 | StringValue: fmt.Sprintf(`function envoy_on_request(handle)
140 | local tag = handle:headers():get("` + headerKey + `")
141 | local channelValue = os.getenv("ORBIT_CHANNEL_TAG")
142 | if tag == nil and channelValue ~= nil then
143 | handle:headers():add("` + headerKey + `", channelValue)
144 | end
145 | end`),
146 | }}
147 |
148 | return &types.Struct{
149 | Fields: map[string]*types.Value{
150 | "name": {
151 | Kind: &types.Value_StringValue{
152 | StringValue: "envoy.lua",
153 | },
154 | },
155 | "typed_config": {
156 | Kind: &types.Value_StructValue{StructValue: out},
157 | },
158 | },
159 | }, nil
160 | }
161 |
162 | func buildHttpFilter(outboundSpec *types.Struct) v1alpha3.EnvoyFilter {
163 | return v1alpha3.EnvoyFilter{
164 | ConfigPatches: []*v1alpha3.EnvoyFilter_EnvoyConfigObjectPatch{
165 | {
166 | ApplyTo: v1alpha3.EnvoyFilter_HTTP_FILTER,
167 | Match: &v1alpha3.EnvoyFilter_EnvoyConfigObjectMatch{
168 | Context: v1alpha3.EnvoyFilter_SIDECAR_OUTBOUND,
169 | ObjectTypes: &v1alpha3.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
170 | Listener: &v1alpha3.EnvoyFilter_ListenerMatch{
171 | FilterChain: &v1alpha3.EnvoyFilter_ListenerMatch_FilterChainMatch{
172 | Filter: &v1alpha3.EnvoyFilter_ListenerMatch_FilterMatch{
173 | Name: "envoy.filters.network.http_connection_manager",
174 | SubFilter: &v1alpha3.EnvoyFilter_ListenerMatch_SubFilterMatch{
175 | Name: "envoy.filters.http.router",
176 | },
177 | },
178 | },
179 | }},
180 | },
181 | Patch: &v1alpha3.EnvoyFilter_Patch{
182 | Operation: v1alpha3.EnvoyFilter_Patch_INSERT_BEFORE,
183 | Value: outboundSpec,
184 | },
185 | },
186 | },
187 | }
188 | }
189 |
190 | // SetupWithManager sets up the controller with the Manager.
191 | func (r *OrbitReconciler) SetupWithManager(mgr ctrl.Manager) error {
192 | return ctrl.NewControllerManagedBy(mgr).
193 | For(&orbitv1alpha1.Orbit{}).
194 | Owns(&istiov1.EnvoyFilter{}).
195 | Complete(r)
196 | }
197 |
--------------------------------------------------------------------------------
/pkg/controllers/serviceroute_controller.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2022 The TeamCode 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 | "k8s.io/apimachinery/pkg/runtime/schema"
23 | "log"
24 |
25 | "github.com/go-logr/logr"
26 | "github.com/google/go-cmp/cmp"
27 | "github.com/google/go-cmp/cmp/cmpopts"
28 | "istio.io/api/networking/v1alpha3"
29 | istiov1 "istio.io/client-go/pkg/apis/networking/v1alpha3"
30 | "k8s.io/apimachinery/pkg/api/errors"
31 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32 | "k8s.io/apimachinery/pkg/runtime"
33 | ctrl "sigs.k8s.io/controller-runtime"
34 | "sigs.k8s.io/controller-runtime/pkg/client"
35 |
36 | routev1alpha1 "kubeorbit.io/api/v1alpha1"
37 | )
38 |
39 | // ServiceRouteReconciler reconciles a ServiceRoute object
40 | type ServiceRouteReconciler struct {
41 | client.Client
42 | Scheme *runtime.Scheme
43 | Log logr.Logger
44 | }
45 |
46 | //+kubebuilder:rbac:groups=network.kubeorbit.io,resources=serviceroutes,verbs=get;list;watch;create;update;patch;delete
47 | //+kubebuilder:rbac:groups=network.kubeorbit.io,resources=serviceroutes/status,verbs=get;update;patch
48 | //+kubebuilder:rbac:groups=network.kubeorbit.io,resources=serviceroutes/finalizers,verbs=update
49 | //+kubebuilder:rbac:groups=networking.istio.io,resources=virtualservices,verbs=get;list;watch;create;update;patch;delete
50 | //+kubebuilder:rbac:groups=networking.istio.io,resources=destinationrules,verbs=get;list;watch;create;update;patch;delete
51 |
52 | // Reconcile is part of the main kubernetes reconciliation loop which aims to
53 | // move the current state of the cluster closer to the desired state.
54 | // TODO(user): Modify the Reconcile function to compare the state specified by
55 | // the ServiceRoute object against the actual cluster state, and then
56 | // perform operations to make the cluster state reflect the state specified by
57 | // the user.
58 | //
59 | // For more details, check Reconcile and its Result here:
60 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile
61 | func (r *ServiceRouteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
62 | _ = r.Log.WithValues("serviceroute", req.NamespacedName)
63 | obj := &routev1alpha1.ServiceRoute{}
64 |
65 | if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
66 | log.Println(err, "unable to fetch object")
67 | } else {
68 | if err := r.reconcileDestinationRule(obj, req); err != nil {
69 | return ctrl.Result{}, fmt.Errorf("reconcileDestinatinRule failed: %w", err)
70 | }
71 |
72 | if err := r.reconcileVirtualService(obj, req); err != nil {
73 | return ctrl.Result{}, fmt.Errorf("reconcileVirtualService failed: %w", err)
74 | }
75 | }
76 |
77 | return ctrl.Result{}, nil
78 | }
79 |
80 | func (r *ServiceRouteReconciler) reconcileDestinationRule(tr *routev1alpha1.ServiceRoute, req ctrl.Request) error {
81 | svcName := tr.GetServiceName()
82 | newSpec := v1alpha3.DestinationRule{
83 | Host: svcName,
84 | Subsets: buildRoute(tr),
85 | }
86 |
87 | destinationRule := &istiov1.DestinationRule{
88 | ObjectMeta: metav1.ObjectMeta{
89 | Name: tr.Name,
90 | Namespace: tr.Namespace,
91 | OwnerReferences: []metav1.OwnerReference{
92 | *metav1.NewControllerRef(tr, schema.GroupVersionKind{
93 | Group: tr.GroupVersionKind().Group,
94 | Version: tr.GroupVersionKind().Version,
95 | Kind: tr.Kind,
96 | }),
97 | },
98 | },
99 | Spec: newSpec,
100 | }
101 |
102 | err := r.Get(context.TODO(), req.NamespacedName, destinationRule)
103 | if errors.IsNotFound(err) {
104 | err = r.Create(context.TODO(), destinationRule)
105 | if err != nil {
106 | return fmt.Errorf("DestinationRule %s.%s create error: %w", svcName, tr.Namespace, err)
107 | }
108 | r.Log.WithValues("serviceroute", fmt.Sprintf("%s.%s", tr.Name, tr.Namespace)).
109 | Info("DestinationRule created", destinationRule.GetName(), tr.Namespace)
110 | return nil
111 | } else if err != nil {
112 | return fmt.Errorf("DestinationRule %s.%s get query error: %w", tr.Name, tr.Namespace, err)
113 | }
114 |
115 | if destinationRule != nil {
116 | if diff := cmp.Diff(newSpec, destinationRule.Spec); diff != "" {
117 | clone := destinationRule.DeepCopy()
118 | clone.Spec = newSpec
119 | err = r.Update(context.TODO(), clone)
120 | if err != nil {
121 | return fmt.Errorf("DestinationRule %s.%s update error: %w", tr.Name, tr.Namespace, err)
122 | }
123 | r.Log.WithValues("serviceroute", fmt.Sprintf("%s.%s", tr.Name, tr.Namespace)).
124 | Info("DestinationRule updated", destinationRule.GetName(), tr.Namespace)
125 | }
126 | }
127 |
128 | return nil
129 | }
130 |
131 | func (r *ServiceRouteReconciler) reconcileVirtualService(tr *routev1alpha1.ServiceRoute, req ctrl.Request) error {
132 | svcName := tr.GetServiceName()
133 |
134 | newSpec := v1alpha3.VirtualService{
135 | Hosts: []string{
136 | svcName,
137 | },
138 | Http: buildHTTP(tr),
139 | }
140 |
141 | virtualService := &istiov1.VirtualService{
142 | ObjectMeta: metav1.ObjectMeta{
143 | Name: tr.Name,
144 | Namespace: tr.Namespace,
145 | OwnerReferences: []metav1.OwnerReference{
146 | *metav1.NewControllerRef(tr, schema.GroupVersionKind{
147 | Group: tr.GroupVersionKind().Group,
148 | Version: tr.GroupVersionKind().Version,
149 | Kind: tr.Kind,
150 | }),
151 | },
152 | },
153 | Spec: newSpec,
154 | }
155 |
156 | err := r.Get(context.TODO(), req.NamespacedName, virtualService)
157 | if errors.IsNotFound(err) {
158 | err = r.Create(context.TODO(), virtualService)
159 | if err != nil {
160 | return fmt.Errorf("VirtualService %s.%s create error: %w", tr.Name, tr.Namespace, err)
161 | }
162 | r.Log.WithValues("serviceroute", fmt.Sprintf("%s.%s", tr.Spec.Name, tr.Namespace)).
163 | Info("VirtualService created", virtualService.GetName(), tr.Namespace)
164 | return nil
165 | } else if err != nil {
166 | return fmt.Errorf("VirtualService %s.%s get query error %v", tr.Name, tr.Namespace, err)
167 | }
168 |
169 | if virtualService != nil {
170 | if diff := cmp.Diff(
171 | newSpec,
172 | virtualService.Spec,
173 | cmpopts.IgnoreFields(v1alpha3.HTTPRoute{}, "Mirror", "MirrorPercentage"),
174 | ); diff != "" {
175 | vtClone := virtualService.DeepCopy()
176 | vtClone.Spec = newSpec
177 | err = r.Update(context.TODO(), vtClone)
178 | if err != nil {
179 | return fmt.Errorf("VirtualService %s.%s update error: %w", tr.Name, tr.Namespace, err)
180 | }
181 | r.Log.WithValues("serviceroute", fmt.Sprintf("%s.%s", tr.Spec.Name, tr.Namespace)).
182 | Info("VirtualService updated", virtualService.GetName(), tr.Namespace)
183 | }
184 | }
185 |
186 | return nil
187 | }
188 |
189 | func buildRoute(tr *routev1alpha1.ServiceRoute) []*v1alpha3.Subset {
190 | defaultRoute := tr.Spec.TrafficRoutes.Default
191 | subsets := make([]*v1alpha3.Subset, 0)
192 |
193 | for _, c := range tr.Spec.TrafficRoutes.TrafficSubset {
194 | if c.Labels != nil {
195 | subsets = append(subsets, &v1alpha3.Subset{
196 | Name: c.Name,
197 | Labels: c.Labels,
198 | })
199 | }
200 | }
201 |
202 | for _, c := range defaultRoute {
203 | if c != "" {
204 | subsets = append(subsets, &v1alpha3.Subset{
205 | Name: c,
206 | Labels: defaultRoute,
207 | })
208 | }
209 | }
210 |
211 | return subsets
212 | }
213 |
214 | func buildHTTP(tr *routev1alpha1.ServiceRoute) []*v1alpha3.HTTPRoute {
215 | httpRoutes := make([]*v1alpha3.HTTPRoute, 0)
216 | defaultRoute := tr.Spec.TrafficRoutes.Default
217 |
218 | for _, c := range tr.Spec.TrafficRoutes.TrafficSubset {
219 | if c.Labels != nil {
220 | headers := make(map[string]*v1alpha3.StringMatch)
221 | for k, match := range c.Headers {
222 | headers[k] = &v1alpha3.StringMatch{
223 | MatchType: &v1alpha3.StringMatch_Exact{Exact: match.Exact},
224 | }
225 | }
226 | httpRoutes = append(httpRoutes, &v1alpha3.HTTPRoute{
227 | Match: []*v1alpha3.HTTPMatchRequest{
228 | {
229 | Headers: headers,
230 | },
231 | },
232 | Route: []*v1alpha3.HTTPRouteDestination{
233 | {
234 | Destination: &v1alpha3.Destination{
235 | Host: tr.Spec.Name,
236 | Subset: c.Name,
237 | },
238 | },
239 | },
240 | })
241 | }
242 | }
243 |
244 | for _, c := range defaultRoute {
245 | if c != "" {
246 | httpRoutes = append(httpRoutes, &v1alpha3.HTTPRoute{
247 | Route: []*v1alpha3.HTTPRouteDestination{
248 | {
249 | Destination: &v1alpha3.Destination{
250 | Host: tr.Spec.Name,
251 | Subset: c,
252 | },
253 | },
254 | },
255 | })
256 | }
257 | }
258 |
259 | return httpRoutes
260 | }
261 |
262 | // SetupWithManager sets up the controller with the Manager.
263 | func (r *ServiceRouteReconciler) SetupWithManager(mgr ctrl.Manager) error {
264 | return ctrl.NewControllerManagedBy(mgr).
265 | For(&routev1alpha1.ServiceRoute{}).
266 | Owns(&istiov1.DestinationRule{}).
267 | Owns(&istiov1.VirtualService{}).
268 | Complete(r)
269 | }
270 |
--------------------------------------------------------------------------------
/api/v1alpha1/zz_generated.deepcopy.go:
--------------------------------------------------------------------------------
1 | //go:build !ignore_autogenerated
2 | // +build !ignore_autogenerated
3 |
4 | /*
5 | Copyright 2022 The TeamCode 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 controller-gen. DO NOT EDIT.
21 |
22 | package v1alpha1
23 |
24 | import (
25 | runtime "k8s.io/apimachinery/pkg/runtime"
26 | )
27 |
28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
29 | func (in *HTTPMatchRequest) DeepCopyInto(out *HTTPMatchRequest) {
30 | *out = *in
31 | if in.Headers != nil {
32 | in, out := &in.Headers, &out.Headers
33 | *out = make(map[string]*StringMatch, len(*in))
34 | for key, val := range *in {
35 | var outVal *StringMatch
36 | if val == nil {
37 | (*out)[key] = nil
38 | } else {
39 | in, out := &val, &outVal
40 | *out = new(StringMatch)
41 | **out = **in
42 | }
43 | (*out)[key] = outVal
44 | }
45 | }
46 | }
47 |
48 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPMatchRequest.
49 | func (in *HTTPMatchRequest) DeepCopy() *HTTPMatchRequest {
50 | if in == nil {
51 | return nil
52 | }
53 | out := new(HTTPMatchRequest)
54 | in.DeepCopyInto(out)
55 | return out
56 | }
57 |
58 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
59 | func (in *Orbit) DeepCopyInto(out *Orbit) {
60 | *out = *in
61 | out.TypeMeta = in.TypeMeta
62 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
63 | in.Spec.DeepCopyInto(&out.Spec)
64 | out.Status = in.Status
65 | }
66 |
67 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Orbit.
68 | func (in *Orbit) DeepCopy() *Orbit {
69 | if in == nil {
70 | return nil
71 | }
72 | out := new(Orbit)
73 | in.DeepCopyInto(out)
74 | return out
75 | }
76 |
77 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
78 | func (in *Orbit) DeepCopyObject() runtime.Object {
79 | if c := in.DeepCopy(); c != nil {
80 | return c
81 | }
82 | return nil
83 | }
84 |
85 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
86 | func (in *OrbitList) DeepCopyInto(out *OrbitList) {
87 | *out = *in
88 | out.TypeMeta = in.TypeMeta
89 | in.ListMeta.DeepCopyInto(&out.ListMeta)
90 | if in.Items != nil {
91 | in, out := &in.Items, &out.Items
92 | *out = make([]Orbit, len(*in))
93 | for i := range *in {
94 | (*in)[i].DeepCopyInto(&(*out)[i])
95 | }
96 | }
97 | }
98 |
99 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrbitList.
100 | func (in *OrbitList) DeepCopy() *OrbitList {
101 | if in == nil {
102 | return nil
103 | }
104 | out := new(OrbitList)
105 | in.DeepCopyInto(out)
106 | return out
107 | }
108 |
109 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
110 | func (in *OrbitList) DeepCopyObject() runtime.Object {
111 | if c := in.DeepCopy(); c != nil {
112 | return c
113 | }
114 | return nil
115 | }
116 |
117 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
118 | func (in *OrbitSpec) DeepCopyInto(out *OrbitSpec) {
119 | *out = *in
120 | in.TrafficRules.DeepCopyInto(&out.TrafficRules)
121 | }
122 |
123 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrbitSpec.
124 | func (in *OrbitSpec) DeepCopy() *OrbitSpec {
125 | if in == nil {
126 | return nil
127 | }
128 | out := new(OrbitSpec)
129 | in.DeepCopyInto(out)
130 | return out
131 | }
132 |
133 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
134 | func (in *OrbitStatus) DeepCopyInto(out *OrbitStatus) {
135 | *out = *in
136 | }
137 |
138 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrbitStatus.
139 | func (in *OrbitStatus) DeepCopy() *OrbitStatus {
140 | if in == nil {
141 | return nil
142 | }
143 | out := new(OrbitStatus)
144 | in.DeepCopyInto(out)
145 | return out
146 | }
147 |
148 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
149 | func (in *ServiceRoute) DeepCopyInto(out *ServiceRoute) {
150 | *out = *in
151 | out.TypeMeta = in.TypeMeta
152 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
153 | in.Spec.DeepCopyInto(&out.Spec)
154 | out.Status = in.Status
155 | }
156 |
157 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceRoute.
158 | func (in *ServiceRoute) DeepCopy() *ServiceRoute {
159 | if in == nil {
160 | return nil
161 | }
162 | out := new(ServiceRoute)
163 | in.DeepCopyInto(out)
164 | return out
165 | }
166 |
167 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
168 | func (in *ServiceRoute) DeepCopyObject() runtime.Object {
169 | if c := in.DeepCopy(); c != nil {
170 | return c
171 | }
172 | return nil
173 | }
174 |
175 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
176 | func (in *ServiceRouteList) DeepCopyInto(out *ServiceRouteList) {
177 | *out = *in
178 | out.TypeMeta = in.TypeMeta
179 | in.ListMeta.DeepCopyInto(&out.ListMeta)
180 | if in.Items != nil {
181 | in, out := &in.Items, &out.Items
182 | *out = make([]ServiceRoute, len(*in))
183 | for i := range *in {
184 | (*in)[i].DeepCopyInto(&(*out)[i])
185 | }
186 | }
187 | }
188 |
189 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceRouteList.
190 | func (in *ServiceRouteList) DeepCopy() *ServiceRouteList {
191 | if in == nil {
192 | return nil
193 | }
194 | out := new(ServiceRouteList)
195 | in.DeepCopyInto(out)
196 | return out
197 | }
198 |
199 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
200 | func (in *ServiceRouteList) DeepCopyObject() runtime.Object {
201 | if c := in.DeepCopy(); c != nil {
202 | return c
203 | }
204 | return nil
205 | }
206 |
207 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
208 | func (in *ServiceRouteSpec) DeepCopyInto(out *ServiceRouteSpec) {
209 | *out = *in
210 | in.TrafficRoutes.DeepCopyInto(&out.TrafficRoutes)
211 | }
212 |
213 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceRouteSpec.
214 | func (in *ServiceRouteSpec) DeepCopy() *ServiceRouteSpec {
215 | if in == nil {
216 | return nil
217 | }
218 | out := new(ServiceRouteSpec)
219 | in.DeepCopyInto(out)
220 | return out
221 | }
222 |
223 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
224 | func (in *ServiceRouteStatus) DeepCopyInto(out *ServiceRouteStatus) {
225 | *out = *in
226 | }
227 |
228 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceRouteStatus.
229 | func (in *ServiceRouteStatus) DeepCopy() *ServiceRouteStatus {
230 | if in == nil {
231 | return nil
232 | }
233 | out := new(ServiceRouteStatus)
234 | in.DeepCopyInto(out)
235 | return out
236 | }
237 |
238 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
239 | func (in *StringMatch) DeepCopyInto(out *StringMatch) {
240 | *out = *in
241 | }
242 |
243 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StringMatch.
244 | func (in *StringMatch) DeepCopy() *StringMatch {
245 | if in == nil {
246 | return nil
247 | }
248 | out := new(StringMatch)
249 | in.DeepCopyInto(out)
250 | return out
251 | }
252 |
253 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
254 | func (in *Subset) DeepCopyInto(out *Subset) {
255 | *out = *in
256 | if in.Labels != nil {
257 | in, out := &in.Labels, &out.Labels
258 | *out = make(map[string]string, len(*in))
259 | for key, val := range *in {
260 | (*out)[key] = val
261 | }
262 | }
263 | if in.Headers != nil {
264 | in, out := &in.Headers, &out.Headers
265 | *out = make(map[string]*StringMatch, len(*in))
266 | for key, val := range *in {
267 | var outVal *StringMatch
268 | if val == nil {
269 | (*out)[key] = nil
270 | } else {
271 | in, out := &val, &outVal
272 | *out = new(StringMatch)
273 | **out = **in
274 | }
275 | (*out)[key] = outVal
276 | }
277 | }
278 | }
279 |
280 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subset.
281 | func (in *Subset) DeepCopy() *Subset {
282 | if in == nil {
283 | return nil
284 | }
285 | out := new(Subset)
286 | in.DeepCopyInto(out)
287 | return out
288 | }
289 |
290 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
291 | func (in *TrafficRouteSpec) DeepCopyInto(out *TrafficRouteSpec) {
292 | *out = *in
293 | if in.TrafficSubset != nil {
294 | in, out := &in.TrafficSubset, &out.TrafficSubset
295 | *out = make([]*Subset, len(*in))
296 | for i := range *in {
297 | if (*in)[i] != nil {
298 | in, out := &(*in)[i], &(*out)[i]
299 | *out = new(Subset)
300 | (*in).DeepCopyInto(*out)
301 | }
302 | }
303 | }
304 | if in.Default != nil {
305 | in, out := &in.Default, &out.Default
306 | *out = make(map[string]string, len(*in))
307 | for key, val := range *in {
308 | (*out)[key] = val
309 | }
310 | }
311 | }
312 |
313 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRouteSpec.
314 | func (in *TrafficRouteSpec) DeepCopy() *TrafficRouteSpec {
315 | if in == nil {
316 | return nil
317 | }
318 | out := new(TrafficRouteSpec)
319 | in.DeepCopyInto(out)
320 | return out
321 | }
322 |
323 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
324 | func (in *TrafficRulesSpec) DeepCopyInto(out *TrafficRulesSpec) {
325 | *out = *in
326 | if in.Headers != nil {
327 | in, out := &in.Headers, &out.Headers
328 | *out = make(map[string]string, len(*in))
329 | for key, val := range *in {
330 | (*out)[key] = val
331 | }
332 | }
333 | }
334 |
335 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficRulesSpec.
336 | func (in *TrafficRulesSpec) DeepCopy() *TrafficRulesSpec {
337 | if in == nil {
338 | return nil
339 | }
340 | out := new(TrafficRulesSpec)
341 | in.DeepCopyInto(out)
342 | return out
343 | }
344 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------