├── 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 | ![work in progress badge](https://img.shields.io/badge/stability-work_in_progress-lightgrey.svg?style=flat-square) 14 | [![Apache License 2.0](https://img.shields.io/github/license/teamcode-inc/kubeorbit?style=flat-square)](https://github.com/teamcode-inc/kubeorbit/blob/master/LICENSE) 15 | [![Discord channel](https://img.shields.io/discord/930779108818956298?style=flat-square)](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 | --------------------------------------------------------------------------------