diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..18e5972 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,78 @@ +name: Test, Build and Push +on: + push: + # Sequence of patterns matched against refs/tags + tags: + - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 +jobs: + main: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Git configs and known_hosts + run: | + export known_hosts=$(ssh-keyscan github.com) + git config --global --add url."git@github.com:".insteadOf "https://github.com/" + + - name: Install SSH key + uses: shimataro/ssh-key-action@v2 + with: + key: ${{ secrets.SSH_PRIVATE_KEY_NETRISAPI }} + known_hosts: ${known_hosts} + + - name: Switch the default system shell + run: sudo rm /bin/sh; sudo ln -s bash /bin/sh + + - name: Make test + run: CONTROLLER_HOST="example.com" make test + + - name: Prepare + id: prep + run: | + DOCKER_IMAGE=${GITHUB_REPOSITORY} + VERSION=edge + if [[ $GITHUB_REF == refs/tags/* ]]; then + VERSION=${GITHUB_REF#refs/tags/} + elif [[ $GITHUB_REF == refs/heads/* ]]; then + VERSION=$(echo ${GITHUB_REF#refs/heads/} | sed -r 's#/+#-#g') + elif [[ $GITHUB_REF == refs/pull/* ]]; then + VERSION=pr-${{ github.event.number }} + fi + TAGS="${DOCKER_IMAGE}:${VERSION}" + if [ "${{ github.event_name }}" = "push" ]; then + TAGS="$TAGS,${DOCKER_IMAGE}:sha-${GITHUB_SHA::8}" + fi + echo ::set-output name=version::${VERSION} + echo ::set-output name=tags::${TAGS} + echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ') + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to DockerHub + if: github.event_name != 'pull_request' + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.prep.outputs.tags }} + ssh: | + ssh_private_key_ci=/home/runner/.ssh/id_rsa + labels: | + org.opencontainers.image.source=${{ github.event.repository.html_url }} + org.opencontainers.image.created=${{ steps.prep.outputs.created }} + org.opencontainers.image.revision=${{ github.sha }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..21533c5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,52 @@ +name: Create release +on: + push: + # Sequence of patterns matched against refs/tags + tags: + - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 +jobs: + build: + name: Upload Release Asset + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Git configs and known_hosts + run: | + export known_hosts=$(ssh-keyscan github.com) + git config --global --add url."git@github.com:".insteadOf "https://github.com/" + - name: Install SSH key + uses: shimataro/ssh-key-action@v2 + with: + key: ${{ secrets.SSH_PRIVATE_KEY_NETRISAPI }} + known_hosts: ${known_hosts} + - name: Generate Manifests + run: make release + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + - name: Upload Release Asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: ./deploy/netris-operator.yaml + asset_name: netris-operator.yaml + asset_content_type: text/yaml + - name: Upload Release Asset - CRD + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: ./deploy/netris-operator.crds.yaml + asset_name: netris-operator.crds.yaml + asset_content_type: text/yaml diff --git a/.gitignore b/.gitignore index d97ffc5..db45eb1 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ bin *.swp *.swo *~ +.netrc +testbin diff --git a/Dockerfile b/Dockerfile index 74eb9d7..db434fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,28 @@ +# syntax=docker/dockerfile:experimental # Build the manager binary FROM golang:1.13 as builder WORKDIR /workspace + +# Config ssh private key +RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts +RUN git config --global --add url."git@github.com:".insteadOf "https://github.com/" + # Copy the Go Modules manifests COPY go.mod go.mod COPY go.sum go.sum # cache deps before building and copying source so that we don't need to re-download as much # and so that source changes don't invalidate our downloaded layer -RUN go mod download +RUN --mount=type=ssh,id=ssh_private_key_ci go mod download # Copy the go source COPY main.go main.go COPY api/ api/ COPY controllers/ controllers/ +COPY configloader/ configloader/ # Build -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go +RUN CGO_ENABLED=0 GOOS=linux GO111MODULE=on go build -a -o manager main.go # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details diff --git a/Makefile b/Makefile index cdf9c88..aee618a 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,17 @@ -# Current Operator version -VERSION ?= 0.0.1 -# Default bundle image tag -BUNDLE_IMG ?= controller-bundle:$(VERSION) -# Options for 'bundle-build' -ifneq ($(origin CHANNELS), undefined) -BUNDLE_CHANNELS := --channels=$(CHANNELS) -endif -ifneq ($(origin DEFAULT_CHANNEL), undefined) -BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) +# Capture image tag from git branch name +GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2> /dev/null || true) +ifeq (,$(GIT_BRANCH)) +TAG = latest +else ifeq (master, $(GIT_BRANCH)) +TAG = latest +else ifeq (HEAD, $(GIT_BRANCH)) +TAG = $(shell git describe --abbrev=0 --tags $(shell git rev-list --abbrev-commit --tags --max-count=1) 2> /dev/null || true) +else +TAG = $(GIT_BRANCH) endif -BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) # Image URL to use all building/pushing image targets -IMG ?= controller:latest +IMG ?= netrisai/netris-operator:$(TAG) # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) CRD_OPTIONS ?= "crd:trivialVersions=true" @@ -53,6 +52,9 @@ deploy: manifests kustomize cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} $(KUSTOMIZE) build config/default | kubectl apply -f - +undeploy: + $(KUSTOMIZE) build config/default | kubectl delete -f - + # Generate manifests e.g. CRD, RBAC etc. manifests: controller-gen $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases @@ -109,15 +111,30 @@ else KUSTOMIZE=$(shell which kustomize) endif -# Generate bundle manifests and metadata, then validate generated files. -.PHONY: bundle -bundle: manifests kustomize - operator-sdk generate kustomize manifests -q - cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) - $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) - operator-sdk bundle validate ./bundle - -# Build the bundle image. -.PHONY: bundle-build -bundle-build: - docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . +release: generate fmt vet manifests kustomize + $(KUSTOMIZE) build config/crd > deploy/netris-operator.crds.yaml + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default > deploy/netris-operator.yaml + +pip-install-reqs: + pip3 install yq pyyaml + +helm: generate fmt vet manifests pip-install-reqs + mkdir -p deploy/charts/netris-operator/crds/ + cp config/crd/bases/* deploy/charts/netris-operator/crds/ + echo "{{- if .Values.rbac.create -}}" > deploy/charts/netris-operator/templates/rbac.yaml + for i in $(shell yq -y .resources config/rbac/kustomization.yaml | awk {'print $$2'});\ + do echo "---" >> deploy/charts/netris-operator/templates/rbac.yaml && \ + scripts/rbac-helm-template.py config/rbac/$${i} | yq -y . >> deploy/charts/netris-operator/templates/rbac.yaml;\ + done + echo "{{- end }}" >> deploy/charts/netris-operator/templates/rbac.yaml + @{ \ + set -e ;\ + HELM_CHART_GEN_TMP_DIR=$$(mktemp -d) ;\ + git clone git@github.com:netrisai/charts.git --depth 1 $$HELM_CHART_GEN_TMP_DIR ;\ + if [[ -z "$${HELM_CHART_REPO_COMMIT_MSG}" ]]; then HELM_CHART_REPO_COMMIT_MSG=Update-$$(date '+%F_%T' -u); fi ;\ + cp -r deploy/charts $$HELM_CHART_GEN_TMP_DIR ;\ + cd $$HELM_CHART_GEN_TMP_DIR ;\ + git add charts && git commit -m $$HELM_CHART_REPO_COMMIT_MSG && git push -u origin main ;\ + rm -rf $$HELM_CHART_GEN_TMP_DIR ;\ + } diff --git a/PROJECT b/PROJECT index caef0ec..014dfd2 100644 --- a/PROJECT +++ b/PROJECT @@ -1,11 +1,14 @@ domain: netris.ai layout: go.kubebuilder.io/v2 projectName: netris-operator -repo: github.com/netrisx/netris-operator +repo: github.com/netrisai/netris-operator resources: - group: k8s kind: VNet version: v1alpha1 +- group: k8s + kind: VNetMeta + version: v1alpha1 version: 3-alpha plugins: go.sdk.operatorframework.io/v2-alpha: {} diff --git a/api/v1alpha1/vnet_types.go b/api/v1alpha1/vnet_types.go index 33674cb..8082d35 100644 --- a/api/v1alpha1/vnet_types.go +++ b/api/v1alpha1/vnet_types.go @@ -28,51 +28,6 @@ import ( // Tenant_name string `json:"tenant_name"` // } -type VNetGateways struct { - Id int `json:"id,omitempty"` - Gateway string `json:"gateway"` - Gw_length string `json:"gw_length"` - Version string `json:"version"` - Va_vlan_id int `json:"va_vlan_id,omitempty"` -} - -// type VNetMembers struct { -// Port_id int `json:"port_id"` -// Vlan_id string `json:"vlan_id"` -// Tenant_id int `json:"tenant_id"` -// ChildPort int `json:"childPort"` -// ParentPort int `json:"parentPort"` -// Member_state string `json:"member_state"` -// Lacp string `json:"lacp"` -// Port_name string `json:"port_name"` -// PortIsUntagged bool `json:"portIsUntagged"` -// } - -// VNetSpec defines the desired state of VNet -type VNetSpec struct { - // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - // Important: Run "make" to regenerate code after modifying this file - - ID int `json:"id,omitempty"` - Name string `json:"name"` - // +kubebuilder:validation:Minimum=1 - Vxlan_id int `json:"vxlan_id,omitempty"` - Mac_address string `json:"mac_address,omitempty"` - MembersCount int `json:"membersCount,omitempty"` - State string `json:"state"` - Provisioning int `json:"provisioning"` - Create_date string `json:"create_date,omitempty"` - Modified_date string `json:"modifiedDate,omitempty"` - Owner int `json:"owner"` - Va_mode bool `json:"va_mode"` - Va_native_vlan int `json:"va_native_vlan"` - Va_vlans string `json:"va_vlans"` - Tenants []int `json:"tenants"` - Sites []int `json:"sites"` - Gateways []VNetGateways `json:"gateways"` - Members string `json:"members"` -} - // VNetStatus defines the observed state of VNet type VNetStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster @@ -86,11 +41,11 @@ type VNetStatus struct { // VNet is the Schema for the vnets API type VNet struct { + // APIVersion string `json:"apiVersion"` + // Kind string `json:"kind"` metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec VNetSpec `json:"spec"` - Status VNetStatus `json:"status,omitempty"` + Spec VNetSpec `json:"spec"` } // +kubebuilder:object:root=true @@ -102,6 +57,28 @@ type VNetList struct { Items []VNet `json:"items"` } +// VNetSpec . +type VNetSpec struct { + Owner string `json:"ownerTenant"` + State string `json:"state,omitempty"` + GuestTenants []string `json:"guestTenants"` + Sites []VNetSite `json:"sites"` +} + +// VNetSite . +type VNetSite struct { + Name string `json:"name"` + Gateways []string `json:"gateways,omitempty"` + SwitchPorts []VNetSwitchPort `json:"switchPorts,omitempty"` +} + +// VNetSwitchPort . +type VNetSwitchPort struct { + Name string `json:"name"` + VlanID int `json:"vlanId,omitempty"` + State string `json:"state,omitempty"` +} + func init() { SchemeBuilder.Register(&VNet{}, &VNetList{}) } diff --git a/api/v1alpha1/vnetmeta_types.go b/api/v1alpha1/vnetmeta_types.go new file mode 100644 index 0000000..a87c09b --- /dev/null +++ b/api/v1alpha1/vnetmeta_types.go @@ -0,0 +1,104 @@ +/* +Copyright 2020. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// VNetMetaSpec defines the desired state of VNetMeta +type VNetMetaSpec struct { + Imported bool `json:"imported"` + VnetCRGeneration int64 `json:"vnetGeneration"` + Gateways []VNetMetaGateway `json:"gateways"` + ID int `json:"id"` + Members []VNetMetaMember `json:"members"` + Name string `json:"name"` + VnetName string `json:"vnetName"` + OwnerID int `json:"ownerid"` + Owner string `json:"owner"` + Provisioning int `json:"provisioning"` + Sites []VNetMetaSite `json:"sites"` + State string `json:"state"` + Tenants []int `json:"tenants"` + VaMode bool `json:"vaMode"` + VaNativeVLAN int `json:"vaNativeVlan"` + VaVLANs string `json:"vaVlans"` +} + +// VNetMetaSite . +type VNetMetaSite struct { + ID int `json:"id,omitempty"` + Name string `json:"name,omitempty"` +} + +// VNetMetaMember . +type VNetMetaMember struct { + ChildPort int `json:"childPort"` + LACP string `json:"lacp"` + MemberState string `json:"member_state"` + ParentPort int `json:"parentPort"` + PortIsUntagged bool `json:"portIsUntagged"` + PortID int `json:"port_id"` + PortName string `json:"port_name"` + TenantID int `json:"tenant_id"` + VLANID int `json:"vlan_id"` +} + +// VNetMetaGateway . +type VNetMetaGateway struct { + Gateway string `json:"gateway"` + GwLength int `json:"gwLength"` + ID int `json:"id,omitempty"` + VaVLANID int `json:"vaVlanId,omitempty"` + Nos string `json:"nos,omitempty"` + Version string `json:"version,omitempty"` +} + +// VNetMetaStatus defines the observed state of VNetMeta +type VNetMetaStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// VNetMeta is the Schema for the vnetmeta API +type VNetMeta struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec VNetMetaSpec `json:"spec,omitempty"` + Status VNetMetaStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// VNetMetaList contains a list of VNetMeta +type VNetMetaList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []VNetMeta `json:"items"` +} + +func init() { + SchemeBuilder.Register(&VNetMeta{}, &VNetMetaList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 4fe0e80..d935c20 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -30,7 +30,6 @@ func (in *VNet) DeepCopyInto(out *VNet) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VNet. @@ -52,46 +51,105 @@ func (in *VNet) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VNetGateways) DeepCopyInto(out *VNetGateways) { +func (in *VNetList) DeepCopyInto(out *VNetList) { *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VNet, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VNetGateways. -func (in *VNetGateways) DeepCopy() *VNetGateways { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VNetList. +func (in *VNetList) DeepCopy() *VNetList { if in == nil { return nil } - out := new(VNetGateways) + out := new(VNetList) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VNetList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VNetList) DeepCopyInto(out *VNetList) { +func (in *VNetMeta) DeepCopyInto(out *VNetMeta) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VNetMeta. +func (in *VNetMeta) DeepCopy() *VNetMeta { + if in == nil { + return nil + } + out := new(VNetMeta) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VNetMeta) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VNetMetaGateway) DeepCopyInto(out *VNetMetaGateway) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VNetMetaGateway. +func (in *VNetMetaGateway) DeepCopy() *VNetMetaGateway { + if in == nil { + return nil + } + out := new(VNetMetaGateway) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VNetMetaList) DeepCopyInto(out *VNetMetaList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]VNet, len(*in)) + *out = make([]VNetMeta, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VNetList. -func (in *VNetList) DeepCopy() *VNetList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VNetMetaList. +func (in *VNetMetaList) DeepCopy() *VNetMetaList { if in == nil { return nil } - out := new(VNetList) + out := new(VNetMetaList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *VNetList) DeepCopyObject() runtime.Object { +func (in *VNetMetaList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -99,25 +157,127 @@ func (in *VNetList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VNetSpec) DeepCopyInto(out *VNetSpec) { +func (in *VNetMetaMember) DeepCopyInto(out *VNetMetaMember) { *out = *in - if in.Tenants != nil { - in, out := &in.Tenants, &out.Tenants - *out = make([]int, len(*in)) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VNetMetaMember. +func (in *VNetMetaMember) DeepCopy() *VNetMetaMember { + if in == nil { + return nil + } + out := new(VNetMetaMember) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VNetMetaSite) DeepCopyInto(out *VNetMetaSite) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VNetMetaSite. +func (in *VNetMetaSite) DeepCopy() *VNetMetaSite { + if in == nil { + return nil + } + out := new(VNetMetaSite) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VNetMetaSpec) DeepCopyInto(out *VNetMetaSpec) { + *out = *in + if in.Gateways != nil { + in, out := &in.Gateways, &out.Gateways + *out = make([]VNetMetaGateway, len(*in)) + copy(*out, *in) + } + if in.Members != nil { + in, out := &in.Members, &out.Members + *out = make([]VNetMetaMember, len(*in)) copy(*out, *in) } if in.Sites != nil { in, out := &in.Sites, &out.Sites + *out = make([]VNetMetaSite, len(*in)) + copy(*out, *in) + } + if in.Tenants != nil { + in, out := &in.Tenants, &out.Tenants *out = make([]int, len(*in)) copy(*out, *in) } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VNetMetaSpec. +func (in *VNetMetaSpec) DeepCopy() *VNetMetaSpec { + if in == nil { + return nil + } + out := new(VNetMetaSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VNetMetaStatus) DeepCopyInto(out *VNetMetaStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VNetMetaStatus. +func (in *VNetMetaStatus) DeepCopy() *VNetMetaStatus { + if in == nil { + return nil + } + out := new(VNetMetaStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VNetSite) DeepCopyInto(out *VNetSite) { + *out = *in if in.Gateways != nil { in, out := &in.Gateways, &out.Gateways - *out = make([]VNetGateways, len(*in)) + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SwitchPorts != nil { + in, out := &in.SwitchPorts, &out.SwitchPorts + *out = make([]VNetSwitchPort, len(*in)) copy(*out, *in) } } +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VNetSite. +func (in *VNetSite) DeepCopy() *VNetSite { + if in == nil { + return nil + } + out := new(VNetSite) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VNetSpec) DeepCopyInto(out *VNetSpec) { + *out = *in + if in.GuestTenants != nil { + in, out := &in.GuestTenants, &out.GuestTenants + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Sites != nil { + in, out := &in.Sites, &out.Sites + *out = make([]VNetSite, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VNetSpec. func (in *VNetSpec) DeepCopy() *VNetSpec { if in == nil { @@ -142,3 +302,18 @@ func (in *VNetStatus) DeepCopy() *VNetStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VNetSwitchPort) DeepCopyInto(out *VNetSwitchPort) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VNetSwitchPort. +func (in *VNetSwitchPort) DeepCopy() *VNetSwitchPort { + if in == nil { + return nil + } + out := new(VNetSwitchPort) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/k8s.netris.ai_vnetmeta.yaml b/config/crd/bases/k8s.netris.ai_vnetmeta.yaml new file mode 100644 index 0000000..ae509fd --- /dev/null +++ b/config/crd/bases/k8s.netris.ai_vnetmeta.yaml @@ -0,0 +1,165 @@ + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.3.0 + creationTimestamp: null + name: vnetmeta.k8s.netris.ai +spec: + group: k8s.netris.ai + names: + kind: VNetMeta + listKind: VNetMetaList + plural: vnetmeta + singular: vnetmeta + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + description: VNetMeta is the Schema for the vnetmeta API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: VNetMetaSpec defines the desired state of VNetMeta + properties: + gateways: + items: + description: VNetMetaGateway . + properties: + gateway: + type: string + gwLength: + type: integer + id: + type: integer + nos: + type: string + vaVlanId: + type: integer + version: + type: string + required: + - gateway + - gwLength + type: object + type: array + id: + type: integer + imported: + type: boolean + members: + items: + description: VNetMetaMember . + properties: + childPort: + type: integer + lacp: + type: string + member_state: + type: string + parentPort: + type: integer + port_id: + type: integer + port_name: + type: string + portIsUntagged: + type: boolean + tenant_id: + type: integer + vlan_id: + type: integer + required: + - childPort + - lacp + - member_state + - parentPort + - portIsUntagged + - port_id + - port_name + - tenant_id + - vlan_id + type: object + type: array + name: + type: string + owner: + type: string + ownerid: + type: integer + provisioning: + type: integer + sites: + items: + description: VNetMetaSite . + properties: + id: + type: integer + name: + type: string + type: object + type: array + state: + type: string + tenants: + items: + type: integer + type: array + vaMode: + type: boolean + vaNativeVlan: + type: integer + vaVlans: + type: string + vnetGeneration: + format: int64 + type: integer + vnetName: + type: string + required: + - gateways + - id + - imported + - members + - name + - owner + - ownerid + - provisioning + - sites + - state + - tenants + - vaMode + - vaNativeVlan + - vaVlans + - vnetGeneration + - vnetName + type: object + status: + description: VNetMetaStatus defines the observed state of VNetMeta + type: object + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/bases/k8s.netris.ai_vnets.yaml b/config/crd/bases/k8s.netris.ai_vnets.yaml index 1e33501..99c9ed6 100644 --- a/config/crd/bases/k8s.netris.ai_vnets.yaml +++ b/config/crd/bases/k8s.netris.ai_vnets.yaml @@ -34,90 +34,48 @@ spec: metadata: type: object spec: - description: VNetSpec defines the desired state of VNet + description: VNetSpec . properties: - create_date: + guestTenants: + items: + type: string + type: array + ownerTenant: type: string - gateways: + sites: items: + description: VNetSite . properties: - gateway: - type: string - gw_length: - type: string - id: - type: integer - va_vlan_id: - type: integer - version: + gateways: + items: + type: string + type: array + name: type: string + switchPorts: + items: + description: VNetSwitchPort . + properties: + name: + type: string + state: + type: string + vlanId: + type: integer + required: + - name + type: object + type: array required: - - gateway - - gw_length - - version + - name type: object type: array - id: - type: integer - mac_address: - type: string - members: - type: string - membersCount: - type: integer - modifiedDate: - type: string - name: - type: string - owner: - type: integer - provisioning: - type: integer - sites: - items: - type: integer - type: array state: type: string - tenants: - items: - type: integer - type: array - va_mode: - type: boolean - va_native_vlan: - type: integer - va_vlans: - type: string - vxlan_id: - minimum: 1 - type: integer required: - - gateways - - members - - name - - owner - - provisioning + - guestTenants + - ownerTenant - sites - - state - - tenants - - va_mode - - va_native_vlan - - va_vlans - type: object - status: - description: VNetStatus defines the observed state of VNet - properties: - status: - description: 'INSERT ADDITIONAL STATUS FIELD - define observed state - of cluster Important: Run "make" to regenerate code after modifying - this file' - type: string - type: - type: string - required: - - status - - type type: object required: - spec diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 88a2318..f97201d 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -3,17 +3,20 @@ # It should be run by config/default resources: - bases/k8s.netris.ai_vnets.yaml +- bases/k8s.netris.ai_vnetmeta.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. # patches here are for enabling the conversion webhook for each CRD #- patches/webhook_in_vnets.yaml +#- patches/webhook_in_vnetmeta.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD #- patches/cainjection_in_vnets.yaml +#- patches/cainjection_in_vnetmeta.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_vnetmeta.yaml b/config/crd/patches/cainjection_in_vnetmeta.yaml new file mode 100644 index 0000000..e0847a5 --- /dev/null +++ b/config/crd/patches/cainjection_in_vnetmeta.yaml @@ -0,0 +1,8 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: vnetmeta.k8s.netris.ai diff --git a/config/crd/patches/webhook_in_vnetmeta.yaml b/config/crd/patches/webhook_in_vnetmeta.yaml new file mode 100644 index 0000000..8428c6e --- /dev/null +++ b/config/crd/patches/webhook_in_vnetmeta.yaml @@ -0,0 +1,17 @@ +# The following patch enables conversion webhook for CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: vnetmeta.k8s.netris.ai +spec: + conversion: + strategy: Webhook + webhookClientConfig: + # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, + # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) + caBundle: Cg== + service: + namespace: system + name: webhook-service + path: /convert diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index df42768..dbe9ad1 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -1,5 +1,5 @@ # Adds namespace to all resources. -namespace: netris-operator-system +namespace: netris-operator # Value of this field is prepended to the # names of all resources, e.g. a deployment named @@ -12,10 +12,6 @@ namePrefix: netris-operator- #commonLabels: # someName: someValue -bases: -- ../crd -- ../rbac -- ../manager # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml #- ../webhook @@ -24,10 +20,10 @@ bases: # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. #- ../prometheus -patchesStrategicMerge: # Protect the /metrics endpoint by putting it behind auth. # If you want your controller-manager to expose the /metrics # endpoint w/o any authn/z, please comment the following line. +patchesStrategicMerge: - manager_auth_proxy_patch.yaml # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in @@ -40,31 +36,9 @@ patchesStrategicMerge: #- webhookcainjection_patch.yaml # the following config is for teaching kustomize how to do var substitution -vars: -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. -#- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR -# objref: -# kind: Certificate -# group: cert-manager.io -# version: v1alpha2 -# name: serving-cert # this name should match the one in certificate.yaml -# fieldref: -# fieldpath: metadata.namespace -#- name: CERTIFICATE_NAME -# objref: -# kind: Certificate -# group: cert-manager.io -# version: v1alpha2 -# name: serving-cert # this name should match the one in certificate.yaml -#- name: SERVICE_NAMESPACE # namespace of the service -# objref: -# kind: Service -# version: v1 -# name: webhook-service -# fieldref: -# fieldpath: metadata.namespace -#- name: SERVICE_NAME -# objref: -# kind: Service -# version: v1 -# name: webhook-service +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../crd +- ../rbac +- ../manager diff --git a/config/manager/custom-env.yaml b/config/manager/custom-env.yaml new file mode 100644 index 0000000..4c92ab7 --- /dev/null +++ b/config/manager/custom-env.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + env: + - name: CONTROLLER_HOST + valueFrom: + secretKeyRef: + name: netris-creds + key: host + - name: CONTROLLER_LOGIN + valueFrom: + secretKeyRef: + name: netris-creds + key: login + - name: CONTROLLER_PASSWORD + valueFrom: + secretKeyRef: + name: netris-creds + key: password + - name: CONTROLLER_INSECURE + value: "false" + - name: NOPERATOR_DEV_MODE + value: "false" + - name: NOPERATOR_REQUEUE_OPERATOR + value: "15" diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 5c5f0b8..6d2e52f 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -1,2 +1,9 @@ resources: - manager.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +images: +- name: controller + newName: netrisai/netris-operator +patchesStrategicMerge: +- custom-env.yaml diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index b6c85a5..99afe52 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Namespace metadata: labels: - control-plane: controller-manager + netris-operator: controller-manager name: system --- apiVersion: apps/v1 @@ -11,16 +11,16 @@ metadata: name: controller-manager namespace: system labels: - control-plane: controller-manager + netris-operator: controller-manager spec: selector: matchLabels: - control-plane: controller-manager + netris-operator: controller-manager replicas: 1 template: metadata: labels: - control-plane: controller-manager + netris-operator: controller-manager spec: containers: - command: @@ -28,6 +28,7 @@ spec: args: - --enable-leader-election image: controller:latest + imagePullPolicy: "Always" name: manager resources: limits: diff --git a/config/prometheus/monitor.yaml b/config/prometheus/monitor.yaml index 9b8047b..0cc267f 100644 --- a/config/prometheus/monitor.yaml +++ b/config/prometheus/monitor.yaml @@ -4,7 +4,7 @@ apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: labels: - control-plane: controller-manager + netris-operator: controller-manager name: controller-manager-metrics-monitor namespace: system spec: @@ -13,4 +13,4 @@ spec: port: https selector: matchLabels: - control-plane: controller-manager + netris-operator: controller-manager diff --git a/config/rbac/auth_proxy_service.yaml b/config/rbac/auth_proxy_service.yaml index 6cf656b..1e62f80 100644 --- a/config/rbac/auth_proxy_service.yaml +++ b/config/rbac/auth_proxy_service.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Service metadata: labels: - control-plane: controller-manager + netris-operator: controller-manager name: controller-manager-metrics-service namespace: system spec: @@ -11,4 +11,4 @@ spec: port: 8443 targetPort: https selector: - control-plane: controller-manager + netris-operator: controller-manager diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index ebd64f9..3859f81 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -6,6 +6,26 @@ metadata: creationTimestamp: null name: manager-role rules: +- apiGroups: + - k8s.netris.ai + resources: + - vnetmeta + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - k8s.netris.ai + resources: + - vnetmeta/status + verbs: + - get + - patch + - update - apiGroups: - k8s.netris.ai resources: diff --git a/config/rbac/vnetmeta_editor_role.yaml b/config/rbac/vnetmeta_editor_role.yaml new file mode 100644 index 0000000..073c2bf --- /dev/null +++ b/config/rbac/vnetmeta_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit vnetmeta. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: vnetmeta-editor-role +rules: +- apiGroups: + - k8s.netris.ai + resources: + - vnetmeta + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - k8s.netris.ai + resources: + - vnetmeta/status + verbs: + - get diff --git a/config/rbac/vnetmeta_viewer_role.yaml b/config/rbac/vnetmeta_viewer_role.yaml new file mode 100644 index 0000000..d581301 --- /dev/null +++ b/config/rbac/vnetmeta_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view vnetmeta. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: vnetmeta-viewer-role +rules: +- apiGroups: + - k8s.netris.ai + resources: + - vnetmeta + verbs: + - get + - list + - watch +- apiGroups: + - k8s.netris.ai + resources: + - vnetmeta/status + verbs: + - get diff --git a/config/samples/k8s_v1alpha1_vnet-awesome.yml b/config/samples/k8s_v1alpha1_vnet-awesome.yml deleted file mode 100644 index db832f2..0000000 --- a/config/samples/k8s_v1alpha1_vnet-awesome.yml +++ /dev/null @@ -1,28 +0,0 @@ -apiVersion: k8s.netris.ai/v1alpha1 -kind: VNet -metadata: - name: netris-operator-test -spec: - owner: admin - state: active - sites: - - name: yerevan - switchPorts: - - name: swp4 - switchName: rlab-leaf1 - vlanId: 1050 - - name: swp5 - switchName: rlab-leaf1 - vlanId: 1050 - - name: san jose - switchPorts: - - name: swp10 - switchName: slab-leaf1 - vlanId: 1051 - - name: swp2 - switchName: slab-leaf1 - vlanId: 1051 - gateways: - - gateway4: 109.23.0.6/24 - - gateway4: 109.25.0.6/24 - - gateway6: 2001:db8:acad::fffe/64 diff --git a/config/samples/k8s_v1alpha1_vnet.yaml b/config/samples/k8s_v1alpha1_vnet.yaml deleted file mode 100644 index 9fa675b..0000000 --- a/config/samples/k8s_v1alpha1_vnet.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: k8s.netris.ai/v1alpha1 -kind: VNet -metadata: - name: netris-operator-test -spec: - name: netris-operator-test - owner: 1 - state: active - provisioning: 1 - tenants: [] - sites: - - 1 - gateways: - - gateway: 109.23.0.6 - gw_length: "24" - version: ipv4 - members: '[{"port_id":13288,"vlan_id":"44","tenant_id":1,"childPort":0,"parentPort":0,"member_state":"active","lacp":"off","port_name":"swp5(port5)@rlab-leaf1 - (Admin)","portIsUntagged":false}]' - va_mode: false - va_native_vlan: 1 - va_vlans: "" diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml deleted file mode 100644 index 6e8a038..0000000 --- a/config/samples/kustomization.yaml +++ /dev/null @@ -1,4 +0,0 @@ -## Append samples you want in your CSV to this file as resources ## -resources: -- k8s_v1alpha1_vnet.yaml -# +kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/webhook/service.yaml b/config/webhook/service.yaml index 31e0f82..306143a 100644 --- a/config/webhook/service.yaml +++ b/config/webhook/service.yaml @@ -9,4 +9,4 @@ spec: - port: 443 targetPort: 9443 selector: - control-plane: controller-manager + netris-operator: controller-manager diff --git a/configloader/config.go b/configloader/config.go index 43fd9ba..5c83397 100644 --- a/configloader/config.go +++ b/configloader/config.go @@ -1,22 +1,25 @@ package configloader import ( - "errors" "log" "os" "path" ) type config struct { - Controller controller `yaml:"controller"` + Controller controller `yaml:"controller"` + LogDevMode bool `yaml:"logdevmode" envconfig:"NOPERATOR_DEV_MODE"` + RequeueInterval int `yaml:"requeueinterval" envconfig:"NOPERATOR_REQUEUE_OPERATOR"` } type controller struct { - Host string `yaml:"address" envconfig:"CONTROLLER_HOST"` + Host string `yaml:"host" envconfig:"CONTROLLER_HOST"` Login string `yaml:"login" envconfig:"CONTROLLER_LOGIN"` Password string `yaml:"password" envconfig:"CONTROLLER_PASSWORD"` + Insecure bool `yaml:"insecure" envconfig:"CONTROLLER_INSECURE"` } +// Root . var Root *config func init() { @@ -29,12 +32,12 @@ func init() { err = Unmarshal(path.Join(wd, "configloader", "config.yml"), ptr) Root = ptr if err != nil { - if errors.Is(err, os.ErrNotExist) { - log.Printf("%s/configloader/config.yml not found. Config not loaded, no panic for tests", wd) + log.Fatalf("configloader error: %v", err) + } else { + if len(ptr.Controller.Host) == 0 { + log.Fatalln("Please set netris controller credentials") } else { - panic(err) + log.Printf("connecting to host - %v", ptr.Controller.Host) } - } else { - log.Printf("%s/configloader/config.yml config loaded", wd) } } diff --git a/configloader/config.yml b/configloader/config.yml index e46b4f9..2f5a245 100644 --- a/configloader/config.yml +++ b/configloader/config.yml @@ -1,4 +1,8 @@ controller: - # address: http://example.com # overwrite env: CONTROLLER_HOST + # host: http://example.com # overwrite env: CONTROLLER_HOST # login: login # overwrite env: CONTROLLER_LOGIN # password: pass # overwrite env: CONTROLLER_PASSWORD + # insecure: false # overwrite env: CONTROLLER_INSECURE + +# logdevmode: false # overwrite env: NOPERATOR_DEV_MODE +# requeueinterval: 15 # overwrite env: NOPERATOR_REQUEUE_OPERATOR diff --git a/configloader/configloader.go b/configloader/configloader.go index 6049f3e..c9305c7 100644 --- a/configloader/configloader.go +++ b/configloader/configloader.go @@ -2,9 +2,11 @@ package configloader import ( "errors" + "log" + "os" + "github.com/kelseyhightower/envconfig" "gopkg.in/yaml.v2" - "os" ) const ymlExt = ".yml" @@ -14,7 +16,12 @@ func readFile(path string, configPtr interface{}) error { if openErr != nil { return openErr } - defer f.Close() + defer func() { + err := f.Close() + if err != nil { + log.Printf("f.Close() error: %v", err) + } + }() decoder := yaml.NewDecoder(f) decErr := decoder.Decode(configPtr) @@ -34,7 +41,7 @@ func readEnv(configPtr interface{}) error { return nil } -// "path" yml file path +// Unmarshal "path" yml file path // "configPtr" pointer to result struct func Unmarshal(path string, configPtr interface{}) error { extension := path[len(path)-4:] @@ -44,7 +51,7 @@ func Unmarshal(path string, configPtr interface{}) error { fileErr := readFile(path, configPtr) if fileErr != nil { - return fileErr + log.Println("config.yml not loaded") } envErr := readEnv(configPtr) diff --git a/controllers/api_handler.go b/controllers/api_handler.go index ef63516..bce0919 100644 --- a/controllers/api_handler.go +++ b/controllers/api_handler.go @@ -1,252 +1,140 @@ +/* +Copyright 2020. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package controllers import ( - "bytes" "fmt" - "github.com/netrisx/netris-operator/configloader" - "io/ioutil" "log" - "net/http" - "net/url" - "sync" "time" -) - -// HTTPReply struct -type HTTPReply struct { - Data []byte - StatusCode int - Status string - Error error -} - -// ConductorAddresses struct -type ConductorAddresses struct { - General string - Auth string - Kubenet string - KubenetClusterInfo string - KubenetNode string - KubenetLB string - KubenetAPIStatus string - L4LB string - VNet string -} -var conductorAddresses = ConductorAddresses{ - General: "/api/general", - Auth: "/api/auth", - Kubenet: "/api/kubenet", - KubenetClusterInfo: "/api/kubenet/clusterinfo", - KubenetNode: "/api/kubenet/node", - KubenetLB: "/api/kubenet/l4lb/connect", - KubenetAPIStatus: "/api/kubenet/changeapistatus", - L4LB: "/api/kubenet/l4lb", - VNet: "/api/v-net", -} + api "github.com/netrisai/netrisapi" -// HTTPCred stores the credentials for connect to http server. User, Password, HTTP Clien e.t.c -type HTTPCred struct { - sync.Mutex - URL url.URL - LoginData loginData - Cookies []http.Cookie - ConnectSID string - Timeout int -} + k8sv1alpha1 "github.com/netrisai/netris-operator/api/v1alpha1" + "github.com/netrisai/netris-operator/configloader" +) -type loginData struct { - Login string `json:"user"` - Password string `json:"password"` - AuthScheme int `json:"auth_scheme_id"` -} +var ( + // Cred stores the Netris API usepoint. + Cred *api.HTTPCred + requeueInterval = time.Duration(10 * time.Second) -var cred *HTTPCred + // NStorage is the instance of the Netris API in-memory storage. + NStorage = NewStorage() +) func init() { + if configloader.Root.RequeueInterval > 0 { + requeueInterval = time.Duration(time.Duration(configloader.Root.RequeueInterval) * time.Second) + } + var err error - cred, err = newHTTPCredentials(10) + Cred, err = api.NewHTTPCredentials(configloader.Root.Controller.Host, configloader.Root.Controller.Login, configloader.Root.Controller.Password, 10) if err != nil { log.Panicf("newHTTPCredentials error %v", err) } - cred.LoginUser() + Cred.InsecureVerify(configloader.Root.Controller.Insecure) + err = Cred.LoginUser() if err != nil { log.Printf("LoginUser error %v", err) - } else { - log.Printf("Logined") } - go cred.checkAuthWithInterval() -} - -func newHTTPCredentials(timeout int) (*HTTPCred, error) { - URL, err := url.Parse(configloader.Root.Controller.Host) + go Cred.CheckAuthWithInterval() + err = NStorage.Download() if err != nil { - return nil, fmt.Errorf("{newHTTPCredentials} %s", err) + log.Printf("Storage.Download() error %v", err) } - if timeout == 0 { - timeout = 5 - } - return &HTTPCred{ - URL: *URL, - LoginData: loginData{ - Login: configloader.Root.Controller.Login, - Password: configloader.Root.Controller.Password, - }, - Timeout: timeout, - }, nil + go NStorage.DownloadWithInterval() + fmt.Println("Requeue interval", requeueInterval) } -// CheckAuth checks the user authorized or not -func (cred *HTTPCred) CheckAuth() error { - reply, err := cred.Get(cred.URL.String() + conductorAddresses.Auth) - if err != nil { - return fmt.Errorf("{CheckAuth} %s", err) - } - if reply.StatusCode == http.StatusOK { - return nil - } else if len(reply.Data) > 0 { - return fmt.Errorf("{CheckAuth} %s", reply.Data) - } - return fmt.Errorf("{CheckAuth} not authorized") -} - -// LoginUser login the user and get the cookies for future use -func (cred *HTTPCred) LoginUser() error { - cred.Lock() - defer cred.Unlock() - reqData := fmt.Sprintf("user=%s&password=%s&auth_scheme_id=1", cred.LoginData.Login, cred.LoginData.Password) - - URL := cred.URL.String() + conductorAddresses.Auth - - req, err := http.NewRequest("POST", URL, bytes.NewBufferString(reqData)) - if err != nil { - return fmt.Errorf("{LoginUser} %s", err) - } - - client := http.Client{ - Timeout: time.Duration(time.Duration(cred.Timeout) * time.Second), - } +func getPortsMeta(portNames []k8sv1alpha1.VNetSwitchPort) []k8sv1alpha1.VNetMetaMember { + hwPorts := make(map[string]*api.APIVNetMember) + portIsUntagged := false + for _, port := range portNames { + vlanID := 1 + if port.VlanID > 0 { + vlanID = port.VlanID + } + if vlanID == 1 { + portIsUntagged = true + } - resp, err := client.Do(req) + state := "active" + if len(port.State) > 0 { + if port.State == "active" || port.State == "disabled" { + state = port.State + } + } - if err != nil { - return fmt.Errorf("{LoginUser} %s", err) - } + hwPorts[port.Name] = &api.APIVNetMember{ + VLANID: vlanID, + PortIsUntagged: portIsUntagged, + MemberState: state, + } - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("{LoginUser} Authentication failed") } - - var cookies []http.Cookie - for _, cookie := range resp.Cookies() { - if cookie.Name == "connect.sid" { - cred.ConnectSID = cookie.Value + for portName := range hwPorts { + if port, yes := NStorage.PortsStorage.FindByName(portName); yes { + hwPorts[portName].PortID = port.ID + hwPorts[portName].PortName = portName + hwPorts[portName].TenantID = port.TenantID + hwPorts[portName].LACP = "off" + hwPorts[portName].ParentPort = port.ParentPort + // hwPorts[portName].Name = port.SlavePortName } - cookies = append(cookies, *cookie) } - cred.Cookies = cookies - return nil + members := []k8sv1alpha1.VNetMetaMember{} + for _, member := range hwPorts { + members = append(members, k8sv1alpha1.VNetMetaMember{ + ChildPort: member.ChildPort, + LACP: member.LACP, + MemberState: member.MemberState, + ParentPort: member.ParentPort, + PortIsUntagged: member.PortIsUntagged, + PortID: member.PortID, + PortName: member.PortName, + TenantID: member.TenantID, + VLANID: member.VLANID, + }) + } + return members } -// Get custom request -func (cred *HTTPCred) Get(address string) (reply HTTPReply, err error) { - request, err := http.NewRequest("GET", address, nil) - if err != nil { - return reply, fmt.Errorf("{Get} [%s] %s", address, err) - } - - request.Header.Set("Content-type", "application/json") - cred.Lock() - for _, cookie := range cred.Cookies { - cook := cookie - request.AddCookie(&cook) - } - cred.Unlock() - client := http.Client{ - Timeout: time.Duration(time.Duration(cred.Timeout) * time.Second), - } - - resp, err := client.Do(request) - if err != nil { - return reply, fmt.Errorf("{Get} [%s] %s", address, err) +func getSites(names []string) map[string]int { + siteList := map[string]int{} + for _, name := range names { + siteList[name] = 0 } - - reply.StatusCode = resp.StatusCode - reply.Status = resp.Status - - defer resp.Body.Close() - reply.Data, err = ioutil.ReadAll(resp.Body) - if err != nil { - return reply, fmt.Errorf("{Get} [%s] %s", address, err) + for siteName := range siteList { + if site, ok := NStorage.SitesStorage.FindByName(siteName); ok { + siteList[siteName] = site.ID + } } - return reply, err + return siteList } -// CustomBodyRequest impelements the POST, PUT, UPDATE requests -func (cred *HTTPCred) CustomBodyRequest(method string, address string, data []byte) (reply HTTPReply, err error) { - requestBody := bytes.NewBuffer(data) - request, err := http.NewRequest(method, address, requestBody) +func getTenantID(name string) int { + tenants, err := Cred.GetTenants() if err != nil { - return reply, fmt.Errorf("{CustomBodyRequest} [%s] [%s] %s", method, address, err) - } - - request.Header.Set("Content-type", "application/json") - cred.Lock() - for _, cookie := range cred.Cookies { - cook := cookie - request.AddCookie(&cook) - } - cred.Unlock() - - client := http.Client{ - Timeout: time.Duration(time.Duration(cred.Timeout) * time.Second), + fmt.Println(err) } - - resp, err := client.Do(request) - if err != nil { - return reply, fmt.Errorf("{CustomBodyRequest} [%s] [%s] %s", method, address, err) - } - - reply.StatusCode = resp.StatusCode - reply.Status = resp.Status - - defer resp.Body.Close() - reply.Data, err = ioutil.ReadAll(resp.Body) - if err != nil { - return reply, fmt.Errorf("{CustomBodyRequest} [%s] [%s] %s", method, address, err) - } - - return reply, nil -} - -// Post custom request -func (cred *HTTPCred) Post(address string, data []byte) (reply HTTPReply, err error) { - return cred.CustomBodyRequest("POST", address, data) -} - -// Put custom request -func (cred *HTTPCred) Put(address string, data []byte) (reply HTTPReply, err error) { - return cred.CustomBodyRequest("PUT", address, data) -} - -// Delete custom request -func (cred *HTTPCred) Delete(address string, data []byte) (reply HTTPReply, err error) { - return cred.CustomBodyRequest("DELETE", address, data) -} - -func (cred *HTTPCred) checkAuthWithInterval() { - ticker := time.NewTicker(5 * time.Second) - for { - select { - case <-ticker.C: - err := cred.CheckAuth() - if err != nil { - log.Println(err) - err := cred.LoginUser() - if err != nil { - log.Println(err) - } - } + for _, tenant := range tenants { + if tenant.Name == name { + return tenant.ID } } + return 0 } diff --git a/controllers/storage.go b/controllers/storage.go new file mode 100644 index 0000000..0f489f6 --- /dev/null +++ b/controllers/storage.go @@ -0,0 +1,355 @@ +/* +Copyright 2020. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "fmt" + "strconv" + "sync" + "time" + + api "github.com/netrisai/netrisapi" +) + +/******************************************************************************** + Storage +*********************************************************************************/ + +// Storage . +type Storage struct { + sync.Mutex + *PortsStorage + *SitesStorage + *TenantsStorage + *VNetStorage +} + +// NewStorage . +func NewStorage() *Storage { + return &Storage{ + PortsStorage: NewPortStorage(), + SitesStorage: NewSitesStorage(), + TenantsStorage: NewTenantsStorage(), + VNetStorage: NewVNetStorage(), + } +} + +// Download . +func (s *Storage) Download() error { + s.Lock() + defer s.Unlock() + err := s.PortsStorage.Download() + err = s.SitesStorage.Download() + err = s.TenantsStorage.Download() + err = s.VNetStorage.Download() + return err +} + +// DownloadWithInterval . +func (s *Storage) DownloadWithInterval() { + ticker := time.NewTicker(10 * time.Second) + for { + select { + case <-ticker.C: + err := s.Download() + if err != nil { + fmt.Println(err) + } + } + } +} + +/******************************************************************************** + Port Storage +*********************************************************************************/ + +// PortsStorage . +type PortsStorage struct { + sync.Mutex + Ports []*api.APIPort +} + +// NewPortStorage . +func NewPortStorage() *PortsStorage { + return &PortsStorage{} +} + +func (p *PortsStorage) storeAll(ports []*api.APIPort) { + p.Ports = ports +} + +// GetAll . +func (p *PortsStorage) GetAll() []*api.APIPort { + p.Lock() + defer p.Unlock() + return p.getAll() +} + +func (p *PortsStorage) getAll() []*api.APIPort { + return p.Ports +} + +// FindByName . +func (p *PortsStorage) FindByName(name string) (*api.APIPort, bool) { + p.Lock() + defer p.Unlock() + return p.findByName(name) +} + +func (p *PortsStorage) findByName(name string) (*api.APIPort, bool) { + for _, port := range p.Ports { + portName := fmt.Sprintf("%s@%s", port.SlavePortName, port.SwitchName) + if portName == name { + return port, true + } + } + return nil, false +} + +// Download . +func (p *PortsStorage) Download() error { + p.Lock() + defer p.Unlock() + ports, err := Cred.GetPorts() + if err != nil { + return err + } + p.storeAll(ports) + return nil +} + +/******************************************************************************** + Sites Storage +*********************************************************************************/ + +// SitesStorage . +type SitesStorage struct { + sync.Mutex + Sites []*api.APISite +} + +// NewSitesStorage . +func NewSitesStorage() *SitesStorage { + return &SitesStorage{} +} + +// GetAll . +func (p *SitesStorage) GetAll() []*api.APISite { + p.Lock() + defer p.Unlock() + return p.getAll() +} + +func (p *SitesStorage) getAll() []*api.APISite { + return p.Sites +} + +func (p *SitesStorage) storeAll(items []*api.APISite) { + p.Sites = items +} + +// FindByName . +func (p *SitesStorage) FindByName(name string) (*api.APISite, bool) { + p.Lock() + defer p.Unlock() + return p.findByName(name) +} + +func (p *SitesStorage) findByName(name string) (*api.APISite, bool) { + for _, site := range p.Sites { + if site.Name == name { + return site, true + } + } + return nil, false +} + +// Download . +func (p *SitesStorage) download() error { + items, err := Cred.GetSites() + if err != nil { + return err + } + p.storeAll(items) + return nil +} + +// Download . +func (p *SitesStorage) Download() error { + p.Lock() + defer p.Unlock() + return p.download() +} + +/******************************************************************************** + Tenants Storage +*********************************************************************************/ + +// TenantsStorage . +type TenantsStorage struct { + sync.Mutex + Tenants []*api.APITenant +} + +// NewTenantsStorage . +func NewTenantsStorage() *TenantsStorage { + return &TenantsStorage{} +} + +// GetAll . +func (p *TenantsStorage) GetAll() []*api.APITenant { + p.Lock() + defer p.Unlock() + return p.getAll() +} + +func (p *TenantsStorage) getAll() []*api.APITenant { + return p.Tenants +} + +func (p *TenantsStorage) storeAll(items []*api.APITenant) { + p.Tenants = items +} + +// FindByName . +func (p *TenantsStorage) FindByName(name string) (*api.APITenant, bool) { + p.Lock() + defer p.Unlock() + return p.findByName(name) +} + +func (p *TenantsStorage) findByName(name string) (*api.APITenant, bool) { + for _, item := range p.Tenants { + if item.Name == name { + return item, true + } + } + return nil, false +} + +// FindByID . +func (p *TenantsStorage) FindByID(id int) (*api.APITenant, bool) { + p.Lock() + defer p.Unlock() + return p.findByID(id) +} + +func (p *TenantsStorage) findByID(id int) (*api.APITenant, bool) { + for _, item := range p.Tenants { + if item.ID == id { + return item, true + } + } + return nil, false +} + +// Download . +func (p *TenantsStorage) download() error { + items, err := Cred.GetTenants() + if err != nil { + return err + } + p.storeAll(items) + return nil +} + +// Download . +func (p *TenantsStorage) Download() error { + p.Lock() + defer p.Unlock() + return p.download() +} + +/******************************************************************************** + VNet Storage +*********************************************************************************/ + +// VNetStorage . +type VNetStorage struct { + sync.Mutex + VNets []*api.APIVNet +} + +// NewVNetStorage . +func NewVNetStorage() *VNetStorage { + return &VNetStorage{} +} + +// GetAll . +func (p *VNetStorage) GetAll() []*api.APIVNet { + p.Lock() + defer p.Unlock() + return p.getAll() +} + +func (p *VNetStorage) getAll() []*api.APIVNet { + return p.VNets +} + +func (p *VNetStorage) storeAll(items []*api.APIVNet) { + p.VNets = items +} + +// FindByName . +func (p *VNetStorage) FindByName(name string) (*api.APIVNet, bool) { + p.Lock() + defer p.Unlock() + return p.findByName(name) +} + +func (p *VNetStorage) findByName(name string) (*api.APIVNet, bool) { + for _, item := range p.VNets { + if item.Name == name { + return item, true + } + } + return nil, false +} + +// FindByID . +func (p *VNetStorage) FindByID(id int) (*api.APIVNet, bool) { + p.Lock() + defer p.Unlock() + return p.findByID(id) +} + +func (p *VNetStorage) findByID(id int) (*api.APIVNet, bool) { + for _, item := range p.VNets { + vnetID, _ := strconv.Atoi(item.ID) + if vnetID == id { + return item, true + } + } + return nil, false +} + +// Download . +func (p *VNetStorage) download() error { + items, err := Cred.GetVNets() + if err != nil { + return err + } + p.storeAll(items) + return nil +} + +// Download . +func (p *VNetStorage) Download() error { + p.Lock() + defer p.Unlock() + return p.download() +} diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 07fecb3..3c2a6d2 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -30,7 +30,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - k8sv1alpha1 "github.com/netrisx/netris-operator/api/v1alpha1" + k8sv1alpha1 "github.com/netrisai/netris-operator/api/v1alpha1" // +kubebuilder:scaffold:imports ) diff --git a/controllers/translations.go b/controllers/translations.go new file mode 100644 index 0000000..1e82964 --- /dev/null +++ b/controllers/translations.go @@ -0,0 +1,344 @@ +/* +Copyright 2020. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "fmt" + + k8sv1alpha1 "github.com/netrisai/netris-operator/api/v1alpha1" + api "github.com/netrisai/netrisapi" + "github.com/r3labs/diff/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// VnetToVnetMeta converts the VNet resource to VNetMeta type and used for add the VNet for Netris API. +func (r *VNetReconciler) VnetToVnetMeta(vnet *k8sv1alpha1.VNet) (*k8sv1alpha1.VNetMeta, error) { + ports := []k8sv1alpha1.VNetSwitchPort{} + siteNames := []string{} + apiGateways := []k8sv1alpha1.VNetMetaGateway{} + + for _, site := range vnet.Spec.Sites { + siteNames = append(siteNames, site.Name) + for _, port := range site.SwitchPorts { + ports = append(ports, port) + } + for _, gateway := range site.Gateways { + apiGateways = append(apiGateways, makeGateway(gateway)) + } + } + prts := getPortsMeta(ports) + + sites := getSites(siteNames) + sitesList := []k8sv1alpha1.VNetMetaSite{} + + for name, id := range sites { + sitesList = append(sitesList, k8sv1alpha1.VNetMetaSite{ + Name: name, + ID: id, + }) + } + + tenantID := 0 + + tenant, ok := NStorage.TenantsStorage.FindByName(vnet.Spec.Owner) + if !ok { + return nil, fmt.Errorf("Tenant '%s' not found", vnet.Spec.Owner) + } + tenantID = tenant.ID + + guestTenants := []int{} + for _, guest := range vnet.Spec.GuestTenants { + tenant, ok := NStorage.TenantsStorage.FindByName(guest) + if !ok { + + return nil, fmt.Errorf("Guest tenant '%s' not found", guest) + } + guestTenants = append(guestTenants, tenant.ID) + } + + state := "active" + if len(vnet.Spec.State) > 0 { + if !(vnet.Spec.State == "active" || vnet.Spec.State == "disabled") { + return nil, fmt.Errorf("Invalid spec.state field") + } + state = vnet.Spec.State + } + + imported := false + if i, ok := vnet.GetAnnotations()["resource.k8s.netris.ai/import"]; ok { + if i == "true" { + imported = true + } + } + + vnetMeta := &k8sv1alpha1.VNetMeta{ + ObjectMeta: metav1.ObjectMeta{ + Name: string(vnet.GetUID()), + Namespace: "default", + }, + TypeMeta: metav1.TypeMeta{}, + Spec: k8sv1alpha1.VNetMetaSpec{ + Imported: imported, + Name: string(vnet.GetUID()), + VnetName: vnet.Name, + Sites: sitesList, + State: state, + OwnerID: tenantID, + Tenants: guestTenants, + Gateways: apiGateways, + Members: prts, + Provisioning: 1, + VaMode: false, + VaNativeVLAN: 1, + VaVLANs: "", + }, + } + + return vnetMeta, nil +} + +// VnetMetaToNetris converts the k8s VNet resource to Netris type and used for add the VNet for Netris API. +func VnetMetaToNetris(vnetMeta *k8sv1alpha1.VNetMeta) (*api.APIVNetAdd, error) { + siteNames := []string{} + apiGateways := []api.APIVNetGateway{} + + for _, site := range vnetMeta.Spec.Sites { + siteNames = append(siteNames, site.Name) + } + for _, gateway := range vnetMeta.Spec.Gateways { + apiGateways = append(apiGateways, api.APIVNetGateway{ + Gateway: gateway.Gateway, + GwLength: gateway.GwLength, + ID: gateway.ID, + Version: gateway.Version, + }) + } + + sites := getSites(siteNames) + siteIDs := []int{} + for _, id := range sites { + siteIDs = append(siteIDs, id) + } + + vnetAdd := &api.APIVNetAdd{ + Name: vnetMeta.Spec.VnetName, + Sites: siteIDs, + Owner: vnetMeta.Spec.OwnerID, + State: vnetMeta.Spec.State, + Tenants: vnetMeta.Spec.Tenants, + Gateways: apiGateways, + Members: k8sMemberToAPIMember(vnetMeta.Spec.Members).String(), + VaMode: false, + VaNativeVLAN: 1, + VaVLANs: "", + Provisioning: 1, + } + + return vnetAdd, nil +} + +// VnetMetaToNetrisUpdate converts the k8s VNet resource to Netris type and used for update the VNet for Netris API. +func VnetMetaToNetrisUpdate(vnetMeta *k8sv1alpha1.VNetMeta) (*api.APIVNetUpdate, error) { + apiGateways := []api.APIVNetGateway{} + + for _, gateway := range vnetMeta.Spec.Gateways { + apiGateways = append(apiGateways, api.APIVNetGateway{ + Gateway: gateway.Gateway, + GwLength: gateway.GwLength, + ID: gateway.ID, + Version: gateway.Version, + }) + } + + siteIDs := []int{} + for _, site := range vnetMeta.Spec.Sites { + siteIDs = append(siteIDs, site.ID) + } + + vnetUpdate := &api.APIVNetUpdate{ + ID: vnetMeta.Spec.ID, + Name: vnetMeta.Spec.VnetName, + Sites: siteIDs, + State: vnetMeta.Spec.State, + Owner: vnetMeta.Spec.OwnerID, + Tenants: vnetMeta.Spec.Tenants, + Gateways: apiGateways, + Members: k8sMemberToAPIMember(vnetMeta.Spec.Members).String(), + VaMode: false, + VaNativeVLAN: "1", + VaVLANs: "", + Provisioning: 1, + } + + return vnetUpdate, nil +} + +func compareVNetMetaAPIVnetGateways(vnetMetaGateways []k8sv1alpha1.VNetMetaGateway, apiVnetGateways []api.APIVNetGateway) bool { + + type gateway struct { + Gateway string `diff:"gateway"` + Length int `diff:"gwLength"` + } + + vnetGateways := []gateway{} + apiGateways := []gateway{} + + for _, g := range vnetMetaGateways { + vnetGateways = append(vnetGateways, gateway{ + Gateway: g.Gateway, + Length: g.GwLength, + }) + } + + for _, g := range apiVnetGateways { + apiGateways = append(apiGateways, gateway{ + Gateway: g.Gateway, + Length: g.GwLength, + }) + } + + changelog, _ := diff.Diff(vnetGateways, apiGateways) + + if len(changelog) > 0 { + return false + } + + return true +} + +func compareVNetMetaAPIVnetMembers(vnetMetaMembers []k8sv1alpha1.VNetMetaMember, apiVnetMembers []api.APIVNetInfoMember) bool { + + type member struct { + PortID int `diff:"port_id"` + TenantID int `diff:"tenant_id"` + VLANID int `diff:"vlan_id"` + State string `diff:"state"` + } + + vnetMembers := []member{} + apiMembers := []member{} + + for _, m := range vnetMetaMembers { + vnetMembers = append(vnetMembers, member{ + PortID: m.PortID, + TenantID: m.TenantID, + VLANID: m.VLANID, + State: m.MemberState, + }) + } + + for _, m := range apiVnetMembers { + apiMembers = append(apiMembers, member{ + PortID: m.PortID, + TenantID: m.TenantID, + VLANID: m.VlanID, + State: m.MemberState, + }) + } + + changelog, _ := diff.Diff(vnetMembers, apiMembers) + + if len(changelog) > 0 { + return false + } + + return true +} + +func compareVNetMetaAPIVnetTenants(vnetMetaTenants []int, apiVnetTenants []int) bool { + changelog, _ := diff.Diff(vnetMetaTenants, apiVnetTenants) + + if len(changelog) > 0 { + return false + } + + return true +} + +func compareVNetMetaAPIVnetSites(vnetMetaSites []k8sv1alpha1.VNetMetaSite, apiVnetSites []int) bool { + + k8sSites := make(map[int]string) + for _, site := range vnetMetaSites { + k8sSites[site.ID] = "" + } + + for _, siteID := range apiVnetSites { + if _, ok := k8sSites[siteID]; !ok { + return false + } + } + + return true +} + +func compareVNetMetaAPIVnet(vnetMeta *k8sv1alpha1.VNetMeta, apiVnet *api.APIVNetInfo) bool { + + if ok := compareVNetMetaAPIVnetSites(vnetMeta.Spec.Sites, apiVnet.SitesID); !ok { + return false + } + if ok := compareVNetMetaAPIVnetGateways(vnetMeta.Spec.Gateways, apiVnet.Gateways); !ok { + return false + } + if ok := compareVNetMetaAPIVnetMembers(vnetMeta.Spec.Members, apiVnet.Members); !ok { + return false + } + + if vnetMeta.Spec.VnetName != apiVnet.Name { + return false + } + + if vnetMeta.Spec.OwnerID != apiVnet.Owner { + return false + } + + if ok := compareVNetMetaAPIVnetTenants(vnetMeta.Spec.Tenants, apiVnet.TenantsID); !ok { + return false + } + + apiVaMode := false + if apiVnet.VaMode > 0 { + apiVaMode = true + } + + if vnetMeta.Spec.VaMode != apiVaMode { + return false + } + + if vnetMeta.Spec.VaVLANs != apiVnet.VaVlans { + return false + } + + return true +} + +func k8sMemberToAPIMember(portNames []k8sv1alpha1.VNetMetaMember) *api.APIVNetMembers { + members := &api.APIVNetMembers{} + for _, port := range portNames { + members.Add(api.APIVNetMember{ + ChildPort: port.ChildPort, + LACP: port.LACP, + MemberState: port.MemberState, + ParentPort: port.ParentPort, + PortIsUntagged: port.PortIsUntagged, + PortID: port.PortID, + PortName: port.PortName, + TenantID: port.TenantID, + VLANID: port.VLANID, + }) + } + return members +} diff --git a/controllers/utils.go b/controllers/utils.go new file mode 100644 index 0000000..1f2691c --- /dev/null +++ b/controllers/utils.go @@ -0,0 +1,86 @@ +/* +Copyright 2020. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "fmt" + "net" + "strconv" + + api "github.com/netrisai/netrisapi" + + k8sv1alpha1 "github.com/netrisai/netris-operator/api/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +func ignoreDeletionPredicate() predicate.Predicate { + return predicate.Funcs{ + UpdateFunc: func(e event.UpdateEvent) bool { + fmt.Println("UPDATE EVENT") + // Ignore updates to CR status in which case metadata.Generation does not change + return e.MetaOld.GetGeneration() != e.MetaNew.GetGeneration() + }, + DeleteFunc: func(e event.DeleteEvent) bool { + fmt.Println("DELETE EVENT") + // Evaluates to false if the object has been confirmed deleted. + return true + }, + } +} + +func makeGateway(gateway string) k8sv1alpha1.VNetMetaGateway { + version := "" + ip, ipNet, err := net.ParseCIDR(gateway) + + if err != nil { + fmt.Println(err) + return k8sv1alpha1.VNetMetaGateway{} + } + + if len(ip.To4()) == net.IPv4len { + version = "ipv4" + } else { + version = "ipv6" + } + + gwLength, _ := ipNet.Mask.Size() + apiGateway := k8sv1alpha1.VNetMetaGateway{ + Gateway: ip.String(), + GwLength: gwLength, + Version: version, + } + return apiGateway +} + +func getVNet(id int) (vnet *api.APIVNet, err error) { + vnets, err := Cred.GetVNets() + if err != nil { + return vnet, err + } + for _, v := range vnets { + vid, err := strconv.Atoi(v.ID) + if err != nil { + return vnet, err + } + if vid == id { + return v, nil + } + } + + return vnet, fmt.Errorf("VNet not found in Netris") +} diff --git a/controllers/vnet_controller.go b/controllers/vnet_controller.go index 7c9dc9a..e5f4eb4 100644 --- a/controllers/vnet_controller.go +++ b/controllers/vnet_controller.go @@ -19,20 +19,17 @@ package controllers import ( "context" "fmt" - "log" - "strconv" - "time" + "go.uber.org/zap/zapcore" "k8s.io/apimachinery/pkg/api/errors" - "encoding/json" - "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - k8sv1alpha1 "github.com/netrisx/netris-operator/api/v1alpha1" + k8sv1alpha1 "github.com/netrisai/netris-operator/api/v1alpha1" + api "github.com/netrisai/netrisapi" ) // VNetReconciler reconciles a VNet object @@ -42,81 +39,170 @@ type VNetReconciler struct { Scheme *runtime.Scheme } -// unmarshaledData struct -var unmarshaledData map[string]interface{} - // +kubebuilder:rbac:groups=k8s.netris.ai,resources=vnets,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=k8s.netris.ai,resources=vnets/status,verbs=get;update;patch // Reconcile vnet events func (r *VNetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() - _ = r.Log.WithValues("vnet", req.NamespacedName) + logger := r.Log.WithValues("name", req.NamespacedName) + debugLogger := logger.V(int(zapcore.WarnLevel)) + vnet := &k8sv1alpha1.VNet{} - reconciledResource := &k8sv1alpha1.VNet{} - err := r.Get(context.Background(), req.NamespacedName, reconciledResource) - if err != nil { + if err := r.Get(context.Background(), req.NamespacedName, vnet); err != nil { if errors.IsNotFound(err) { - fmt.Println("GO TO DELETE RESOURSE") + debugLogger.Info(err.Error()) + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + vnetMetaNamespaced := req.NamespacedName + vnetMetaNamespaced.Name = string(vnet.GetUID()) + vnetMeta := &k8sv1alpha1.VNetMeta{} + metaFound := true + if err := r.Get(context.Background(), vnetMetaNamespaced, vnetMeta); err != nil { + if errors.IsNotFound(err) { + debugLogger.Info(err.Error()) + metaFound = false + vnetMeta = nil } else { - log.Printf("r.Get 1: %v", err) + return ctrl.Result{}, err } - } else { - fmt.Println("GO TO CREATE/UPDATE RESOURSE") - reconciledResourceSpecJSON, err := json.Marshal(reconciledResource.Spec) + } + + if vnet.DeletionTimestamp != nil { + logger.Info("Go to delete") + _, err := r.deleteVNet(vnet, vnetMeta) if err != nil { - log.Printf("reconciledResourceSpecJSON error: %v", err) + logger.Error(fmt.Errorf("{deleteVNet} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil } - if reconciledResource.Spec.ID == 0 { - fmt.Println("GO TO CREATE") - fmt.Println("Create with params: ---", string(reconciledResourceSpecJSON)) - createVNet, err := AddVNet(cred, reconciledResourceSpecJSON) + logger.Info("Vnet deleted") + return ctrl.Result{}, nil + } + + if metaFound { + debugLogger.Info("Meta found") + if vnet.GetGeneration() != vnetMeta.Spec.VnetCRGeneration { + debugLogger.Info("Generating New Meta") + vnetID := vnetMeta.Spec.ID + newVnetMeta, err := r.VnetToVnetMeta(vnet) if err != nil { - log.Printf("createVNet error: %v", err) + logger.Error(fmt.Errorf("{VnetToVnetMeta} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil } - fmt.Println(string(createVNet.Data)) - if err := json.Unmarshal(createVNet.Data, &unmarshaledData); err != nil { - log.Fatalf("vnetResponseUnmarshal error: %v", err) + vnetMeta.Spec = newVnetMeta.DeepCopy().Spec + vnetMeta.Spec.ID = vnetID + vnetMeta.Spec.VnetCRGeneration = vnet.GetGeneration() + + err = r.Update(context.Background(), vnetMeta.DeepCopyObject(), &client.UpdateOptions{}) + if err != nil { + logger.Error(fmt.Errorf("{vnetMeta Update} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil } - isSuccess := unmarshaledData["isSuccess"].(bool) - if isSuccess { - vnetID := unmarshaledData["data"].(map[string]interface{}) - fmt.Println("vnetID - ", vnetID["circuitID"]) - vid, err := strconv.Atoi(fmt.Sprint(vnetID["circuitID"])) - if err != nil { - fmt.Println("id convert error") - } - reconciledResource.Spec.ID = vid - r.Patch(context.Background(), reconciledResource.DeepCopyObject(), client.Merge, &client.PatchOptions{}) - if err != nil { - log.Printf("r.Patch( error: %v", err) - } + } + } else { + debugLogger.Info("Meta not found") + if vnet.GetFinalizers() == nil { + vnet.SetFinalizers([]string{"vnet.k8s.netris.ai/delete"}) + err := r.Patch(context.Background(), vnet.DeepCopyObject(), client.Merge, &client.PatchOptions{}) + if err != nil { + logger.Error(fmt.Errorf("{Patch VNet Finalizer} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil } + return ctrl.Result{}, nil + } - } else { - fmt.Println("GO TO UPDATE") + vnetMeta, err := r.VnetToVnetMeta(vnet) + if err != nil { + logger.Error(fmt.Errorf("{VnetToVnetMeta} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + + vnetMeta.Spec.VnetCRGeneration = vnet.GetGeneration() + + if err := r.Create(context.Background(), vnetMeta.DeepCopyObject(), &client.CreateOptions{}); err != nil { + logger.Error(fmt.Errorf("{vnetMeta Create} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil } } - // return ctrl.Result{}, nil - return ctrl.Result{RequeueAfter: time.Second * 60}, nil + return ctrl.Result{}, nil +} + +func updateVNet(vnet *api.APIVNetUpdate) (ctrl.Result, error) { + reply, err := Cred.ValidateVNet(vnet) + if err != nil { + return ctrl.Result{}, fmt.Errorf("{updateVNet} %s", err) + } + resp, err := api.ParseAPIResponse(reply.Data) + if !resp.IsSuccess { + return ctrl.Result{}, fmt.Errorf("{updateVNet} %s", fmt.Errorf(resp.Message)) + } + + reply, err = Cred.UpdateVNet(vnet) + if err != nil { + return ctrl.Result{}, fmt.Errorf("{updateVNet} %s", err) + } + resp, err = api.ParseAPIResponse(reply.Data) + if !resp.IsSuccess { + return ctrl.Result{}, fmt.Errorf("{updateVNet} %s", fmt.Errorf(resp.Message)) + } + + return ctrl.Result{}, nil +} + +func (r *VNetReconciler) deleteVNet(vnet *k8sv1alpha1.VNet, vnetMeta *k8sv1alpha1.VNetMeta) (ctrl.Result, error) { + if vnetMeta != nil && vnetMeta.Spec.ID > 0 { + reply, err := Cred.DeleteVNet(vnetMeta.Spec.ID, []int{1}) + + if err != nil { + return ctrl.Result{}, fmt.Errorf("{deleteVNet} %s", err) + } + resp, err := api.ParseAPIResponse(reply.Data) + if !resp.IsSuccess { + if resp.Message != "Invalid circuit ID" { + return ctrl.Result{}, fmt.Errorf("{deleteVNet} %s", fmt.Errorf(resp.Message)) + } + } + } + return r.deleteCRs(vnet, vnetMeta) +} + +func (r *VNetReconciler) deleteCRs(vnet *k8sv1alpha1.VNet, vnetMeta *k8sv1alpha1.VNetMeta) (ctrl.Result, error) { + if vnetMeta != nil { + _, err := r.deleteVnetMetaCR(vnetMeta) + if err != nil { + return ctrl.Result{}, fmt.Errorf("{deleteCRs} %s", err) + } + } + + return r.deleteVnetCR(vnet) +} + +func (r *VNetReconciler) deleteVnetCR(vnet *k8sv1alpha1.VNet) (ctrl.Result, error) { + vnet.ObjectMeta.SetFinalizers(nil) + vnet.SetFinalizers(nil) + if err := r.Update(context.Background(), vnet.DeepCopyObject(), &client.UpdateOptions{}); err != nil { + return ctrl.Result{}, fmt.Errorf("{deleteVnetCR} %s", err) + } + + return ctrl.Result{}, nil +} + +func (r *VNetReconciler) deleteVnetMetaCR(vnetMeta *k8sv1alpha1.VNetMeta) (ctrl.Result, error) { + if err := r.Delete(context.Background(), vnetMeta.DeepCopyObject(), &client.DeleteOptions{}); err != nil { + return ctrl.Result{}, fmt.Errorf("{deleteVnetMetaCR} %s", err) + } + + return ctrl.Result{}, nil } // SetupWithManager Resources func (r *VNetReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&k8sv1alpha1.VNet{}). + // WithEventFilter(ignoreDeletionPredicate()). Complete(r) } - -// AddVNet new -func AddVNet(cred *HTTPCred, vnet []byte) (reply HTTPReply, err error) { - address := cred.URL.String() + conductorAddresses.VNet - reply, err = cred.Post(address, vnet) - if err != nil { - return reply, err - } - - return reply, nil -} diff --git a/controllers/vnetmeta_controller.go b/controllers/vnetmeta_controller.go new file mode 100644 index 0000000..8050917 --- /dev/null +++ b/controllers/vnetmeta_controller.go @@ -0,0 +1,181 @@ +/* +Copyright 2020. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "fmt" + "strconv" + + "go.uber.org/zap/zapcore" + "k8s.io/apimachinery/pkg/api/errors" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + k8sv1alpha1 "github.com/netrisai/netris-operator/api/v1alpha1" + api "github.com/netrisai/netrisapi" +) + +// VNetMetaReconciler reconciles a VNetMeta object +type VNetMetaReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=k8s.netris.ai,resources=vnetmeta,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=k8s.netris.ai,resources=vnetmeta/status,verbs=get;update;patch + +// Reconcile . +func (r *VNetMetaReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + _ = context.Background() + logger := r.Log.WithValues("name", req.NamespacedName) + debugLogger := logger.V(int(zapcore.WarnLevel)) + + vnetMeta := &k8sv1alpha1.VNetMeta{} + if err := r.Get(context.Background(), req.NamespacedName, vnetMeta); err != nil { + if errors.IsNotFound(err) { + debugLogger.Info(err.Error()) + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + if vnetMeta.DeletionTimestamp != nil { + return ctrl.Result{}, nil + } + + if vnetMeta.Spec.ID == 0 { + debugLogger.Info("ID Not found in meta") + + if vnetMeta.Spec.Imported { + logger.Info("Importing vnet") + debugLogger.Info("Imported yaml mode. Finding VNet by name") + if vnet, ok := NStorage.VNetStorage.findByName(vnetMeta.Spec.VnetName); ok { + debugLogger.Info("Imported yaml mode. Vnet found") + vnetID, err := strconv.Atoi(vnet.ID) + if err != nil { + debugLogger.Info(err.Error()) + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + vnetMeta.Spec.ID = vnetID + err = r.Patch(context.Background(), vnetMeta.DeepCopyObject(), client.Merge, &client.PatchOptions{}) + if err != nil { + logger.Error(fmt.Errorf("{patch vnetmeta.Spec.ID} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + debugLogger.Info("Imported yaml mode. ID patched") + logger.Info("VNet imported") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + logger.Info("VNet not found for import") + debugLogger.Info("Imported yaml mode. VNet not found") + } + + logger.Info("Creating VNet") + if _, err := r.createVNet(vnetMeta); err != nil { + logger.Error(fmt.Errorf("{createVNet} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + logger.Info("VNet Created") + } else { + vnets, err := Cred.GetVNetsByID(vnetMeta.Spec.ID) + if err != nil { + logger.Error(fmt.Errorf("{GetVNetsByID} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + if len(vnets) == 0 { + debugLogger.Info("VNet not found in Netris") + debugLogger.Info("Going to create VNet") + logger.Info("Creating VNet") + if _, err := r.createVNet(vnetMeta); err != nil { + logger.Error(fmt.Errorf("{createVNet} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + logger.Info("VNet Created") + } else { + apiVnet := vnets[0] + debugLogger.Info("Comparing VnetMeta with Netris Vnet") + if ok := compareVNetMetaAPIVnet(vnetMeta, apiVnet); ok { + debugLogger.Info("Nothing Changed") + } else { + debugLogger.Info("Something changed") + debugLogger.Info("Go to update Vnet in Netris") + logger.Info("Updating VNet") + vnetMeta.Spec.State = apiVnet.State + updateVnet, err := VnetMetaToNetrisUpdate(vnetMeta) + if err != nil { + logger.Error(fmt.Errorf("{VnetMetaToNetrisUpdate} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + _, err = updateVNet(updateVnet) + if err != nil { + logger.Error(fmt.Errorf("{updateVNet} %s", err), "") + return ctrl.Result{RequeueAfter: requeueInterval}, nil + } + logger.Info("VNet Updated") + } + } + } + + return ctrl.Result{RequeueAfter: requeueInterval}, nil +} + +// SetupWithManager . +func (r *VNetMetaReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&k8sv1alpha1.VNetMeta{}). + Complete(r) +} + +func (r *VNetMetaReconciler) createVNet(vnetMeta *k8sv1alpha1.VNetMeta) (ctrl.Result, error) { + debugLogger := r.Log.WithValues( + "name", fmt.Sprintf("%s/%s", vnetMeta.Namespace, vnetMeta.Name), + "vnetName", vnetMeta.Spec.VnetName, + ).V(int(zapcore.WarnLevel)) + + vnetAdd, err := VnetMetaToNetris(vnetMeta) + if err != nil { + return ctrl.Result{}, err + } + reply, err := Cred.AddVNet(vnetAdd) + if err != nil { + return ctrl.Result{}, err + } + resp, err := api.ParseAPIResponse(reply.Data) + if !resp.IsSuccess { + return ctrl.Result{}, fmt.Errorf(resp.Message) + } + + idStruct := api.APIVNetAddReply{} + api.CustomDecode(resp.Data, &idStruct) + + debugLogger.Info("VNet Created", "id", idStruct.CircuitID) + + vnetMeta.Spec.ID = idStruct.CircuitID + + err = r.Patch(context.Background(), vnetMeta.DeepCopyObject(), client.Merge, &client.PatchOptions{}) // requeue + if err != nil { + return ctrl.Result{}, err + } + + debugLogger.Info("ID patched to meta", "id", idStruct.CircuitID) + return ctrl.Result{}, nil +} diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..bad6162 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,82 @@ +# Netris-Operator Deployment + +Netris-operator runs within your Kubernetes cluster as a deployment resource. It utilizes CustomResourceDefinitions to configure netris cloud resources. + +It is deployed using regular YAML manifests, like any other application on Kubernetes. + +# Installing with regular manifests + +All resources are included in a single YAML manifest file: + +1) Install the CustomResourceDefinitions and netris-operator itself: + +``` +kubectl apply -f https://github.com/netrisai/netris-operator/releases/download/v0.3.1/netris-operator.yaml +``` + + +2) Create credentials secret for netris-operator: + +``` +kubectl -n netris-operator create secret generic netris-creds \ + --from-literal=host="http://example.com" \ + --from-literal=login="login" --from-literal=password="pass" +``` + +# Installing with Helm + +As an alternative to the YAML manifests referenced above, we also provide an official Helm chart for installing netris-operator. +## Prerequisites + +- Helm v3 only + +## Steps + +In order to install the Helm chart, you must follow these steps: + +Create the namespace for netris-operator: + +``` +kubectl create namespace netris-operator +``` + +Add the Netris Helm repository: + +``` +helm repo add netrisai https://netrisai.github.io/charts +``` + +Update your local Helm chart repository cache: + +``` +helm repo update +``` + +### Option 1: Creds from secret + +1) Create credentials secret for netris-operator: + +``` +kubectl -n netris-operator create secret generic netris-creds \ + --from-literal=host="http://example.com" \ + --from-literal=login="login" --from-literal=password="pass" +``` + +2) Install helm chart + +``` +helm install netris-operator netrisai/netris-operator \ +--namespace netris-operator +``` + +### Option 2: Creds from helm values + + 1) Install helm chart with netris controller creds + +``` +helm install netris-operator netrisai/netris-operator \ +--namespace netris-operator \ +--set controller.host="http://example.com" \ +--set controller.login="login" \ +--set controller.password="pass" +``` diff --git a/deploy/charts/netris-operator/.helmignore b/deploy/charts/netris-operator/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/deploy/charts/netris-operator/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/deploy/charts/netris-operator/Chart.yaml b/deploy/charts/netris-operator/Chart.yaml new file mode 100644 index 0000000..267c9ee --- /dev/null +++ b/deploy/charts/netris-operator/Chart.yaml @@ -0,0 +1,32 @@ +apiVersion: v2 +name: netris-operator +description: A Helm chart for netris-operator + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.2.3 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +appVersion: v0.3.1 +home: https://github.com/netrisai/netris-operator +icon: https://www.netris.ai/wp-content/uploads/2020/05/logo-600.png # [todo] Change url to permalink +keywords: + - netris-operator + - netris + - netops +sources: + - https://github.com/netrisai/netris-operator + diff --git a/deploy/charts/netris-operator/crds/k8s.netris.ai_vnetmeta.yaml b/deploy/charts/netris-operator/crds/k8s.netris.ai_vnetmeta.yaml new file mode 100644 index 0000000..df647e9 --- /dev/null +++ b/deploy/charts/netris-operator/crds/k8s.netris.ai_vnetmeta.yaml @@ -0,0 +1,162 @@ + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.3.0 + creationTimestamp: null + name: vnetmeta.k8s.netris.ai +spec: + group: k8s.netris.ai + names: + kind: VNetMeta + listKind: VNetMetaList + plural: vnetmeta + singular: vnetmeta + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + description: VNetMeta is the Schema for the vnetmeta API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: VNetMetaSpec defines the desired state of VNetMeta + properties: + gateways: + items: + description: VNetMetaGateway . + properties: + gateway: + type: string + gwLength: + type: integer + id: + type: integer + nos: + type: string + vaVlanId: + type: integer + version: + type: string + required: + - gateway + - gwLength + type: object + type: array + id: + type: integer + members: + items: + description: VNetMetaMember . + properties: + childPort: + type: integer + lacp: + type: string + member_state: + type: string + parentPort: + type: integer + port_id: + type: integer + port_name: + type: string + portIsUntagged: + type: boolean + tenant_id: + type: integer + vlan_id: + type: integer + required: + - childPort + - lacp + - member_state + - parentPort + - portIsUntagged + - port_id + - port_name + - tenant_id + - vlan_id + type: object + type: array + name: + type: string + owner: + type: string + ownerid: + type: integer + provisioning: + type: integer + sites: + items: + description: VNetMetaSite . + properties: + id: + type: integer + name: + type: string + type: object + type: array + state: + type: string + tenants: + items: + type: integer + type: array + vaMode: + type: boolean + vaNativeVlan: + type: integer + vaVlans: + type: string + vnetGeneration: + format: int64 + type: integer + vnetName: + type: string + required: + - gateways + - id + - members + - name + - owner + - ownerid + - provisioning + - sites + - state + - tenants + - vaMode + - vaNativeVlan + - vaVlans + - vnetGeneration + - vnetName + type: object + status: + description: VNetMetaStatus defines the observed state of VNetMeta + type: object + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/deploy/charts/netris-operator/crds/k8s.netris.ai_vnets.yaml b/deploy/charts/netris-operator/crds/k8s.netris.ai_vnets.yaml new file mode 100644 index 0000000..8b5e939 --- /dev/null +++ b/deploy/charts/netris-operator/crds/k8s.netris.ai_vnets.yaml @@ -0,0 +1,99 @@ + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.3.0 + creationTimestamp: null + name: vnets.k8s.netris.ai +spec: + group: k8s.netris.ai + names: + kind: VNet + listKind: VNetList + plural: vnets + singular: vnet + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + description: VNet is the Schema for the vnets API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: VNetSpec . + properties: + guestTenants: + items: + type: integer + type: array + ownerTenant: + type: string + sites: + items: + description: VNetSite . + properties: + gateways: + items: + description: VNetGateway . + properties: + gateway4: + type: string + gateway6: + type: string + type: object + type: array + name: + type: string + switchPorts: + items: + description: VNetSwitchPort . + properties: + name: + type: string + vlanId: + type: integer + required: + - name + type: object + type: array + required: + - gateways + - name + - switchPorts + type: object + type: array + state: + type: string + required: + - guestTenants + - ownerTenant + - sites + type: object + required: + - spec + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/deploy/charts/netris-operator/templates/NOTES.txt b/deploy/charts/netris-operator/templates/NOTES.txt new file mode 100644 index 0000000..9741adb --- /dev/null +++ b/deploy/charts/netris-operator/templates/NOTES.txt @@ -0,0 +1,6 @@ +Netris-Operator has been deployed successfully! + +More information on the netris resources and how to configure them +can be found in our documentation: + +https://github.com/netrisai/netris-operator/tree/master/samples diff --git a/deploy/charts/netris-operator/templates/_helpers.tpl b/deploy/charts/netris-operator/templates/_helpers.tpl new file mode 100644 index 0000000..8bc0c93 --- /dev/null +++ b/deploy/charts/netris-operator/templates/_helpers.tpl @@ -0,0 +1,118 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "netris-operator.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "netris-operator.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "netris-operator.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "netris-operator.labels" -}} +helm.sh/chart: {{ include "netris-operator.chart" . }} +{{ include "netris-operator.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "netris-operator.selectorLabels" -}} +app.kubernetes.io/name: {{ include "netris-operator.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "netris-operator.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "netris-operator.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Allow the release namespace to be overridden for multi-namespace deployments in combined charts. +*/}} +{{- define "netris-operator.namespace" -}} +{{- if .Values.namespaceOverride -}} +{{- .Values.namespaceOverride -}} +{{- else -}} +{{- .Release.Namespace -}} +{{- end -}} +{{- end -}} + +{{/* +Create netris-opeator controller envs +*/}} +{{- define "netris-operator.controller.envs" -}} +- name: CONTROLLER_HOST +{{- if .Values.controller.host }} + value: {{ .Values.controller.host | quote }} +{{- else }} + valueFrom: + secretKeyRef: + name: {{ .Values.controllerCreds.host.secretName }} + key: {{ .Values.controllerCreds.host.key }} +{{- end }} +- name: CONTROLLER_LOGIN +{{- if .Values.controller.login }} + value: {{ .Values.controller.login | quote }} +{{- else }} + valueFrom: + secretKeyRef: + name: {{ .Values.controllerCreds.login.secretName }} + key: {{ .Values.controllerCreds.login.key }} +{{- end }} +- name: CONTROLLER_PASSWORD +{{- if .Values.controller.password }} + value: {{ .Values.controller.password | quote }} +{{- else }} + valueFrom: + secretKeyRef: + name: {{ .Values.controllerCreds.password.secretName }} + key: {{ .Values.controllerCreds.password.key }} +{{- end }} +{{- if or (eq (lower (toString .Values.controller.insecure )) "true") (eq (lower (toString .Values.controller.insecure )) "false") }} +- name: CONTROLLER_INSECURE + value: {{ .Values.controller.insecure | quote }} +{{- end }} +- name: NOPERATOR_DEV_MODE +{{- if eq (lower (toString .Values.logLevel )) "debug" }} + value: "true" +{{- else }} + value: "false" +{{- end }} +- name: NOPERATOR_REQUEUE_OPERATOR + value: {{ .Values.requeueInterval | default 15 | quote }} +{{- end -}} diff --git a/deploy/charts/netris-operator/templates/deployment.yaml b/deploy/charts/netris-operator/templates/deployment.yaml new file mode 100644 index 0000000..83f7fe3 --- /dev/null +++ b/deploy/charts/netris-operator/templates/deployment.yaml @@ -0,0 +1,65 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "netris-operator.fullname" . }} + labels: + {{- include "netris-operator.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "netris-operator.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "netris-operator.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "netris-operator.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: 10 + containers: + - name: {{ .Chart.Name }}-kube-rbac-proxy + args: + - --secure-listen-address=0.0.0.0:{{ .Values.service.port }} + - --upstream=http://127.0.0.1:8080/ + - --logtostderr=true + - --v=10 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 + ports: + - containerPort: {{ .Values.service.port }} + name: https + - name: {{ .Chart.Name }}-manager + command: + - /manager + args: + - --metrics-addr=127.0.0.1:8080 + - --enable-leader-election + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + env: + {{- include "netris-operator.controller.envs" . | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/deploy/charts/netris-operator/templates/rbac.yaml b/deploy/charts/netris-operator/templates/rbac.yaml new file mode 100644 index 0000000..e6aebd3 --- /dev/null +++ b/deploy/charts/netris-operator/templates/rbac.yaml @@ -0,0 +1,151 @@ +{{- if .Values.rbac.create -}} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: '{{ include "netris-operator.fullname" . }}-manager-role' +rules: + - apiGroups: + - k8s.netris.ai + resources: + - vnetmeta + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - k8s.netris.ai + resources: + - vnetmeta/status + verbs: + - get + - patch + - update + - apiGroups: + - k8s.netris.ai + resources: + - vnets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - k8s.netris.ai + resources: + - vnets/status + verbs: + - get + - patch + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: '{{ include "netris-operator.fullname" . }}-manager-rolebinding' +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: '{{ include "netris-operator.fullname" . }}-manager-role' +subjects: + - kind: ServiceAccount + name: '{{ include "netris-operator.serviceAccountName" . }}' + namespace: '{{ include "netris-operator.namespace" . }}' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: '{{ include "netris-operator.fullname" . }}-leader-election-role' + namespace: '{{ include "netris-operator.namespace" . }}' +rules: + - apiGroups: + - '' + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - '' + resources: + - configmaps/status + verbs: + - get + - update + - patch + - apiGroups: + - '' + resources: + - events + verbs: + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: '{{ include "netris-operator.fullname" . }}-leader-election-rolebinding' + namespace: '{{ include "netris-operator.namespace" . }}' +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: '{{ include "netris-operator.fullname" . }}-leader-election-role' +subjects: + - kind: ServiceAccount + name: '{{ include "netris-operator.serviceAccountName" . }}' + namespace: '{{ include "netris-operator.namespace" . }}' +--- +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: '{{ include "netris-operator.fullname" . }}-proxy-role' +rules: + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create + - apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: '{{ include "netris-operator.fullname" . }}-proxy-rolebinding' +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: '{{ include "netris-operator.fullname" . }}-proxy-role' +subjects: + - kind: ServiceAccount + name: '{{ include "netris-operator.serviceAccountName" . }}' + namespace: '{{ include "netris-operator.namespace" . }}' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: '{{ include "netris-operator.fullname" . }}-metrics-reader' +rules: + - nonResourceURLs: + - /metrics + verbs: + - get +{{- end }} diff --git a/deploy/charts/netris-operator/templates/service.yaml b/deploy/charts/netris-operator/templates/service.yaml new file mode 100644 index 0000000..76ee8ae --- /dev/null +++ b/deploy/charts/netris-operator/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "netris-operator.fullname" . }} + labels: + {{- include "netris-operator.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: https + protocol: TCP + name: https + selector: + {{- include "netris-operator.selectorLabels" . | nindent 4 }} diff --git a/deploy/charts/netris-operator/templates/serviceaccount.yaml b/deploy/charts/netris-operator/templates/serviceaccount.yaml new file mode 100644 index 0000000..f5e4976 --- /dev/null +++ b/deploy/charts/netris-operator/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "netris-operator.serviceAccountName" . }} + labels: + {{- include "netris-operator.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/deploy/charts/netris-operator/values.yaml b/deploy/charts/netris-operator/values.yaml new file mode 100644 index 0000000..1324021 --- /dev/null +++ b/deploy/charts/netris-operator/values.yaml @@ -0,0 +1,86 @@ +# Default values for netris-operator. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: netrisai/netris-operator + pullPolicy: Always + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +controller: {} + # host: http://example.com + # login: login + # password: pass + # insecure: false + +controllerCreds: + host: + secretName: netris-creds + key: host + login: + secretName: netris-creds + key: login + password: + secretName: netris-creds + key: password + +# Set the log level of netris-operator. Possible values 'info' or 'debug' +logLevel: info + +# Set the requeue interval in seconds for the netris-operator. +requeueInterval: 15 + +rbac: + # Specifies whether RBAC resources should be created + create: true + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 8443 + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/go.mod b/go.mod index 30b3d98..8c777ee 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,17 @@ -module github.com/netrisx/netris-operator +module github.com/netrisai/netris-operator -go 1.13 +go 1.14 require ( github.com/go-logr/logr v0.1.0 github.com/kelseyhightower/envconfig v1.4.0 + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/netrisai/netrisapi v0.0.0-20210210174159-ffdd848b88a2 github.com/onsi/ginkgo v1.12.1 github.com/onsi/gomega v1.10.1 + github.com/r3labs/diff/v2 v2.9.1 + go.uber.org/zap v1.10.0 + golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect gopkg.in/yaml.v2 v2.3.0 k8s.io/apimachinery v0.18.6 k8s.io/client-go v0.18.6 diff --git a/go.sum b/go.sum index 98caccc..aed6d20 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,7 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -43,6 +44,7 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -196,10 +198,14 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -213,7 +219,12 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks= +github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -224,6 +235,32 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/netrisai/netrisapi v0.0.0-20201229123610-e57db0e1168e h1:AlqRzsUZpW5moPJF46lfu9IJ2mVmFJYp9rtjuSxie+I= +github.com/netrisai/netrisapi v0.0.0-20201229123610-e57db0e1168e/go.mod h1:IqkB+A4amKw4jG8dgAWjcWE0W7rvIbSl4K+E3v332ZM= +github.com/netrisai/netrisapi v0.0.0-20210114184219-92fb20a45251 h1:AvjbU7LuGlAmMAlzBzDZio0T1HBBzbkcPetDfff54mM= +github.com/netrisai/netrisapi v0.0.0-20210114184219-92fb20a45251/go.mod h1:IqkB+A4amKw4jG8dgAWjcWE0W7rvIbSl4K+E3v332ZM= +github.com/netrisai/netrisapi v0.0.0-20210127194657-9c00472807ed h1:90jGnYEEfiaicuEOP7BNk7X/crq3Yf1CyeZc6WaoDbo= +github.com/netrisai/netrisapi v0.0.0-20210127194657-9c00472807ed/go.mod h1:IqkB+A4amKw4jG8dgAWjcWE0W7rvIbSl4K+E3v332ZM= +github.com/netrisai/netrisapi v0.0.0-20210127202113-c4b60bf58ede h1:2JUz6P67+yrzTaoR6GAi43nexnzd4YZyl6uSKUkvqi4= +github.com/netrisai/netrisapi v0.0.0-20210127202113-c4b60bf58ede/go.mod h1:IqkB+A4amKw4jG8dgAWjcWE0W7rvIbSl4K+E3v332ZM= +github.com/netrisai/netrisapi v0.0.0-20210127204449-549080364a07 h1:FspTZqG3QfAjQtbI8XX10o3Um4aaOChtppnG3dVlcLM= +github.com/netrisai/netrisapi v0.0.0-20210127204449-549080364a07/go.mod h1:IqkB+A4amKw4jG8dgAWjcWE0W7rvIbSl4K+E3v332ZM= +github.com/netrisai/netrisapi v0.0.0-20210128092937-db2dc25f66ed h1:IZN5k0o/LhrYJ0GbaQZFuVRpXUPOfb4xvc8jf0szwZA= +github.com/netrisai/netrisapi v0.0.0-20210128092937-db2dc25f66ed/go.mod h1:IqkB+A4amKw4jG8dgAWjcWE0W7rvIbSl4K+E3v332ZM= +github.com/netrisai/netrisapi v0.0.0-20210128093722-6f2c18c0a59b h1:w+1EfqEskYTNES5DWsDblVx5fd2u2oEwde3hpc9HVIQ= +github.com/netrisai/netrisapi v0.0.0-20210128093722-6f2c18c0a59b/go.mod h1:IqkB+A4amKw4jG8dgAWjcWE0W7rvIbSl4K+E3v332ZM= +github.com/netrisai/netrisapi v0.0.0-20210128093850-452bac9d19bb h1:D3b25gG7masM51WppmddGvqSP6yzta2EuMOolugh9Cc= +github.com/netrisai/netrisapi v0.0.0-20210128093850-452bac9d19bb/go.mod h1:IqkB+A4amKw4jG8dgAWjcWE0W7rvIbSl4K+E3v332ZM= +github.com/netrisai/netrisapi v0.0.0-20210128094240-ea7fbb8c8588 h1:PwgQtY+dMdid4EVw+2EKj5Lpf14TJu0uTM0JO/496fY= +github.com/netrisai/netrisapi v0.0.0-20210128094240-ea7fbb8c8588/go.mod h1:IqkB+A4amKw4jG8dgAWjcWE0W7rvIbSl4K+E3v332ZM= +github.com/netrisai/netrisapi v0.0.0-20210128094713-d0e2960a1ace h1:dk14lhkDahx7vAvdoTjcdvwrPZA7dlG6qRYeNs9oo9c= +github.com/netrisai/netrisapi v0.0.0-20210128094713-d0e2960a1ace/go.mod h1:IqkB+A4amKw4jG8dgAWjcWE0W7rvIbSl4K+E3v332ZM= +github.com/netrisai/netrisapi v0.0.0-20210128145759-088151c01d6b h1:EvaOftLnbdz7HP5aLg+BoAO6e0KEbb/8jYbxaxJKMKg= +github.com/netrisai/netrisapi v0.0.0-20210128145759-088151c01d6b/go.mod h1:IqkB+A4amKw4jG8dgAWjcWE0W7rvIbSl4K+E3v332ZM= +github.com/netrisai/netrisapi v0.0.0-20210201135514-d3bd77ef0747 h1:Ok2H0sLiVFLw88bBoHeZGR4oWnnUeIpGd8pbBv4gjRI= +github.com/netrisai/netrisapi v0.0.0-20210201135514-d3bd77ef0747/go.mod h1:IqkB+A4amKw4jG8dgAWjcWE0W7rvIbSl4K+E3v332ZM= +github.com/netrisai/netrisapi v0.0.0-20210210174159-ffdd848b88a2 h1:QlwYKDpr3YMmmjo7FdzckgxOG8ioj0YdJ8q60H+5taM= +github.com/netrisai/netrisapi v0.0.0-20210210174159-ffdd848b88a2/go.mod h1:IqkB+A4amKw4jG8dgAWjcWE0W7rvIbSl4K+E3v332ZM= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -260,11 +297,18 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/r3labs/diff v1.1.0 h1:V53xhrbTHrWFWq3gI4b94AjgEJOerO1+1l0xyHOBi8M= +github.com/r3labs/diff v1.1.0/go.mod h1:7WjXasNzi0vJetRcB/RqNl5dlIsmXcTTLmF5IoH6Xig= +github.com/r3labs/diff/v2 v2.9.1 h1:PE0m2ueSI0a0ymwTu1Js0iS8PO9yLvQqw/44s3VJlgg= +github.com/r3labs/diff/v2 v2.9.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -285,11 +329,16 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -331,6 +380,7 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -365,10 +415,18 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo= +golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY= +golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -402,6 +460,8 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -422,6 +482,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -438,6 +500,9 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 606681a..c0996d0 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -12,4 +12,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -*/ \ No newline at end of file +*/ diff --git a/main.go b/main.go index 506f503..4ca4ee4 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ import ( "flag" "os" + "go.uber.org/zap/zapcore" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -27,8 +28,9 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/log/zap" - k8sv1alpha1 "github.com/netrisx/netris-operator/api/v1alpha1" - "github.com/netrisx/netris-operator/controllers" + k8sv1alpha1 "github.com/netrisai/netris-operator/api/v1alpha1" + "github.com/netrisai/netris-operator/configloader" + "github.com/netrisai/netris-operator/controllers" // +kubebuilder:scaffold:imports ) @@ -53,7 +55,11 @@ func main() { "Enabling this will ensure there is only one active controller manager.") flag.Parse() - ctrl.SetLogger(zap.New(zap.UseDevMode(true))) + if configloader.Root.LogDevMode { + ctrl.SetLogger(zap.New(zap.Level(zapcore.DebugLevel), zap.UseDevMode(false))) + } else { + ctrl.SetLogger(zap.New(zap.UseDevMode(false), zap.StacktraceLevel(zapcore.DPanicLevel))) + } mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Namespace: "", @@ -70,12 +76,21 @@ func main() { if err = (&controllers.VNetReconciler{ Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("VNet"), + Log: ctrl.Log.WithName("VNet"), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "VNet") os.Exit(1) } + if err = (&controllers.VNetMetaReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("VNetMeta"), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "VNetMeta") + os.Exit(1) + } + // +kubebuilder:scaffold:builder setupLog.Info("starting manager") diff --git a/samples/vnet/README.md b/samples/vnet/README.md new file mode 100644 index 0000000..7169944 --- /dev/null +++ b/samples/vnet/README.md @@ -0,0 +1,130 @@ +# Note +Register the Netris [CRDs](https://github.com/netrisai/netris-operator/tree/dev/deploy) in the Kubernetes cluster before creating objects. + + +# Kind: VNet + +### VNet Attributes + +``` +apiVersion: k8s.netris.ai/v1alpha1 +kind: VNet +metadata: + name: myVnet +spec: + ownerTenant: admin # [1] + guestTenants: [] # [2] + state: active # [3] optional + sites: # [4] + - name: yerevan # [5] + gateways: # [6] + - 109.23.0.6/24 + - 109.24.72.6/24 + - 2001:db8:acad::fffe/64 + switchPorts: # [7] + - name: swp4@rlab-leaf1 # [8] + vlanId: 1050 # [9] optional + - name: swp7@rlab-leaf1 + state: disable # [10] optional +``` + +Ref | Attribute | Default | Description +----| -------------------------------------- | ----------- | ---------------- +[1] | ownerTenant | "" | Users with permission to owner tenant can manage parameters of the V-Net as well as add/edit/remove ports assigned to any of tenants where user has permission. +[2] | guestTenants | [] | List of tenants allowed to add/edit/remove ports to the V-Net but not allowed to manage other parameters of the circuit. +[3] | state | active | V-Net state. Allowed values: `active` or `disable`. +[4] | sites | [] | List of sites. Ports from these sites will be allowed to participate to the V-Net. Multi-site circuits are possible for sites connected through a backbone port. +[5] | sites[n].name | "" | Site's name. +[6] | sites[n].gateways | [] | List of gateways. Selected address will be serving as anycast default gateway for selected subnet. In case of multi-site V-Net, multi-site subnet should be configured under Subnets section. +[7] | sites[n].switchPorts | [] | List of switchPorts. +[8] | sites[n].switchPorts[n].name | "" | SwitchPorts name. +[9] | sites[n].switchPorts[n].vlanId | nil | VLAN tag for current port. If `vlanid` is not set - means port untagged +[10] | sites[n].switchPorts[n].state | active | Port state. Allowed values: `active` or `disable`. + + +### E-BGP Attributes + +``` +apiVersion: k8s.netris.ai/v1alpha1 +kind: EBGP +metadata: + name: myEbgp +spec: + site: Default # [1] + softgate: softgate1 # [2] Ignoring when terminateOnSwitch == true + neighborAs: 23456 # [3] + transport: # [4] + type: vnet # [5] optional + name: myvnet # [6] + vlanId: 4 # [7] optional. Ignoring when transport.type == vnet + localIP: 172.16.0.1/30 # [8] + remoteIP: 172.16.0.2/30 # [9] + description: someDesc # [10] optional + state: enabled # [11] optional + terminateOnSwitch: false # [12] optional + multihop: # [13] optional + neighborAddress: 8.8.8.8 # [14] optional + updateSource: 10.254.97.33 # [15] optional + hops: 5 # [16] optional + bgpPassword: somestrongpass # [17] optional + allowAsIn: 5 # [18] optional + defaultOriginate: false # [19] optional + prefixInboundMax: 10000 # [20] optional + inboundRouteMap: my-in-rm # [21] optional + outboundRouteMap: my-out-rm # [22] optional + localPreference: 100 # [23] optional. Ignoring when *RouteMap defined + weight: 0 # [24] optional. Ignoring when *RouteMap defined + prependInbound: 2 # [25] optional. Ignoring when *RouteMap defined + prependOutbound: 1 # [26] optional. Ignoring when *RouteMap defined + prefixListInbound: # [27] optional. Ignoring when *RouteMap defined + - deny 127.0.0.0/8 le 32 + - permit 0.0.0.0/0 le 24 + prefixListOutbound: # [28] optional. Ignoring when *RouteMap defined + - permit 192.168.0.0/23 + sendBGPCommunity: # [29] optional. Ignoring when *RouteMap defined + - 65501:777 + - 65501:779 +``` + +Ref | Attribute | Default | Description +----| -------------------------------------- | ----------- | ---------------- +[1] | sites | "" | BGP session site +[2] | softgate | "" | Defines softgate for Layer-3 and BGP session termination. Ignoring when terminateOnSwitch == true +[3] | neighborAs | 0 | BGP neighbor AS number +[4] | transport | {} | Physical port where BGP neighbor cable is connected or an existing V-Net service +[5] | transport.type | port | Possible values: port/vnet +[6] | transport.name | "" | Possible values: portName@switchName/vnetName +[7] | transport.vlanId | nil | Ignoring when transport.type == vnet +[8] | localIP | "" | BGP session local ip +[9] | remoteIP | "" | BGP session remote ip +[10]| description | "" | BGP session description +[11]| state | enabled | Possible values: enabled/disabled; enabled - initiating and waiting for BGP connections, disabled - disable Layer-2 tunnel and Layer-3 address. +[12]| terminateOnSwitch | false | Terminate Layer-3 and BGP session directly on the physical spine or border switch. Available for BGP sessions limited to 1000 prefixes or less. +[13]| multihop | {} | Multihop BGP session configurations +[14]| multihop.neighborAddress | "" | - +[15]| multihop.updateSource | "" | - +[16]| multihop.hops | 0 | - +[17]| bgpPassword | "" | BGP session password +[18]| allowAsIn | 0 | [todo] --- +[19]| defaultOriginate | false | Originate default route to current neighbor. +[20]| prefixInboundMax | 0 | BGP session will be terminated if neighbor advertises more prefixes than defined. +[21]| inboundRouteMap | "" | Reference to route-map resource. +[22]| outboundRouteMap | "" | Reference to route-map resource. +[23]| localPreference | 100 | - +[24]| weight | 0 | - +[25]| prependInbound | 0 | Number of times to prepend self AS to as-path of received prefix advertisements. +[26]| prependOutbound | 0 | Number of times to prepend self AS to as-path being advertised to neighbors. +[27]| prefixListInbound | [] | - +[28]| prefixListOutbound | [] | Define outbound prefix list, if not defined autogenerated prefix list will apply which will permit defined allocations and assignments, and will deny all private addresses. +[29]| sendBGPCommunity | [] | Send BGP Community Unconditionally advertise defined list of BGP communities towards BGP neighbor. Format: AA:NN Community number in AA:NN format (where AA and NN are (0-65535)) or local-AS|no-advertise|no-export|internet or additive + + + +# Annotations + +> Annotation keys and values can only be strings. Other types, such as boolean or numeric values must be quoted, i.e. "true", "false", "100". + + +Name | Type | Description +-------------------------------------- | ---------------- | ---------------- +`resource.k8s.netris.ai/import` |"true" or "false" | Allow importing existing resources. diff --git a/samples/vnet/ebgp.yaml b/samples/vnet/ebgp.yaml new file mode 100644 index 0000000..a78deea --- /dev/null +++ b/samples/vnet/ebgp.yaml @@ -0,0 +1,39 @@ +apiVersion: k8s.netris.ai/v1alpha1 +kind: EBGP +metadata: + name: myEbgp +spec: + site: Default + softgate: softgate1 + neighborAs: 23456 + transport: + type: vnet + name: myvnet + vlanId: 4 + localIP: 172.16.0.1/30 + remoteIP: 172.16.0.2/30 + description: someDesc + state: enabled + terminateOnSwitch: false + multihop: + neighborAddress: 8.8.8.8 + updateSource: 10.254.97.33 + hops: 5 + bgpPassword: somestrongpass + allowAsIn: 5 + defaultOriginate: false + prefixInboundMax: 10000 + inboundRouteMap: my-in-rm + outboundRouteMap: my-out-rm + localPreference: 100 + weight: 0 + prependInbound: 2 + prependOutbound: 1 + prefixListInbound: + - deny 127.0.0.0/8 le 32 + - permit 0.0.0.0/0 le 24 + prefixListOutbound: + - permit 192.168.0.0/23 + sendBGPCommunity: + - 65501:777 + - 65501:779 diff --git a/samples/vnet/vnet.yaml b/samples/vnet/vnet.yaml new file mode 100644 index 0000000..c573615 --- /dev/null +++ b/samples/vnet/vnet.yaml @@ -0,0 +1,16 @@ +apiVersion: k8s.netris.ai/v1alpha1 +kind: VNet +metadata: + name: myvnet +spec: + ownerTenant: Admin + guestTenants: [] + sites: + - name: Yerevan + gateways: + - 109.23.0.6/24 + - 2001:db8:acad::fffe/64 + switchPorts: + - name: swp5@rlab-spine1 + vlanId: 1050 + - name: swp7@rlab-leaf1 diff --git a/scripts/rbac-helm-template.py b/scripts/rbac-helm-template.py new file mode 100755 index 0000000..aa2ec77 --- /dev/null +++ b/scripts/rbac-helm-template.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 + +import argparse +import yaml + +fullname = '{{ include "netris-operator.fullname" . }}' +namespace = '{{ include "netris-operator.namespace" . }}' +service_account_name = '{{ include "netris-operator.serviceAccountName" . }}' + + +def main(): + args = args_parser() + with open(args.y, 'r') as f: + yaml_items = yaml.safe_load(f) + kind = '' + try: + kind = yaml_items['kind'] + except KeyError: # not k8s resource + exit(1) + if 'creationTimestamp' in yaml_items['metadata']: + del yaml_items['metadata']['creationTimestamp'] + if kind == 'Role': + role(yaml_items) + elif kind == 'ClusterRole': + cluster_role(yaml_items) + elif kind == 'RoleBinding': + role_binding(yaml_items) + elif kind == 'ClusterRoleBinding': + cluster_role_binding(yaml_items) + else: # unexpected resource kind + exit(1) + + +def args_parser(): + parser = argparse.ArgumentParser(description='rbac to helm template script') + parser.add_argument('y', metavar='$1', type=str, help='yaml file path') + args = parser.parse_args() + return args + + +def role(_yaml): + name = _yaml['metadata']['name'] + _yaml['metadata']['name'] = '{}-{}'.format(fullname, name) + _yaml['metadata']['namespace'] = namespace + print(_yaml) + + +def role_binding(_yaml): + name = _yaml['metadata']['name'] + role_ref_name = _yaml['roleRef']['name'] + _yaml['metadata']['name'] = '{}-{}'.format(fullname, name) + _yaml['metadata']['namespace'] = namespace + _yaml['roleRef']['name'] = '{}-{}'.format(fullname, role_ref_name) + for key, value in enumerate(_yaml['subjects']): + if value['kind'] == 'ServiceAccount': + _yaml['subjects'][key]['name'] = service_account_name + _yaml['subjects'][key]['namespace'] = namespace + print(_yaml) + + +def cluster_role(_yaml): + name = _yaml['metadata']['name'] + _yaml['metadata']['name'] = '{}-{}'.format(fullname, name) + print(_yaml) + + +def cluster_role_binding(_yaml): + name = _yaml['metadata']['name'] + role_ref_name = _yaml['roleRef']['name'] + _yaml['metadata']['name'] = '{}-{}'.format(fullname, name) + _yaml['roleRef']['name'] = '{}-{}'.format(fullname, role_ref_name) + for key, value in enumerate(_yaml['subjects']): + if value['kind'] == 'ServiceAccount': + _yaml['subjects'][key]['name'] = service_account_name + _yaml['subjects'][key]['namespace'] = namespace + print(_yaml) + + +if __name__ == '__main__': + main()