Add Sdewan Mwan3Conf CRD and controller 43/2243/5
authorchengli3 <cheng1.li@intel.com>
Wed, 19 Feb 2020 14:30:06 +0000 (22:30 +0800)
committerchengli3 <cheng1.li@intel.com>
Mon, 24 Feb 2020 05:46:39 +0000 (13:46 +0800)
The sdewan operator is developed under kubebuilder framework

We define two CRDs in this patch: Sdewan and Mwan3Conf

Sdewan defines the CNF base info, which node we should deploy the CNF
on, which network should the CNF use with multus CNI, etc.

The Mwan3Conf defines the mwan3 rules. In the next step, we are going to
develop the firewall and the ipsec functions. Mwan3Conf is validated by
k8s api admission webhook.

For each created Sdewan instance, the controller creates a pod, a
configmap and a service for the instance. The pod runs openswrt which
provides network services, i.e. sdwan, firewall, ipsec etc.

The configmap stores the network interface information and the
entrypoint.sh.
The network interface information has the following format:
```
[
  {
    "name": "ovn-priv-net",
    "isProvider": false,
    "interface": "net0",
    "defaultGateway": false
  }
]
```

The service created by the controller is used for openwrt api access.
We call this svc to apply rules, get openwrt info, restart openwrt
service.

After the openwrt pod ready, the Sdewan controller apply the configured
mwan3 rules.
mwan3 rule details are configured in Mwan3Conf CR, which is referenced
by Sdewan.Spec.Mwan3Conf
Every time the Mwan3Conf instance changes, the controller re-apply the
new rules by calling opwnrt
api. We can also change the rule refernce at the runtime.

Signed-off-by: chengli3 <cheng1.li@intel.com>
Change-Id: Ic6fa4e8c61da5a560d69f749cd40d8f3b9320e81

69 files changed:
.gitignore [new file with mode: 0644]
Dockerfile [new file with mode: 0644]
Makefile [new file with mode: 0644]
PROJECT [new file with mode: 0644]
api/v1alpha1/groupversion_info.go [new file with mode: 0644]
api/v1alpha1/mwan3conf_types.go [new file with mode: 0644]
api/v1alpha1/mwan3conf_webhook.go [new file with mode: 0644]
api/v1alpha1/sdewan_types.go [new file with mode: 0644]
api/v1alpha1/zz_generated.deepcopy.go [new file with mode: 0644]
config/certmanager/certificate.yaml [new file with mode: 0644]
config/certmanager/kustomization.yaml [new file with mode: 0644]
config/certmanager/kustomizeconfig.yaml [new file with mode: 0644]
config/crd/bases/batch.sdewan.akraino.org_mwan3confs.yaml [new file with mode: 0644]
config/crd/bases/batch.sdewan.akraino.org_sdewans.yaml [new file with mode: 0644]
config/crd/kustomization.yaml [new file with mode: 0644]
config/crd/kustomizeconfig.yaml [new file with mode: 0644]
config/crd/patches/cainjection_in_mwan3confs.yaml [new file with mode: 0644]
config/crd/patches/cainjection_in_sdewans.yaml [new file with mode: 0644]
config/crd/patches/webhook_in_mwan3confs.yaml [new file with mode: 0644]
config/crd/patches/webhook_in_sdewans.yaml [new file with mode: 0644]
config/default/kustomization.yaml [new file with mode: 0644]
config/default/manager_auth_proxy_patch.yaml [new file with mode: 0644]
config/default/manager_image_patch.yaml [new file with mode: 0644]
config/default/manager_webhook_patch.yaml [new file with mode: 0644]
config/default/webhookcainjection_patch.yaml [new file with mode: 0644]
config/manager/kustomization.yaml [new file with mode: 0644]
config/manager/manager.yaml [new file with mode: 0644]
config/prometheus/kustomization.yaml [new file with mode: 0644]
config/prometheus/monitor.yaml [new file with mode: 0644]
config/rbac/auth_proxy_role.yaml [new file with mode: 0644]
config/rbac/auth_proxy_role_binding.yaml [new file with mode: 0644]
config/rbac/auth_proxy_service.yaml [new file with mode: 0644]
config/rbac/kustomization.yaml [new file with mode: 0644]
config/rbac/leader_election_role.yaml [new file with mode: 0644]
config/rbac/leader_election_role_binding.yaml [new file with mode: 0644]
config/rbac/mwan3conf_editor_role.yaml [new file with mode: 0644]
config/rbac/mwan3conf_viewer_role.yaml [new file with mode: 0644]
config/rbac/role.yaml [new file with mode: 0644]
config/rbac/role_binding.yaml [new file with mode: 0644]
config/rbac/sdewan_editor_role.yaml [new file with mode: 0644]
config/rbac/sdewan_viewer_role.yaml [new file with mode: 0644]
config/samples/batch_v1alpha1_mwan3conf.yaml [new file with mode: 0644]
config/samples/batch_v1alpha1_sdewan.yaml [new file with mode: 0644]
config/webhook/kustomization.yaml [new file with mode: 0644]
config/webhook/kustomizeconfig.yaml [new file with mode: 0644]
config/webhook/manifests.yaml [new file with mode: 0644]
config/webhook/service.yaml [new file with mode: 0644]
controllers/mwan3conf_controller.go [new file with mode: 0644]
controllers/sdewan_controller.go [new file with mode: 0644]
controllers/suite_test.go [new file with mode: 0644]
deploy/crds/sdewan.akraino.org_mwan3rules_crd.yaml [new file with mode: 0644]
deploy/crds/sdewan.akraino.org_sdewans_crd.yaml [new file with mode: 0644]
deploy/crds/sdewan.akraino.org_v1alpha1_mwan3rule_cr.yaml [new file with mode: 0644]
deploy/crds/sdewan.akraino.org_v1alpha1_sdewan_cr.yaml [new file with mode: 0644]
deploy/operator.yaml [new file with mode: 0644]
deploy/role.yaml [new file with mode: 0644]
deploy/role_binding.yaml [new file with mode: 0644]
deploy/service_account.yaml [new file with mode: 0644]
go.mod [new file with mode: 0644]
go.sum [new file with mode: 0644]
hack/boilerplate.go.txt [new file with mode: 0644]
main.go [new file with mode: 0644]
openwrt/mwan3.go [new file with mode: 0644]
openwrt/openwrtclient.go [new file with mode: 0644]
openwrt/service.go [new file with mode: 0644]
openwrt/utils.go [new file with mode: 0644]
readme.md [new file with mode: 0644]
sdewan-deploy.yaml [new file with mode: 0644]
wrtprovider/wrtprovider.go [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..bf52783
--- /dev/null
@@ -0,0 +1,25 @@
+
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+bin
+
+# Test binary, build with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Kubernetes Generated files - skip generated files, except for vendored files
+
+!vendor/**/zz_generated.*
+
+# editor and IDE paraphernalia
+.idea
+*.swp
+*.swo
+*~
+my-sdewan-deploy.yaml
diff --git a/Dockerfile b/Dockerfile
new file mode 100644 (file)
index 0000000..018b7b6
--- /dev/null
@@ -0,0 +1,29 @@
+# Build the manager binary
+FROM golang:1.13 as builder
+
+WORKDIR /workspace
+# 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
+
+# Copy the go source
+COPY main.go main.go
+COPY api/ api/
+COPY controllers/ controllers/
+COPY openwrt/ openwrt/
+COPY wrtprovider/ wrtprovider/
+
+# Build
+RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 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
+FROM gcr.io/distroless/static:nonroot
+WORKDIR /
+COPY --from=builder /workspace/manager .
+USER nonroot:nonroot
+
+ENTRYPOINT ["/manager"]
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..33196ac
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,84 @@
+
+# Image URL to use all building/pushing image targets
+IMG ?= integratedcloudnative/sdewan-controller:dev
+# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
+CRD_OPTIONS ?= "crd:trivialVersions=true"
+
+# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
+ifeq (,$(shell go env GOBIN))
+GOBIN=$(shell go env GOPATH)/bin
+else
+GOBIN=$(shell go env GOBIN)
+endif
+
+all: manager
+
+# Run tests
+test: generate fmt vet manifests
+       go test ./... -coverprofile cover.out
+
+# Build manager binary
+manager: generate fmt vet
+       go build -o bin/manager main.go
+
+# Run against the configured Kubernetes cluster in ~/.kube/config
+run: generate fmt vet manifests
+       go run ./main.go
+
+# Install CRDs into a cluster
+install: manifests
+       kustomize build config/crd | kubectl apply -f -
+
+# Uninstall CRDs from a cluster
+uninstall: manifests
+       kustomize build config/crd | kubectl delete -f -
+
+# Deploy controller in the configured Kubernetes cluster in ~/.kube/config
+yaml-gen: manifests
+       cd config/manager && kustomize edit set image controller=${IMG}
+       kustomize build config/default > my-sdewan-deploy.yaml
+
+# Deploy controller in the configured Kubernetes cluster in ~/.kube/config
+deploy: yaml-gen
+       kubectl apply -f my-sdewan-deploy.yaml
+
+# 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
+
+# Run go fmt against code
+fmt:
+       go fmt ./...
+
+# Run go vet against code
+vet:
+       go vet ./...
+
+# Generate code
+generate: controller-gen
+       $(CONTROLLER_GEN) object:headerFile=./hack/boilerplate.go.txt paths="./..."
+
+# Build the docker image
+docker-build: test
+       docker build . -t ${IMG}
+
+# Push the docker image
+docker-push:
+       docker push ${IMG}
+
+# find or download controller-gen
+# download controller-gen if necessary
+controller-gen:
+ifeq (, $(shell which controller-gen))
+       @{ \
+       set -e ;\
+       CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
+       cd $$CONTROLLER_GEN_TMP_DIR ;\
+       go mod init tmp ;\
+       go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.5 ;\
+       rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
+       }
+CONTROLLER_GEN=$(GOBIN)/controller-gen
+else
+CONTROLLER_GEN=$(shell which controller-gen)
+endif
diff --git a/PROJECT b/PROJECT
new file mode 100644 (file)
index 0000000..6c51d5d
--- /dev/null
+++ b/PROJECT
@@ -0,0 +1,10 @@
+domain: sdewan.akraino.org
+repo: sdewan.akraino.org/sdewan
+resources:
+- group: batch
+  kind: Sdewan
+  version: v1alpha1
+- group: batch
+  kind: Mwan3Conf
+  version: v1alpha1
+version: "2"
diff --git a/api/v1alpha1/groupversion_info.go b/api/v1alpha1/groupversion_info.go
new file mode 100644 (file)
index 0000000..2d9b3fe
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+
+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 contains API Schema definitions for the batch v1alpha1 API group
+// +kubebuilder:object:generate=true
+// +groupName=batch.sdewan.akraino.org
+package v1alpha1
+
+import (
+       "k8s.io/apimachinery/pkg/runtime/schema"
+       "sigs.k8s.io/controller-runtime/pkg/scheme"
+)
+
+var (
+       // GroupVersion is group version used to register these objects
+       GroupVersion = schema.GroupVersion{Group: "batch.sdewan.akraino.org", Version: "v1alpha1"}
+
+       // SchemeBuilder is used to add go types to the GroupVersionKind scheme
+       SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
+
+       // AddToScheme adds the types in this group-version to the given scheme.
+       AddToScheme = SchemeBuilder.AddToScheme
+)
diff --git a/api/v1alpha1/mwan3conf_types.go b/api/v1alpha1/mwan3conf_types.go
new file mode 100644 (file)
index 0000000..c9cf444
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+
+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.
+
+type PolicyMember struct {
+       Network string `json:"network"`
+       Metric  int    `json:"metric"`
+       Weight  int    `json:"weight"`
+}
+
+type PolicyMembers struct {
+       Members []PolicyMember `json:"members"`
+}
+type Rule struct {
+       UsePolicy string `json:"use_policy"`
+
+       // +optional
+       DestIP string `json:"dest_ip"`
+
+       // +optional
+       DestPort string `json:"dest_port"`
+
+       // +optional
+       SrcIP string `json:"src_IP"`
+
+       // +optional
+       SrcPort string `json:"src_port"`
+
+       // +optional
+       Proto string `json:"proto"`
+
+       // +optional
+       Family string `json:"family"`
+
+       // +optional
+       Sticky string `json:"sticky"`
+
+       // +optional
+       Timeout string `json:"timeout"`
+}
+
+// Mwan3ConfSpec defines the desired state of Mwan3Conf
+type Mwan3ConfSpec struct {
+       // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
+       // Important: Run "make" to regenerate code after modifying this file
+
+       Policies map[string]PolicyMembers `json:"policy"`
+       Rules    map[string]Rule          `json:"rule"`
+}
+
+// Mwan3ConfStatus defines the observed state of Mwan3Conf
+type Mwan3ConfStatus 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
+
+// Mwan3Conf is the Schema for the mwan3confs API
+type Mwan3Conf struct {
+       metav1.TypeMeta   `json:",inline"`
+       metav1.ObjectMeta `json:"metadata,omitempty"`
+
+       Spec   Mwan3ConfSpec   `json:"spec,omitempty"`
+       Status Mwan3ConfStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// Mwan3ConfList contains a list of Mwan3Conf
+type Mwan3ConfList struct {
+       metav1.TypeMeta `json:",inline"`
+       metav1.ListMeta `json:"metadata,omitempty"`
+       Items           []Mwan3Conf `json:"items"`
+}
+
+func init() {
+       SchemeBuilder.Register(&Mwan3Conf{}, &Mwan3ConfList{})
+}
diff --git a/api/v1alpha1/mwan3conf_webhook.go b/api/v1alpha1/mwan3conf_webhook.go
new file mode 100644 (file)
index 0000000..661af76
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+
+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 (
+       "k8s.io/apimachinery/pkg/runtime"
+       ctrl "sigs.k8s.io/controller-runtime"
+       logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
+       "sigs.k8s.io/controller-runtime/pkg/webhook"
+)
+
+// log is for logging in this package.
+var mwan3conflog = logf.Log.WithName("mwan3conf-resource")
+
+func (r *Mwan3Conf) SetupWebhookWithManager(mgr ctrl.Manager) error {
+       return ctrl.NewWebhookManagedBy(mgr).
+               For(r).
+               Complete()
+}
+
+// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
+
+// +kubebuilder:webhook:path=/mutate-batch-sdewan-akraino-org-v1alpha1-mwan3conf,mutating=true,failurePolicy=fail,groups=batch.sdewan.akraino.org,resources=mwan3confs,verbs=create;update,versions=v1alpha1,name=mmwan3conf.kb.io
+
+var _ webhook.Defaulter = &Mwan3Conf{}
+
+// Default implements webhook.Defaulter so a webhook will be registered for the type
+func (r *Mwan3Conf) Default() {
+       mwan3conflog.Info("default", "name", r.Name)
+
+       // TODO(user): fill in your defaulting logic.
+}
+
+// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
+// +kubebuilder:webhook:verbs=create;update,path=/validate-batch-sdewan-akraino-org-v1alpha1-mwan3conf,mutating=false,failurePolicy=fail,groups=batch.sdewan.akraino.org,resources=mwan3confs,versions=v1alpha1,name=vmwan3conf.kb.io
+
+var _ webhook.Validator = &Mwan3Conf{}
+
+// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
+func (r *Mwan3Conf) ValidateCreate() error {
+       mwan3conflog.Info("validate create", "name", r.Name)
+
+       // TODO(user): fill in your validation logic upon object creation.
+       return r.ValidateMwan3()
+}
+
+// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
+func (r *Mwan3Conf) ValidateUpdate(old runtime.Object) error {
+       mwan3conflog.Info("validate update", "name", r.Name)
+
+       // TODO(user): fill in your validation logic upon object update.
+       return r.ValidateMwan3()
+}
+
+// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
+func (r *Mwan3Conf) ValidateDelete() error {
+       mwan3conflog.Info("validate delete", "name", r.Name)
+
+       // TODO(user): fill in your validation logic upon object deletion.
+       return nil
+}
+
+func (r *Mwan3Conf) ValidateMwan3() error {
+       policies := r.Spec.Policies
+       for _, rOptions := range r.Spec.Rules {
+               if _, ok := policies[rOptions.UsePolicy]; !ok {
+                       return &UnmatchError{"policy", rOptions.UsePolicy}
+               }
+       }
+       return nil
+}
+
+type UnmatchError struct {
+       RType string
+       Name  string
+}
+
+func (err *UnmatchError) Error() string {
+       return err.RType + " :" + err.Name + " doesnt' exist"
+}
diff --git a/api/v1alpha1/sdewan_types.go b/api/v1alpha1/sdewan_types.go
new file mode 100644 (file)
index 0000000..8b39bc4
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+
+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.
+
+type Network struct {
+       Name string `json:"name"`
+
+       // +optional
+       IsProvider bool `json:"isProvider"`
+
+       // +optional
+       Interface string `json:"interface"`
+
+       // +optional
+       DefaultGateway bool `json:"defaultGateway"`
+}
+
+// SdewanSpec defines the desired state of Sdewan
+type SdewanSpec struct {
+       // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
+       // Important: Run "make" to regenerate code after modifying this file
+
+       // Foo is an example field of Sdewan. Edit Sdewan_types.go to remove/update
+       Node     string    `json:"node"`
+       Networks []Network `json:"networks"`
+       // +optional
+       Mwan3Conf string `json:"mwan3Conf"`
+}
+
+type Mwan3Status struct {
+       Name string `json:"name"`
+
+       IsApplied bool `json:"isApplied"`
+
+       // +optional
+       // +nullable
+       AppliedTime *metav1.Time `json:"appliedTime"`
+}
+
+// SdewanStatus defines the observed state of Sdewan
+type SdewanStatus struct {
+       // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
+       // Important: Run "make" to regenerate code after modifying this file
+       // +optional
+       Mwan3Status Mwan3Status `json:"mwan3Status"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+
+// Sdewan is the Schema for the sdewans API
+type Sdewan struct {
+       metav1.TypeMeta   `json:",inline"`
+       metav1.ObjectMeta `json:"metadata,omitempty"`
+
+       Spec   SdewanSpec   `json:"spec,omitempty"`
+       Status SdewanStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// SdewanList contains a list of Sdewan
+type SdewanList struct {
+       metav1.TypeMeta `json:",inline"`
+       metav1.ListMeta `json:"metadata,omitempty"`
+       Items           []Sdewan `json:"items"`
+}
+
+func init() {
+       SchemeBuilder.Register(&Sdewan{}, &SdewanList{})
+}
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
new file mode 100644 (file)
index 0000000..3d59282
--- /dev/null
@@ -0,0 +1,321 @@
+// +build !ignore_autogenerated
+
+/*
+
+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.
+*/
+
+// Code generated by controller-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+       "k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Mwan3Conf) DeepCopyInto(out *Mwan3Conf) {
+       *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 Mwan3Conf.
+func (in *Mwan3Conf) DeepCopy() *Mwan3Conf {
+       if in == nil {
+               return nil
+       }
+       out := new(Mwan3Conf)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Mwan3Conf) 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 *Mwan3ConfList) DeepCopyInto(out *Mwan3ConfList) {
+       *out = *in
+       out.TypeMeta = in.TypeMeta
+       in.ListMeta.DeepCopyInto(&out.ListMeta)
+       if in.Items != nil {
+               in, out := &in.Items, &out.Items
+               *out = make([]Mwan3Conf, len(*in))
+               for i := range *in {
+                       (*in)[i].DeepCopyInto(&(*out)[i])
+               }
+       }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mwan3ConfList.
+func (in *Mwan3ConfList) DeepCopy() *Mwan3ConfList {
+       if in == nil {
+               return nil
+       }
+       out := new(Mwan3ConfList)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Mwan3ConfList) 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 *Mwan3ConfSpec) DeepCopyInto(out *Mwan3ConfSpec) {
+       *out = *in
+       if in.Policies != nil {
+               in, out := &in.Policies, &out.Policies
+               *out = make(map[string]PolicyMembers, len(*in))
+               for key, val := range *in {
+                       (*out)[key] = *val.DeepCopy()
+               }
+       }
+       if in.Rules != nil {
+               in, out := &in.Rules, &out.Rules
+               *out = make(map[string]Rule, len(*in))
+               for key, val := range *in {
+                       (*out)[key] = val
+               }
+       }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mwan3ConfSpec.
+func (in *Mwan3ConfSpec) DeepCopy() *Mwan3ConfSpec {
+       if in == nil {
+               return nil
+       }
+       out := new(Mwan3ConfSpec)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Mwan3ConfStatus) DeepCopyInto(out *Mwan3ConfStatus) {
+       *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mwan3ConfStatus.
+func (in *Mwan3ConfStatus) DeepCopy() *Mwan3ConfStatus {
+       if in == nil {
+               return nil
+       }
+       out := new(Mwan3ConfStatus)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Mwan3Status) DeepCopyInto(out *Mwan3Status) {
+       *out = *in
+       if in.AppliedTime != nil {
+               in, out := &in.AppliedTime, &out.AppliedTime
+               *out = (*in).DeepCopy()
+       }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mwan3Status.
+func (in *Mwan3Status) DeepCopy() *Mwan3Status {
+       if in == nil {
+               return nil
+       }
+       out := new(Mwan3Status)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Network) DeepCopyInto(out *Network) {
+       *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Network.
+func (in *Network) DeepCopy() *Network {
+       if in == nil {
+               return nil
+       }
+       out := new(Network)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PolicyMember) DeepCopyInto(out *PolicyMember) {
+       *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyMember.
+func (in *PolicyMember) DeepCopy() *PolicyMember {
+       if in == nil {
+               return nil
+       }
+       out := new(PolicyMember)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PolicyMembers) DeepCopyInto(out *PolicyMembers) {
+       *out = *in
+       if in.Members != nil {
+               in, out := &in.Members, &out.Members
+               *out = make([]PolicyMember, len(*in))
+               copy(*out, *in)
+       }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyMembers.
+func (in *PolicyMembers) DeepCopy() *PolicyMembers {
+       if in == nil {
+               return nil
+       }
+       out := new(PolicyMembers)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Rule) DeepCopyInto(out *Rule) {
+       *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rule.
+func (in *Rule) DeepCopy() *Rule {
+       if in == nil {
+               return nil
+       }
+       out := new(Rule)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Sdewan) DeepCopyInto(out *Sdewan) {
+       *out = *in
+       out.TypeMeta = in.TypeMeta
+       in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+       in.Spec.DeepCopyInto(&out.Spec)
+       in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Sdewan.
+func (in *Sdewan) DeepCopy() *Sdewan {
+       if in == nil {
+               return nil
+       }
+       out := new(Sdewan)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Sdewan) 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 *SdewanList) DeepCopyInto(out *SdewanList) {
+       *out = *in
+       out.TypeMeta = in.TypeMeta
+       in.ListMeta.DeepCopyInto(&out.ListMeta)
+       if in.Items != nil {
+               in, out := &in.Items, &out.Items
+               *out = make([]Sdewan, len(*in))
+               for i := range *in {
+                       (*in)[i].DeepCopyInto(&(*out)[i])
+               }
+       }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SdewanList.
+func (in *SdewanList) DeepCopy() *SdewanList {
+       if in == nil {
+               return nil
+       }
+       out := new(SdewanList)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *SdewanList) 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 *SdewanSpec) DeepCopyInto(out *SdewanSpec) {
+       *out = *in
+       if in.Networks != nil {
+               in, out := &in.Networks, &out.Networks
+               *out = make([]Network, len(*in))
+               copy(*out, *in)
+       }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SdewanSpec.
+func (in *SdewanSpec) DeepCopy() *SdewanSpec {
+       if in == nil {
+               return nil
+       }
+       out := new(SdewanSpec)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SdewanStatus) DeepCopyInto(out *SdewanStatus) {
+       *out = *in
+       in.Mwan3Status.DeepCopyInto(&out.Mwan3Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SdewanStatus.
+func (in *SdewanStatus) DeepCopy() *SdewanStatus {
+       if in == nil {
+               return nil
+       }
+       out := new(SdewanStatus)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *UnmatchError) DeepCopyInto(out *UnmatchError) {
+       *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UnmatchError.
+func (in *UnmatchError) DeepCopy() *UnmatchError {
+       if in == nil {
+               return nil
+       }
+       out := new(UnmatchError)
+       in.DeepCopyInto(out)
+       return out
+}
diff --git a/config/certmanager/certificate.yaml b/config/certmanager/certificate.yaml
new file mode 100644 (file)
index 0000000..237c937
--- /dev/null
@@ -0,0 +1,25 @@
+# The following manifests contain a self-signed issuer CR and a certificate CR.
+# More document can be found at https://docs.cert-manager.io
+# WARNING: Targets CertManager 0.11 check https://docs.cert-manager.io/en/latest/tasks/upgrading/index.html for breaking changes
+apiVersion: cert-manager.io/v1alpha2
+kind: Issuer
+metadata:
+  name: selfsigned-issuer
+  namespace: system
+spec:
+  selfSigned: {}
+---
+apiVersion: cert-manager.io/v1alpha2
+kind: Certificate
+metadata:
+  name: serving-cert  # this name should match the one appeared in kustomizeconfig.yaml
+  namespace: system
+spec:
+  # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize
+  dnsNames:
+  - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc
+  - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local
+  issuerRef:
+    kind: Issuer
+    name: selfsigned-issuer
+  secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize
diff --git a/config/certmanager/kustomization.yaml b/config/certmanager/kustomization.yaml
new file mode 100644 (file)
index 0000000..bebea5a
--- /dev/null
@@ -0,0 +1,5 @@
+resources:
+- certificate.yaml
+
+configurations:
+- kustomizeconfig.yaml
diff --git a/config/certmanager/kustomizeconfig.yaml b/config/certmanager/kustomizeconfig.yaml
new file mode 100644 (file)
index 0000000..90d7c31
--- /dev/null
@@ -0,0 +1,16 @@
+# This configuration is for teaching kustomize how to update name ref and var substitution 
+nameReference:
+- kind: Issuer
+  group: cert-manager.io
+  fieldSpecs:
+  - kind: Certificate
+    group: cert-manager.io
+    path: spec/issuerRef/name
+
+varReference:
+- kind: Certificate
+  group: cert-manager.io
+  path: spec/commonName
+- kind: Certificate
+  group: cert-manager.io
+  path: spec/dnsNames
diff --git a/config/crd/bases/batch.sdewan.akraino.org_mwan3confs.yaml b/config/crd/bases/batch.sdewan.akraino.org_mwan3confs.yaml
new file mode 100644 (file)
index 0000000..547f4e9
--- /dev/null
@@ -0,0 +1,104 @@
+
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.2.5
+  creationTimestamp: null
+  name: mwan3confs.batch.sdewan.akraino.org
+spec:
+  group: batch.sdewan.akraino.org
+  names:
+    kind: Mwan3Conf
+    listKind: Mwan3ConfList
+    plural: mwan3confs
+    singular: mwan3conf
+  scope: Namespaced
+  subresources:
+    status: {}
+  validation:
+    openAPIV3Schema:
+      description: Mwan3Conf is the Schema for the mwan3confs 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: Mwan3ConfSpec defines the desired state of Mwan3Conf
+          properties:
+            policy:
+              additionalProperties:
+                properties:
+                  members:
+                    items:
+                      properties:
+                        metric:
+                          type: integer
+                        network:
+                          type: string
+                        weight:
+                          type: integer
+                      required:
+                      - metric
+                      - network
+                      - weight
+                      type: object
+                    type: array
+                required:
+                - members
+                type: object
+              type: object
+            rule:
+              additionalProperties:
+                properties:
+                  dest_ip:
+                    type: string
+                  dest_port:
+                    type: string
+                  family:
+                    type: string
+                  proto:
+                    type: string
+                  src_IP:
+                    type: string
+                  src_port:
+                    type: string
+                  sticky:
+                    type: string
+                  timeout:
+                    type: string
+                  use_policy:
+                    type: string
+                required:
+                - use_policy
+                type: object
+              type: object
+          required:
+          - policy
+          - rule
+          type: object
+        status:
+          description: Mwan3ConfStatus defines the observed state of Mwan3Conf
+          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/batch.sdewan.akraino.org_sdewans.yaml b/config/crd/bases/batch.sdewan.akraino.org_sdewans.yaml
new file mode 100644 (file)
index 0000000..991b8cc
--- /dev/null
@@ -0,0 +1,96 @@
+
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.2.5
+  creationTimestamp: null
+  name: sdewans.batch.sdewan.akraino.org
+spec:
+  group: batch.sdewan.akraino.org
+  names:
+    kind: Sdewan
+    listKind: SdewanList
+    plural: sdewans
+    singular: sdewan
+  scope: Namespaced
+  subresources:
+    status: {}
+  validation:
+    openAPIV3Schema:
+      description: Sdewan is the Schema for the sdewans 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: SdewanSpec defines the desired state of Sdewan
+          properties:
+            mwan3Conf:
+              type: string
+            networks:
+              items:
+                properties:
+                  defaultGateway:
+                    type: boolean
+                  interface:
+                    type: string
+                  isProvider:
+                    type: boolean
+                  name:
+                    type: string
+                required:
+                - name
+                type: object
+              type: array
+            node:
+              description: Foo is an example field of Sdewan. Edit Sdewan_types.go
+                to remove/update
+              type: string
+          required:
+          - networks
+          - node
+          type: object
+        status:
+          description: SdewanStatus defines the observed state of Sdewan
+          properties:
+            mwan3Status:
+              description: 'INSERT ADDITIONAL STATUS FIELD - define observed state
+                of cluster Important: Run "make" to regenerate code after modifying
+                this file'
+              properties:
+                appliedTime:
+                  format: date-time
+                  nullable: true
+                  type: string
+                isApplied:
+                  type: boolean
+                name:
+                  type: string
+              required:
+              - isApplied
+              - name
+              type: object
+          type: object
+      type: object
+  version: v1alpha1
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+status:
+  acceptedNames:
+    kind: ""
+    plural: ""
+  conditions: []
+  storedVersions: []
diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml
new file mode 100644 (file)
index 0000000..d8fda98
--- /dev/null
@@ -0,0 +1,24 @@
+# This kustomization.yaml is not intended to be run by itself,
+# since it depends on service name and namespace that are out of this kustomize package.
+# It should be run by config/default
+resources:
+- bases/batch.sdewan.akraino.org_sdewans.yaml
+- bases/batch.sdewan.akraino.org_mwan3confs.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_sdewans.yaml
+#- patches/webhook_in_mwan3confs.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_sdewans.yaml
+#- patches/cainjection_in_mwan3confs.yaml
+# +kubebuilder:scaffold:crdkustomizecainjectionpatch
+
+# the following config is for teaching kustomize how to do kustomization for CRDs.
+configurations:
+- kustomizeconfig.yaml
diff --git a/config/crd/kustomizeconfig.yaml b/config/crd/kustomizeconfig.yaml
new file mode 100644 (file)
index 0000000..6f83d9a
--- /dev/null
@@ -0,0 +1,17 @@
+# This file is for teaching kustomize how to substitute name and namespace reference in CRD
+nameReference:
+- kind: Service
+  version: v1
+  fieldSpecs:
+  - kind: CustomResourceDefinition
+    group: apiextensions.k8s.io
+    path: spec/conversion/webhookClientConfig/service/name
+
+namespace:
+- kind: CustomResourceDefinition
+  group: apiextensions.k8s.io
+  path: spec/conversion/webhookClientConfig/service/namespace
+  create: false
+
+varReference:
+- path: metadata/annotations
diff --git a/config/crd/patches/cainjection_in_mwan3confs.yaml b/config/crd/patches/cainjection_in_mwan3confs.yaml
new file mode 100644 (file)
index 0000000..a375d59
--- /dev/null
@@ -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: mwan3confs.batch.sdewan.akraino.org
diff --git a/config/crd/patches/cainjection_in_sdewans.yaml b/config/crd/patches/cainjection_in_sdewans.yaml
new file mode 100644 (file)
index 0000000..7e9c2c3
--- /dev/null
@@ -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: sdewans.batch.sdewan.akraino.org
diff --git a/config/crd/patches/webhook_in_mwan3confs.yaml b/config/crd/patches/webhook_in_mwan3confs.yaml
new file mode 100644 (file)
index 0000000..42c4580
--- /dev/null
@@ -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: mwan3confs.batch.sdewan.akraino.org
+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/crd/patches/webhook_in_sdewans.yaml b/config/crd/patches/webhook_in_sdewans.yaml
new file mode 100644 (file)
index 0000000..faaf7bc
--- /dev/null
@@ -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: sdewans.batch.sdewan.akraino.org
+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
new file mode 100644 (file)
index 0000000..7f8dc21
--- /dev/null
@@ -0,0 +1,75 @@
+# Adds namespace to all resources.
+namespace: sdwan-system
+
+# Value of this field is prepended to the
+# names of all resources, e.g. a deployment named
+# "wordpress" becomes "alices-wordpress".
+# Note that it should also match with the prefix (text before '-') of the namespace
+# field above.
+namePrefix: sdwan-
+
+# Labels to add to all resources and selectors.
+#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
+# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
+- ../certmanager
+# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 
+#- ../prometheus
+
+patchesStrategicMerge:
+- manager_image_patch.yaml
+  # Protect the /metrics endpoint by putting it behind auth.
+  # Only one of manager_auth_proxy_patch.yaml and
+  # manager_prometheus_metrics_patch.yaml should be enabled.
+- manager_auth_proxy_patch.yaml
+  # If you want your controller-manager to expose the /metrics
+  # endpoint w/o any authn/z, uncomment the following line and
+  # comment manager_auth_proxy_patch.yaml.
+  # Only one of manager_auth_proxy_patch.yaml and
+  # manager_prometheus_metrics_patch.yaml should be enabled.
+#- manager_prometheus_metrics_patch.yaml
+
+# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml
+- manager_webhook_patch.yaml
+
+# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.
+# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
+# 'CERTMANAGER' needs to be enabled to use ca injection
+- 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
diff --git a/config/default/manager_auth_proxy_patch.yaml b/config/default/manager_auth_proxy_patch.yaml
new file mode 100644 (file)
index 0000000..61cb5e7
--- /dev/null
@@ -0,0 +1,25 @@
+# This patch inject a sidecar container which is a HTTP proxy for the controller manager,
+# it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews.
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: controller-manager
+  namespace: system
+spec:
+  template:
+    spec:
+      containers:
+      - name: kube-rbac-proxy
+        image: gcr.io/kubebuilder/kube-rbac-proxy:v0.4.1
+        args:
+        - "--secure-listen-address=0.0.0.0:8443"
+        - "--upstream=http://127.0.0.1:8080/"
+        - "--logtostderr=true"
+        - "--v=10"
+        ports:
+        - containerPort: 8443
+          name: https
+      - name: manager
+        args:
+        - "--metrics-addr=127.0.0.1:8080"
+        - "--enable-leader-election"
diff --git a/config/default/manager_image_patch.yaml b/config/default/manager_image_patch.yaml
new file mode 100644 (file)
index 0000000..d5c887f
--- /dev/null
@@ -0,0 +1,15 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: controller-manager
+  namespace: system
+spec:
+  template:
+    spec:
+      containers:
+      - name: manager
+        imagePullPolicy: IfNotPresent
+        image: integratedcloudnative/sdewan-controller:dev
+        env:
+          - name: OPENWRT_IMAGE
+            value: "integratedcloudnative/openwrt:dev"
diff --git a/config/default/manager_webhook_patch.yaml b/config/default/manager_webhook_patch.yaml
new file mode 100644 (file)
index 0000000..738de35
--- /dev/null
@@ -0,0 +1,23 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: controller-manager
+  namespace: system
+spec:
+  template:
+    spec:
+      containers:
+      - name: manager
+        ports:
+        - containerPort: 9443
+          name: webhook-server
+          protocol: TCP
+        volumeMounts:
+        - mountPath: /tmp/k8s-webhook-server/serving-certs
+          name: cert
+          readOnly: true
+      volumes:
+      - name: cert
+        secret:
+          defaultMode: 420
+          secretName: webhook-server-cert
diff --git a/config/default/webhookcainjection_patch.yaml b/config/default/webhookcainjection_patch.yaml
new file mode 100644 (file)
index 0000000..7e79bf9
--- /dev/null
@@ -0,0 +1,15 @@
+# This patch add annotation to admission webhook config and
+# the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize.
+apiVersion: admissionregistration.k8s.io/v1beta1
+kind: MutatingWebhookConfiguration
+metadata:
+  name: mutating-webhook-configuration
+  annotations:
+    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+---
+apiVersion: admissionregistration.k8s.io/v1beta1
+kind: ValidatingWebhookConfiguration
+metadata:
+  name: validating-webhook-configuration
+  annotations:
+    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml
new file mode 100644 (file)
index 0000000..1b69499
--- /dev/null
@@ -0,0 +1,8 @@
+resources:
+- manager.yaml
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+images:
+- name: controller
+  newName: integratedcloudnative/sdewan-controller
+  newTag: dev
diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml
new file mode 100644 (file)
index 0000000..b6c85a5
--- /dev/null
@@ -0,0 +1,39 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+  labels:
+    control-plane: controller-manager
+  name: system
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: controller-manager
+  namespace: system
+  labels:
+    control-plane: controller-manager
+spec:
+  selector:
+    matchLabels:
+      control-plane: controller-manager
+  replicas: 1
+  template:
+    metadata:
+      labels:
+        control-plane: controller-manager
+    spec:
+      containers:
+      - command:
+        - /manager
+        args:
+        - --enable-leader-election
+        image: controller:latest
+        name: manager
+        resources:
+          limits:
+            cpu: 100m
+            memory: 30Mi
+          requests:
+            cpu: 100m
+            memory: 20Mi
+      terminationGracePeriodSeconds: 10
diff --git a/config/prometheus/kustomization.yaml b/config/prometheus/kustomization.yaml
new file mode 100644 (file)
index 0000000..ed13716
--- /dev/null
@@ -0,0 +1,2 @@
+resources:
+- monitor.yaml
diff --git a/config/prometheus/monitor.yaml b/config/prometheus/monitor.yaml
new file mode 100644 (file)
index 0000000..e2d9b08
--- /dev/null
@@ -0,0 +1,15 @@
+
+# Prometheus Monitor Service (Metrics)
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor
+metadata:
+  labels:
+    control-plane: controller-manager
+  name: controller-manager-metrics-monitor
+  namespace: system
+spec:
+  endpoints:
+    - path: /metrics
+      port: https
+  selector:
+    control-plane: controller-manager
diff --git a/config/rbac/auth_proxy_role.yaml b/config/rbac/auth_proxy_role.yaml
new file mode 100644 (file)
index 0000000..618f5e4
--- /dev/null
@@ -0,0 +1,13 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: proxy-role
+rules:
+- apiGroups: ["authentication.k8s.io"]
+  resources:
+  - tokenreviews
+  verbs: ["create"]
+- apiGroups: ["authorization.k8s.io"]
+  resources:
+  - subjectaccessreviews
+  verbs: ["create"]
diff --git a/config/rbac/auth_proxy_role_binding.yaml b/config/rbac/auth_proxy_role_binding.yaml
new file mode 100644 (file)
index 0000000..48ed1e4
--- /dev/null
@@ -0,0 +1,12 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: proxy-rolebinding
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: proxy-role
+subjects:
+- kind: ServiceAccount
+  name: default
+  namespace: system
diff --git a/config/rbac/auth_proxy_service.yaml b/config/rbac/auth_proxy_service.yaml
new file mode 100644 (file)
index 0000000..6cf656b
--- /dev/null
@@ -0,0 +1,14 @@
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    control-plane: controller-manager
+  name: controller-manager-metrics-service
+  namespace: system
+spec:
+  ports:
+  - name: https
+    port: 8443
+    targetPort: https
+  selector:
+    control-plane: controller-manager
diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml
new file mode 100644 (file)
index 0000000..817f1fe
--- /dev/null
@@ -0,0 +1,11 @@
+resources:
+- role.yaml
+- role_binding.yaml
+- leader_election_role.yaml
+- leader_election_role_binding.yaml
+# Comment the following 3 lines if you want to disable
+# the auth proxy (https://github.com/brancz/kube-rbac-proxy)
+# which protects your /metrics endpoint.
+- auth_proxy_service.yaml
+- auth_proxy_role.yaml
+- auth_proxy_role_binding.yaml
diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml
new file mode 100644 (file)
index 0000000..eaa7915
--- /dev/null
@@ -0,0 +1,32 @@
+# permissions to do leader election.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: leader-election-role
+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
diff --git a/config/rbac/leader_election_role_binding.yaml b/config/rbac/leader_election_role_binding.yaml
new file mode 100644 (file)
index 0000000..eed1690
--- /dev/null
@@ -0,0 +1,12 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: leader-election-rolebinding
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: leader-election-role
+subjects:
+- kind: ServiceAccount
+  name: default
+  namespace: system
diff --git a/config/rbac/mwan3conf_editor_role.yaml b/config/rbac/mwan3conf_editor_role.yaml
new file mode 100644 (file)
index 0000000..3e57fb7
--- /dev/null
@@ -0,0 +1,26 @@
+# permissions to do edit mwan3confs.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: mwan3conf-editor-role
+rules:
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - mwan3confs
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - mwan3confs/status
+  verbs:
+  - get
+  - patch
+  - update
diff --git a/config/rbac/mwan3conf_viewer_role.yaml b/config/rbac/mwan3conf_viewer_role.yaml
new file mode 100644 (file)
index 0000000..2d4e1c4
--- /dev/null
@@ -0,0 +1,20 @@
+# permissions to do viewer mwan3confs.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: mwan3conf-viewer-role
+rules:
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - mwan3confs
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - mwan3confs/status
+  verbs:
+  - get
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
new file mode 100644 (file)
index 0000000..fcf1614
--- /dev/null
@@ -0,0 +1,48 @@
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  creationTimestamp: null
+  name: manager-role
+rules:
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - mwan3confs
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - mwan3confs/status
+  verbs:
+  - get
+  - patch
+  - update
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - sdewans
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - sdewans/status
+  verbs:
+  - get
+  - patch
+  - update
diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml
new file mode 100644 (file)
index 0000000..8f26587
--- /dev/null
@@ -0,0 +1,12 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: manager-rolebinding
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: manager-role
+subjects:
+- kind: ServiceAccount
+  name: default
+  namespace: system
diff --git a/config/rbac/sdewan_editor_role.yaml b/config/rbac/sdewan_editor_role.yaml
new file mode 100644 (file)
index 0000000..154cdae
--- /dev/null
@@ -0,0 +1,26 @@
+# permissions to do edit sdewans.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: sdewan-editor-role
+rules:
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - sdewans
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - sdewans/status
+  verbs:
+  - get
+  - patch
+  - update
diff --git a/config/rbac/sdewan_viewer_role.yaml b/config/rbac/sdewan_viewer_role.yaml
new file mode 100644 (file)
index 0000000..b953aee
--- /dev/null
@@ -0,0 +1,20 @@
+# permissions to do viewer sdewans.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: sdewan-viewer-role
+rules:
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - sdewans
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - sdewans/status
+  verbs:
+  - get
diff --git a/config/samples/batch_v1alpha1_mwan3conf.yaml b/config/samples/batch_v1alpha1_mwan3conf.yaml
new file mode 100644 (file)
index 0000000..f3b5921
--- /dev/null
@@ -0,0 +1,16 @@
+apiVersion: batch.sdewan.akraino.org/v1alpha1
+kind: Mwan3Conf
+metadata:
+  name: example-mwan3conf
+spec:
+  # Add fields here
+  policy:
+    testpolicy1:
+      members:
+        - network: ovn-priv-net
+          metric: 1
+          weight: 4
+  rule:
+    https:
+      use_policy: testpolicy1
+      dest_ip: 10.10.10.16
diff --git a/config/samples/batch_v1alpha1_sdewan.yaml b/config/samples/batch_v1alpha1_sdewan.yaml
new file mode 100644 (file)
index 0000000..336ce04
--- /dev/null
@@ -0,0 +1,10 @@
+apiVersion: batch.sdewan.akraino.org/v1alpha1
+kind: Sdewan
+metadata:
+  name: example-sdewan
+spec:
+  # Add fields here
+  node: "ubuntu18"
+  networks:
+    - name: ovn-priv-net
+  mwan3Conf: example-mwan3conf
diff --git a/config/webhook/kustomization.yaml b/config/webhook/kustomization.yaml
new file mode 100644 (file)
index 0000000..9cf2613
--- /dev/null
@@ -0,0 +1,6 @@
+resources:
+- manifests.yaml
+- service.yaml
+
+configurations:
+- kustomizeconfig.yaml
diff --git a/config/webhook/kustomizeconfig.yaml b/config/webhook/kustomizeconfig.yaml
new file mode 100644 (file)
index 0000000..25e21e3
--- /dev/null
@@ -0,0 +1,25 @@
+# the following config is for teaching kustomize where to look at when substituting vars.
+# It requires kustomize v2.1.0 or newer to work properly.
+nameReference:
+- kind: Service
+  version: v1
+  fieldSpecs:
+  - kind: MutatingWebhookConfiguration
+    group: admissionregistration.k8s.io
+    path: webhooks/clientConfig/service/name
+  - kind: ValidatingWebhookConfiguration
+    group: admissionregistration.k8s.io
+    path: webhooks/clientConfig/service/name
+
+namespace:
+- kind: MutatingWebhookConfiguration
+  group: admissionregistration.k8s.io
+  path: webhooks/clientConfig/service/namespace
+  create: true
+- kind: ValidatingWebhookConfiguration
+  group: admissionregistration.k8s.io
+  path: webhooks/clientConfig/service/namespace
+  create: true
+
+varReference:
+- path: metadata/annotations
diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml
new file mode 100644 (file)
index 0000000..486d07f
--- /dev/null
@@ -0,0 +1,52 @@
+
+---
+apiVersion: admissionregistration.k8s.io/v1beta1
+kind: MutatingWebhookConfiguration
+metadata:
+  creationTimestamp: null
+  name: mutating-webhook-configuration
+webhooks:
+- clientConfig:
+    caBundle: Cg==
+    service:
+      name: webhook-service
+      namespace: system
+      path: /mutate-batch-sdewan-akraino-org-v1alpha1-mwan3conf
+  failurePolicy: Fail
+  name: mmwan3conf.kb.io
+  rules:
+  - apiGroups:
+    - batch.sdewan.akraino.org
+    apiVersions:
+    - v1alpha1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - mwan3confs
+
+---
+apiVersion: admissionregistration.k8s.io/v1beta1
+kind: ValidatingWebhookConfiguration
+metadata:
+  creationTimestamp: null
+  name: validating-webhook-configuration
+webhooks:
+- clientConfig:
+    caBundle: Cg==
+    service:
+      name: webhook-service
+      namespace: system
+      path: /validate-batch-sdewan-akraino-org-v1alpha1-mwan3conf
+  failurePolicy: Fail
+  name: vmwan3conf.kb.io
+  rules:
+  - apiGroups:
+    - batch.sdewan.akraino.org
+    apiVersions:
+    - v1alpha1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - mwan3confs
diff --git a/config/webhook/service.yaml b/config/webhook/service.yaml
new file mode 100644 (file)
index 0000000..31e0f82
--- /dev/null
@@ -0,0 +1,12 @@
+
+apiVersion: v1
+kind: Service
+metadata:
+  name: webhook-service
+  namespace: system
+spec:
+  ports:
+    - port: 443
+      targetPort: 9443
+  selector:
+    control-plane: controller-manager
diff --git a/controllers/mwan3conf_controller.go b/controllers/mwan3conf_controller.go
new file mode 100644 (file)
index 0000000..8a92e3d
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package controllers
+
+import (
+       "context"
+
+       "github.com/go-logr/logr"
+       "k8s.io/apimachinery/pkg/api/errors"
+       "k8s.io/apimachinery/pkg/runtime"
+       ctrl "sigs.k8s.io/controller-runtime"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+
+       batchv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"
+)
+
+// Mwan3ConfReconciler reconciles a Mwan3Conf object
+type Mwan3ConfReconciler struct {
+       client.Client
+       Log    logr.Logger
+       Scheme *runtime.Scheme
+}
+
+// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=mwan3confs,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=mwan3confs/status,verbs=get;update;patch
+
+func (r *Mwan3ConfReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
+       ctx := context.Background()
+       log := r.Log.WithValues("mwan3conf", req.NamespacedName)
+
+       instance := &batchv1alpha1.Mwan3Conf{}
+       err := r.Get(ctx, req.NamespacedName, instance)
+       if err != nil {
+               if errors.IsNotFound(err) {
+                       // Request object not found, could have been deleted after reconcile request.
+                       // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
+                       // Return and don't requeue
+                       return ctrl.Result{}, nil
+               }
+               // Error reading the object - requeue the request.
+               return ctrl.Result{}, err
+       }
+
+       foundSdewans := &batchv1alpha1.SdewanList{}
+       err = r.List(ctx, foundSdewans, client.MatchingFields{".spec.mwan3Conf": instance.Name})
+       if err != nil && errors.IsNotFound(err) {
+               log.Info("No sdewan using this mwan3 conf", "mwan3conf", instance.Name)
+               return ctrl.Result{}, nil
+       } else if err != nil {
+               log.Error(err, "Failed to get the sdewan list using this mwan3 conf", "mwan3conf", instance.Name)
+               return ctrl.Result{}, nil
+       } else {
+               log.Info("Applying mwan3 conf for multiple sdewan instances as the conf changes", "mwan3conf", instance.Name)
+               for _, sdewan := range foundSdewans.Items {
+                       // Updating sdewan to set status isapplied = false
+                       // this will trigger sdewan controller to apply the new conf
+                       sdewan.Status.Mwan3Status = batchv1alpha1.Mwan3Status{Name: instance.Name, IsApplied: false}
+                       err := r.Status().Update(ctx, &sdewan)
+                       if err != nil {
+                               log.Error(err, "Failed to update the sdewan instance status", "sdewan", sdewan.Name)
+                       }
+               }
+       }
+       return ctrl.Result{}, nil
+}
+
+func (r *Mwan3ConfReconciler) SetupWithManager(mgr ctrl.Manager) error {
+       if err := mgr.GetFieldIndexer().IndexField(&batchv1alpha1.Sdewan{}, ".spec.mwan3Conf", func(rawObj runtime.Object) []string {
+               // grab the job object, extract the owner...
+               sdewan := rawObj.(*batchv1alpha1.Sdewan)
+               return []string{sdewan.Spec.Mwan3Conf}
+       }); err != nil {
+               return err
+       }
+       return ctrl.NewControllerManagedBy(mgr).
+               For(&batchv1alpha1.Mwan3Conf{}).
+               Complete(r)
+}
diff --git a/controllers/sdewan_controller.go b/controllers/sdewan_controller.go
new file mode 100644 (file)
index 0000000..72a6788
--- /dev/null
@@ -0,0 +1,365 @@
+/*
+
+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"
+       "encoding/json"
+       "fmt"
+       "os"
+       "reflect"
+       "time"
+
+       "github.com/go-logr/logr"
+       corev1 "k8s.io/api/core/v1"
+       "k8s.io/apimachinery/pkg/api/errors"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apimachinery/pkg/runtime"
+       "k8s.io/apimachinery/pkg/types"
+       "k8s.io/apimachinery/pkg/util/intstr"
+       ctrl "sigs.k8s.io/controller-runtime"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+
+       batchv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"
+       "sdewan.akraino.org/sdewan/wrtprovider"
+)
+
+var OpenwrtTag = "hle2/openwrt-1806-mwan3:latest"
+
+func init() {
+       if img := os.Getenv("OPENWRT_IMAGE"); img != "" {
+               OpenwrtTag = img
+       }
+}
+
+// SdewanReconciler reconciles a Sdewan object
+type SdewanReconciler struct {
+       client.Client
+       Log    logr.Logger
+       Scheme *runtime.Scheme
+}
+
+// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=sdewans,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=batch.sdewan.akraino.org,resources=sdewans/status,verbs=get;update;patch
+
+func (r *SdewanReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
+       ctx := context.Background()
+       log := r.Log.WithValues("sdewan", req.NamespacedName)
+
+       // your logic here
+       // Fetch the Sdewan instance
+       instance := &batchv1alpha1.Sdewan{}
+       err := r.Get(ctx, req.NamespacedName, instance)
+       if err != nil {
+               if errors.IsNotFound(err) {
+                       // Request object not found, could have been deleted after reconcile request.
+                       // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
+                       // Return and don't requeue
+                       return ctrl.Result{}, nil
+               }
+               // Error reading the object - requeue the request.
+               return ctrl.Result{}, err
+       }
+       for i, network := range instance.Spec.Networks {
+               if network.Interface == "" {
+                       instance.Spec.Networks[i].Interface = fmt.Sprintf("net%d", i)
+               }
+       }
+
+       cm := newConfigmapForCR(instance)
+       if err := ctrl.SetControllerReference(instance, cm, r.Scheme); err != nil {
+               log.Error(err, "Failed to set configmap controller reference", "Configmap.Name", cm.Name)
+               return ctrl.Result{}, nil
+       }
+       foundcm := &corev1.ConfigMap{}
+       err = r.Get(ctx, types.NamespacedName{Name: cm.Name, Namespace: cm.Namespace}, foundcm)
+       if err != nil && errors.IsNotFound(err) {
+               log.Info("Creating a new Configmap", "Configmap.Namespace", cm.Namespace, "Configmap.Name", cm.Name)
+               err = r.Create(ctx, cm)
+               if err != nil {
+                       log.Error(err, "Failed to create configmap", "Configmap.Name", cm.Name)
+                       return ctrl.Result{}, nil
+               }
+       } else if err != nil {
+               log.Error(err, "Error happends when fetch the configmap", "Configmap.Name", cm.Name)
+               return ctrl.Result{}, nil
+       } else if reflect.DeepEqual(foundcm.Data, cm.Data) {
+               log.Info("Updating Configmap", "Configmap.Namespace", cm.Namespace, "Configmap.Name", cm.Name)
+               err = r.Update(ctx, cm)
+               if err != nil {
+                       log.Error(err, "Failed to update configmap", "Configmap.Name", cm.Name)
+                       return ctrl.Result{}, nil
+               }
+       } else {
+               log.Info("Configmap not changed", "Configmap.Namespace", foundcm.Namespace, "Configmap.Name", foundcm.Name)
+       }
+       // Define a new Pod object
+       pod := newPodForCR(instance)
+
+       // Set Sdewan instance as the owner and controller
+       if err := ctrl.SetControllerReference(instance, pod, r.Scheme); err != nil {
+               return ctrl.Result{}, nil
+       }
+
+       // Check if this Pod already exists
+       foundpod := &corev1.Pod{}
+       err = r.Get(ctx, types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}, foundpod)
+       if err != nil && errors.IsNotFound(err) {
+               log.Info("Creating a new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name)
+               err = r.Create(ctx, pod)
+               if err != nil {
+                       log.Error(err, "Failed to create the new pod", "Name", pod.Name)
+                       return ctrl.Result{}, nil
+               }
+
+               // Pod created successfully - don't requeue
+               log.Info("A new Pod created", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name)
+       } else if err != nil {
+               return ctrl.Result{}, nil
+       } else {
+               // Pod already exists - don't requeue
+               log.Info("Pod already exists", "Pod.Namespace", foundpod.Namespace, "Pod.Name", foundpod.Name)
+       }
+
+       svc := newSvcForCR(instance)
+       // Set Sdewan instance as the owner and controller
+       if err := ctrl.SetControllerReference(instance, svc, r.Scheme); err != nil {
+               return ctrl.Result{}, nil
+       }
+       // Check if this svc already exists
+       foundsvc := &corev1.Service{}
+       err = r.Get(ctx, types.NamespacedName{Name: svc.Name, Namespace: svc.Namespace}, foundsvc)
+       if err != nil && errors.IsNotFound(err) {
+               log.Info("Creating a new Service", "Service.Namespace", svc.Namespace, "Service.Name", svc.Name)
+               err = r.Create(ctx, svc)
+               if err != nil {
+                       return ctrl.Result{}, nil
+               }
+               log.Info("A new Service created", "Service.Namespace", svc.Namespace, "Service.Name", svc.Name)
+       } else if err != nil {
+               return ctrl.Result{}, nil
+       } else {
+               log.Info("Service already exists", "Service.Namespace", foundsvc.Namespace, "Service.Name", foundsvc.Name)
+       }
+
+       // Apply rules if the pod is ready
+       if len(foundpod.Status.ContainerStatuses) > 0 && foundpod.Status.ContainerStatuses[0].Ready {
+               mwan3conf := &batchv1alpha1.Mwan3Conf{}
+               err = r.Get(ctx, types.NamespacedName{Name: instance.Spec.Mwan3Conf, Namespace: instance.Namespace}, mwan3conf)
+               if err != nil {
+                       log.Error(err, "unable to find the mwan3conf", "namespace", instance.Namespace, "mwan3 name", instance.Spec.Mwan3Conf)
+                       instance.Status.Mwan3Status = batchv1alpha1.Mwan3Status{Name: instance.Spec.Mwan3Conf, IsApplied: false}
+                       err = r.Status().Update(ctx, instance)
+                       if err != nil {
+                               log.Error(err, "Failed to update Sdewan status")
+                               return ctrl.Result{}, nil
+                       }
+                       return ctrl.Result{}, nil
+               }
+               if (instance.Status.Mwan3Status.Name != instance.Spec.Mwan3Conf) || !instance.Status.Mwan3Status.IsApplied {
+                       err = wrtprovider.Mwan3Apply(mwan3conf, instance)
+                       if err != nil {
+                               log.Error(err, "Failed to apply the mwan3conf", "namespace", instance.Namespace, "mwan3 name", instance.Spec.Mwan3Conf)
+                               instance.Status.Mwan3Status = batchv1alpha1.Mwan3Status{Name: instance.Spec.Mwan3Conf, IsApplied: false}
+                               r.Status().Update(ctx, instance)
+                               return ctrl.Result{}, nil
+                       }
+                       instance.Status.Mwan3Status = batchv1alpha1.Mwan3Status{Name: instance.Spec.Mwan3Conf, IsApplied: true, AppliedTime: &metav1.Time{Time: time.Now()}}
+                       err = r.Status().Update(ctx, instance)
+                       if err != nil {
+                               log.Error(err, "Failed to update Sdewan status")
+                               return ctrl.Result{}, nil
+                       }
+                       log.Info("sdewan config applied")
+               } else {
+                       log.Info("mwan3 conf not chnaged, so not re-apply", "sdewan", instance.Name)
+               }
+       } else {
+               log.Info("Don't apply conf as the pod is not ready", "sdewan", instance.Name)
+       }
+
+       return ctrl.Result{}, nil
+}
+
+func (r *SdewanReconciler) SetupWithManager(mgr ctrl.Manager) error {
+       return ctrl.NewControllerManagedBy(mgr).
+               For(&batchv1alpha1.Sdewan{}).
+               Owns(&corev1.Pod{}).
+               Complete(r)
+}
+
+// newSvcForCR returns a svc with the same name/namespace as the cr
+func newSvcForCR(cr *batchv1alpha1.Sdewan) *corev1.Service {
+       return &corev1.Service{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      cr.Name,
+                       Namespace: cr.Namespace,
+               },
+               Spec: corev1.ServiceSpec{
+                       Ports: []corev1.ServicePort{
+                               {
+                                       Name: "api",
+                                       Port: 80,
+                               },
+                       },
+                       Selector: map[string]string{
+                               "app": cr.Name,
+                       },
+               },
+       }
+}
+
+// newConfigmapForCR returns a configmap with the same name/namespace as the cr
+func newConfigmapForCR(cr *batchv1alpha1.Sdewan) *corev1.ConfigMap {
+       netjson, _ := json.MarshalIndent(cr.Spec.Networks, "", "  ")
+       return &corev1.ConfigMap{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      cr.Name,
+                       Namespace: cr.Namespace,
+               },
+               Data: map[string]string{
+                       "networks.json": string(netjson),
+                       "entrypoint.sh": `#!/bin/bash
+# Always exit on errors.
+set -e
+echo "" > /etc/config/network
+cat > /etc/config/mwan3 <<EOF
+config globals 'globals'
+    option mmx_mask '0x3F00'
+    option local_source 'lan'
+EOF
+for net in $(jq -c ".[]" /tmp/sdewan/networks.json)
+do
+  interface=$(echo $net | jq -r .interface)
+  ipaddr=$(ifconfig $interface | awk '/inet/{print $2}' | cut -f2 -d ":" | awk 'NR==1 {print $1}')
+  if [ "$isProvider" == "true" ] || [ "$isProvider" == "1" ]]
+  then
+    vif="wan_$interface"
+  else
+    vif="lan_$interface"
+  fi
+  netmask=$(ifconfig $interface | awk '/inet/{print $4}' | cut -f2 -d ":" | awk 'NR==1 {print $1}')
+  cat >> /etc/config/network <<EOF
+config interface '$vif'
+    option ifname '$interface'
+    option proto 'static'
+    option ipaddr '$ipaddr'
+    option netmask '$netmask'
+EOF
+  cat >> /etc/config/mwan3 <<EOF
+config interface '$vif'
+        option enabled '1'
+        option family 'ipv4'
+        option reliability '2'
+        option count '1'
+        option timeout '2'
+        option failure_latency '1000'
+        option recovery_latency '500'
+        option failure_loss '20'
+        option recovery_loss '5'
+        option interval '5'
+        option down '3'
+        option up '8'
+EOF
+done
+/sbin/procd &
+/sbin/ubusd &
+iptables -S
+sleep 1
+/etc/init.d/rpcd start
+/etc/init.d/dnsmasq start
+/etc/init.d/network start
+/etc/init.d/odhcpd start
+/etc/init.d/uhttpd start
+/etc/init.d/log start
+/etc/init.d/dropbear start
+/etc/init.d/mwan3 restart
+echo "Entering sleep... (success)"
+# Sleep forever.
+while true; do sleep 100; done`,
+               },
+       }
+}
+
+// newPodForCR returns a busybox pod with the same name/namespace as the cr
+func newPodForCR(cr *batchv1alpha1.Sdewan) *corev1.Pod {
+       labels := map[string]string{
+               "app": cr.Name,
+       }
+       priv := true
+       var netmaps []map[string]interface{}
+       for _, net := range cr.Spec.Networks {
+               netmaps = append(netmaps, map[string]interface{}{
+                       "name":           net.Name,
+                       "interface":      net.Interface,
+                       "defaultGateway": fmt.Sprintf("%t", net.DefaultGateway),
+               })
+       }
+       netjson, _ := json.MarshalIndent(netmaps, "", "  ")
+       volumes := []corev1.Volume{
+               {
+                       Name: cr.Name,
+                       VolumeSource: corev1.VolumeSource{
+                               ConfigMap: &corev1.ConfigMapVolumeSource{
+                                       LocalObjectReference: corev1.LocalObjectReference{Name: cr.Name},
+                               },
+                       },
+               },
+       }
+       return &corev1.Pod{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      cr.Name,
+                       Namespace: cr.Namespace,
+                       Labels:    labels,
+                       Annotations: map[string]string{
+                               "k8s.v1.cni.cncf.io/networks":      `[{ "name": "ovn-networkobj"}]`,
+                               "k8s.plugin.opnfv.org/nfn-network": `{ "type": "ovn4nfv", "interface": ` + string(netjson) + "}",
+                       },
+               },
+               Spec: corev1.PodSpec{
+                       Containers: []corev1.Container{
+                               {
+                                       Name:            "sdewan",
+                                       Image:           OpenwrtTag,
+                                       Command:         []string{"/bin/sh", "/tmp/sdewan/entrypoint.sh"},
+                                       ImagePullPolicy: corev1.PullIfNotPresent,
+                                       ReadinessProbe: &corev1.Probe{
+                                               Handler: corev1.Handler{HTTPGet: &corev1.HTTPGetAction{
+                                                       Path:   "/",
+                                                       Port:   intstr.IntOrString{IntVal: 80},
+                                                       Scheme: corev1.URISchemeHTTP},
+                                               },
+                                               InitialDelaySeconds: 5,
+                                               PeriodSeconds:       5,
+                                               FailureThreshold:    5,
+                                       },
+                                       SecurityContext: &corev1.SecurityContext{
+                                               Privileged: &priv,
+                                       },
+                                       VolumeMounts: []corev1.VolumeMount{
+                                               {
+                                                       Name:      cr.Name,
+                                                       ReadOnly:  true,
+                                                       MountPath: "/tmp/sdewan",
+                                               },
+                                       },
+                               },
+                       },
+                       NodeSelector: map[string]string{"kubernetes.io/hostname": cr.Spec.Node},
+                       Volumes:      volumes,
+               },
+       }
+}
diff --git a/controllers/suite_test.go b/controllers/suite_test.go
new file mode 100644 (file)
index 0000000..b1c15a3
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+
+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 (
+       "path/filepath"
+       "testing"
+
+       . "github.com/onsi/ginkgo"
+       . "github.com/onsi/gomega"
+
+       "k8s.io/client-go/kubernetes/scheme"
+       "k8s.io/client-go/rest"
+       batchv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+       "sigs.k8s.io/controller-runtime/pkg/envtest"
+       logf "sigs.k8s.io/controller-runtime/pkg/log"
+       "sigs.k8s.io/controller-runtime/pkg/log/zap"
+       // +kubebuilder:scaffold:imports
+)
+
+// These tests use Ginkgo (BDD-style Go testing framework). Refer to
+// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
+
+var cfg *rest.Config
+var k8sClient client.Client
+var testEnv *envtest.Environment
+
+func TestAPIs(t *testing.T) {
+       RegisterFailHandler(Fail)
+
+       RunSpecsWithDefaultAndCustomReporters(t,
+               "Controller Suite",
+               []Reporter{envtest.NewlineReporter{}})
+}
+
+var _ = BeforeSuite(func(done Done) {
+       logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
+
+       By("bootstrapping test environment")
+       testEnv = &envtest.Environment{
+               CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
+       }
+
+       var err error
+       cfg, err = testEnv.Start()
+       Expect(err).ToNot(HaveOccurred())
+       Expect(cfg).ToNot(BeNil())
+
+       err = batchv1alpha1.AddToScheme(scheme.Scheme)
+       Expect(err).NotTo(HaveOccurred())
+
+       err = batchv1alpha1.AddToScheme(scheme.Scheme)
+       Expect(err).NotTo(HaveOccurred())
+
+       // +kubebuilder:scaffold:scheme
+
+       k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
+       Expect(err).ToNot(HaveOccurred())
+       Expect(k8sClient).ToNot(BeNil())
+
+       close(done)
+}, 60)
+
+var _ = AfterSuite(func() {
+       By("tearing down the test environment")
+       err := testEnv.Stop()
+       Expect(err).ToNot(HaveOccurred())
+})
diff --git a/deploy/crds/sdewan.akraino.org_mwan3rules_crd.yaml b/deploy/crds/sdewan.akraino.org_mwan3rules_crd.yaml
new file mode 100644 (file)
index 0000000..1be69ee
--- /dev/null
@@ -0,0 +1,76 @@
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: mwan3rules.sdewan.akraino.org
+spec:
+  group: sdewan.akraino.org
+  names:
+    kind: Mwan3Rule
+    listKind: Mwan3RuleList
+    plural: mwan3rules
+    singular: mwan3rule
+  scope: Namespaced
+  subresources:
+    status: {}
+  validation:
+    openAPIV3Schema:
+      description: Mwan3Rule is the Schema for the mwan3rules 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: Mwan3RuleSpec defines the desired state of Mwan3Rule
+          properties:
+            policy:
+              description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
+                Important: Run "operator-sdk generate k8s" to regenerate code after
+                modifying this file Add custom validation using kubebuilder tags:
+                https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'
+            rule:
+              additionalProperties:
+                properties:
+                  dest_ip:
+                    type: string
+                  dest_port:
+                    type: string
+                  family:
+                    type: string
+                  proto:
+                    type: string
+                  src_IP:
+                    type: string
+                  src_port:
+                    type: string
+                  sticky:
+                    type: string
+                  timeout:
+                    type: string
+                  use_policy:
+                    type: string
+                required:
+                - use_policy
+                type: object
+              type: object
+          required:
+          - policy
+          - rule
+          type: object
+        status:
+          description: Mwan3RuleStatus defines the observed state of Mwan3Rule
+          type: object
+      type: object
+  version: v1alpha1
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
diff --git a/deploy/crds/sdewan.akraino.org_sdewans_crd.yaml b/deploy/crds/sdewan.akraino.org_sdewans_crd.yaml
new file mode 100644 (file)
index 0000000..698269f
--- /dev/null
@@ -0,0 +1,88 @@
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: sdewans.sdewan.akraino.org
+spec:
+  group: sdewan.akraino.org
+  names:
+    kind: Sdewan
+    listKind: SdewanList
+    plural: sdewans
+    singular: sdewan
+  scope: Namespaced
+  subresources:
+    status: {}
+  validation:
+    openAPIV3Schema:
+      description: Sdewan is the Schema for the sdewans 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: SdewanSpec defines the desired state of Sdewan
+          properties:
+            mwan3Rule:
+              type: string
+            networks:
+              items:
+                description: SdewanSpec defines the desired state of Sdewan
+                properties:
+                  defaultGateway:
+                    type: boolean
+                  interface:
+                    type: string
+                  isProvider:
+                    type: boolean
+                  name:
+                    type: string
+                required:
+                - name
+                type: object
+              type: array
+            node:
+              description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
+                Important: Run "operator-sdk generate k8s" to regenerate code after
+                modifying this file Add custom validation using kubebuilder tags:
+                https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'
+              type: string
+          required:
+          - networks
+          - node
+          type: object
+        status:
+          description: SdewanStatus defines the observed state of Sdewan
+          properties:
+            mwan3Status:
+              description: 'INSERT ADDITIONAL STATUS FIELD - define observed state
+                of cluster Important: Run "operator-sdk generate k8s" to regenerate
+                code after modifying this file Add custom validation using kubebuilder
+                tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'
+              properties:
+                appliedTime:
+                  type: string
+                isApplied:
+                  type: boolean
+                name:
+                  type: string
+              required:
+              - name
+              type: object
+          required:
+          - mwan3Status
+          type: object
+      type: object
+  version: v1alpha1
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
diff --git a/deploy/crds/sdewan.akraino.org_v1alpha1_mwan3rule_cr.yaml b/deploy/crds/sdewan.akraino.org_v1alpha1_mwan3rule_cr.yaml
new file mode 100644 (file)
index 0000000..2eeca7a
--- /dev/null
@@ -0,0 +1,15 @@
+apiVersion: sdewan.akraino.org/v1alpha1
+kind: Mwan3Rule
+metadata:
+  name: example-mwan3rule
+spec:
+  # Add fields here
+  policy:
+    testpolicy1:
+    - network: ovn-priv-net
+      metric: 1
+      weight: 4
+  rule:
+    https:
+      use_policy: testpolicy1
+      dest_ip: 10.10.10.10
diff --git a/deploy/crds/sdewan.akraino.org_v1alpha1_sdewan_cr.yaml b/deploy/crds/sdewan.akraino.org_v1alpha1_sdewan_cr.yaml
new file mode 100644 (file)
index 0000000..19092dd
--- /dev/null
@@ -0,0 +1,10 @@
+apiVersion: sdewan.akraino.org/v1alpha1
+kind: Sdewan
+metadata:
+  name: example-sdewan
+spec:
+  # Add fields here
+  node: "ubuntu18"
+  networks:
+    - name: ovn-priv-net
+  mwan3Rule: example-mwan3rule
diff --git a/deploy/operator.yaml b/deploy/operator.yaml
new file mode 100644 (file)
index 0000000..ea2ceb3
--- /dev/null
@@ -0,0 +1,46 @@
+---
+apiVersion: k8s.cni.cncf.io/v1
+kind: NetworkAttachmentDefinition
+metadata:
+  name: ovn-networkobj
+spec:
+  config: '{
+     "cniVersion": "0.3.1",
+     "name": "ovn4nfv-k8s-plugin",
+     "type": "ovn4nfvk8s-cni"
+  }'
+
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: sdewan-operator
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      name: sdewan-operator
+  template:
+    metadata:
+      labels:
+        name: sdewan-operator
+    spec:
+      serviceAccountName: sdewan-operator
+      containers:
+        - name: sdewan-operator
+          # Replace this with the built image name
+          image: chengtcli/sdewan-operator:latest
+          command:
+          - sdewan-operator
+          imagePullPolicy: IfNotPresent
+          env:
+            - name: WATCH_NAMESPACE
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.namespace
+            - name: POD_NAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.name
+            - name: OPERATOR_NAME
+              value: "sdewan-operator"
diff --git a/deploy/role.yaml b/deploy/role.yaml
new file mode 100644 (file)
index 0000000..46f5a18
--- /dev/null
@@ -0,0 +1,81 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  creationTimestamp: null
+  name: sdewan-operator
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - pods
+  - services
+  - services/finalizers
+  - endpoints
+  - persistentvolumeclaims
+  - events
+  - configmaps
+  - secrets
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - apps
+  resources:
+  - deployments
+  - daemonsets
+  - replicasets
+  - statefulsets
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - monitoring.coreos.com
+  resources:
+  - servicemonitors
+  verbs:
+  - get
+  - create
+- apiGroups:
+  - apps
+  resourceNames:
+  - sdewan-operator
+  resources:
+  - deployments/finalizers
+  verbs:
+  - update
+- apiGroups:
+  - ""
+  resources:
+  - pods
+  verbs:
+  - get
+- apiGroups:
+  - apps
+  resources:
+  - replicasets
+  - deployments
+  verbs:
+  - get
+- apiGroups:
+  - sdewan.akraino.org
+  resources:
+  - '*'
+  - mwan3rules
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
diff --git a/deploy/role_binding.yaml b/deploy/role_binding.yaml
new file mode 100644 (file)
index 0000000..3631643
--- /dev/null
@@ -0,0 +1,11 @@
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: sdewan-operator
+subjects:
+- kind: ServiceAccount
+  name: sdewan-operator
+roleRef:
+  kind: Role
+  name: sdewan-operator
+  apiGroup: rbac.authorization.k8s.io
diff --git a/deploy/service_account.yaml b/deploy/service_account.yaml
new file mode 100644 (file)
index 0000000..17bdb2f
--- /dev/null
@@ -0,0 +1,4 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: sdewan-operator
diff --git a/go.mod b/go.mod
new file mode 100644 (file)
index 0000000..695b128
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,16 @@
+module sdewan.akraino.org/sdewan
+
+go 1.13
+
+require (
+       github.com/emicklei/go-restful v2.9.6+incompatible // indirect
+       github.com/go-logr/logr v0.1.0
+       github.com/go-openapi/spec v0.19.4 // indirect
+       github.com/onsi/ginkgo v1.10.1
+       github.com/onsi/gomega v1.7.0
+       golang.org/x/sys v0.0.0-20190922100055-0a153f010e69 // indirect
+       k8s.io/api v0.0.0-20191114100352-16d7abae0d2a
+       k8s.io/apimachinery v0.17.0
+       k8s.io/client-go v0.0.0-20191114101535-6c5935290e33
+       sigs.k8s.io/controller-runtime v0.4.0
+)
diff --git a/go.sum b/go.sum
new file mode 100644 (file)
index 0000000..c1a7a3e
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,602 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
+github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
+github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
+github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
+github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
+github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
+github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
+github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
+github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
+github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
+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/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+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=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+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/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+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=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
+github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
+github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/emicklei/go-restful v2.9.6+incompatible h1:tfrHha8zJ01ywiOEC1miGY8st1/igzWB8OmvPgoYX7w=
+github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
+github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/go-critic/go-critic v0.3.5-0.20190904082202-d79a9f0c64db/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg=
+github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
+github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54=
+github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
+github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
+github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
+github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
+github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
+github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
+github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
+github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
+github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
+github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
+github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
+github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
+github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
+github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
+github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
+github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
+github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
+github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
+github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
+github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
+github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=
+github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
+github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
+github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
+github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
+github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
+github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
+github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
+github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
+github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
+github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
+github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
+github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
+github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
+github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
+github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
+github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
+github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
+github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
+github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
+github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
+github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ=
+github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
+github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
+github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg=
+github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw=
+github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU=
+github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk=
+github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI=
+github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks=
+github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc=
+github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
+github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
+github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
+github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
+github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 h1:u4bArs140e9+AfE52mFHOXVFnOSBJBRlzTHrOPLOIhE=
+github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
+github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk=
+github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=
+github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8=
+github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o=
+github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
+github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
+github.com/golangci/golangci-lint v1.19.1/go.mod h1:2CEc4Fxx3vxDv7g8DyXkHCBF73AOzAymcJAprs2vCps=
+github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
+github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
+github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
+github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA=
+github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI=
+github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
+github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
+github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
+github.com/googleapis/gnostic v0.3.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
+github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
+github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
+github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
+github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
+github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
+github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
+github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
+github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+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/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/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
+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=
+github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
+github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
+github.com/matoous/godox v0.0.0-20190910121045-032ad8106c86/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
+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/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+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/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
+github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
+github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
+github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
+github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
+github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
+github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
+github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
+github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
+github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
+github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
+github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
+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/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+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=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
+github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+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/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/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+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=
+go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
+go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
+go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
+go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
+golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+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-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-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68=
+golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
+golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
+golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190621203818-d432491b9138/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg=
+golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190922100055-0a153f010e69 h1:rOhMmluY6kLMhdnrivzec6lLgaVbMHMn2ISQXJeJ5EM=
+golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/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.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0=
+gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
+gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
+gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
+gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+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/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=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+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/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
+gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.0.0/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/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=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+k8s.io/api v0.0.0-20190313235455-40a48860b5ab/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
+k8s.io/api v0.0.0-20190918155943-95b840bb6a1f h1:8FRUST8oUkEI45WYKyD8ed7Ad0Kg5v11zHyPkEVb2xo=
+k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48=
+k8s.io/api v0.0.0-20191016225839-816a9b7df678 h1:z/0BV/tMBIvdwZvqBH/f7TWjQX9y3dj1nMNhrSK0h/8=
+k8s.io/api v0.0.0-20191016225839-816a9b7df678/go.mod h1:LZQaT8MvVpl7Bg2lYFcQm7+Mpdxq8p1NFl3yh+5DCwY=
+k8s.io/api v0.0.0-20191114100352-16d7abae0d2a h1:86XISgFlG7lPOWj6wYLxd+xqhhVt/WQjS4Tf39rP09s=
+k8s.io/api v0.0.0-20191114100352-16d7abae0d2a/go.mod h1:qetVJgs5i8jwdFIdoOZ70ks0ecgU+dYwqZ2uD1srwOU=
+k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM=
+k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
+k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783 h1:V6ndwCPoao1yZ52agqOKaUAl7DYWVGiXjV7ePA2i610=
+k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY=
+k8s.io/apiextensions-apiserver v0.17.0 h1:+XgcGxqaMztkbbvsORgCmHIb4uImHKvTjNyu7b8gRnA=
+k8s.io/apiextensions-apiserver v0.17.0/go.mod h1:XiIFUakZywkUl54fVXa7QTEHcqQz9HG55nHd1DCoHj8=
+k8s.io/apimachinery v0.0.0-20190313205120-d7deff9243b1/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
+k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655 h1:CS1tBQz3HOXiseWZu6ZicKX361CZLT97UFnnPx0aqBw=
+k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4=
+k8s.io/apimachinery v0.0.0-20191016225534-b1267f8c42b4/go.mod h1:92mWDd8Ji2sw2157KIgino5wCxffA8KSvhW2oY4ypdw=
+k8s.io/apimachinery v0.0.0-20191020214737-6c8691705fc5/go.mod h1:92mWDd8Ji2sw2157KIgino5wCxffA8KSvhW2oY4ypdw=
+k8s.io/apimachinery v0.0.0-20191028221656-72ed19daf4bb/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ=
+k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo=
+k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
+k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg=
+k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg=
+k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90 h1:mLmhKUm1X+pXu0zXMEzNsOF5E2kKFGe5o6BZBIIqA6A=
+k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk=
+k8s.io/client-go v0.0.0-20191114101535-6c5935290e33 h1:07mhG/2oEoo3N+sHVOo0L9PJ/qvbk3N5n2dj8IWefnQ=
+k8s.io/client-go v0.0.0-20191114101535-6c5935290e33/go.mod h1:4L/zQOBkEf4pArQJ+CMk1/5xjA30B5oyWv+Bzb44DOw=
+k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg=
+k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
+k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=
+k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
+k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE=
+k8s.io/code-generator v0.17.0/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
+k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA=
+k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc=
+k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
+k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
+k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
+k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
+k8s.io/klog v0.4.0 h1:lCJCxf/LIowc2IGS9TPjWDyXY4nOmdGdfcwwDQCOURQ=
+k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
+k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
+k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
+k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf h1:EYm5AW/UUDbnmnI+gK0TJDVK9qPLhM+sRHYanNKw0EQ=
+k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
+k8s.io/kube-openapi v0.0.0-20190918143330-0270cf2f1c1d/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
+k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
+k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
+k8s.io/utils v0.0.0-20190801114015-581e00157fb1 h1:+ySTxfHnfzZb9ys375PXNlLhkJPLKgHajBU0N62BDvE=
+k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
+k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
+k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
+modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
+modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
+modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
+modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
+modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
+sigs.k8s.io/controller-runtime v0.4.0 h1:wATM6/m+3w8lj8FXNaO6Fs/rq/vqoOjO1Q116Z9NPsg=
+sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns=
+sigs.k8s.io/controller-tools v0.2.5 h1:kH7HKWed9XO42OTxyhUtqyImiefdZV2Q9Jbrytvhf18=
+sigs.k8s.io/controller-tools v0.2.5/go.mod h1:+t0Hz6tOhJQCdd7IYO0mNzimmiM9sqMU0021u6UCF2o=
+sigs.k8s.io/kustomize/api v0.1.1 h1:W2dWXex2MhF4/EZNokZllvet2RejCHqdAFklufN7VTg=
+sigs.k8s.io/kustomize/api v0.1.1/go.mod h1:FyfJD1q1QMjC/TvK78b6cCtZB+mbpnGIo9YOvbucJes=
+sigs.k8s.io/kustomize/kustomize/v3 v3.2.1/go.mod h1:jXW5RpjfoZtLLrpCKVCZ6AHs8iV3+nkRl084TFFsWLE=
+sigs.k8s.io/kustomize/kustomize/v3 v3.2.2/go.mod h1:fptJqEJbGtNOHHQgc9dhN/Vme7q3aCHu2x9vi7fAFoQ=
+sigs.k8s.io/kustomize/kustomize/v3 v3.2.3/go.mod h1:zdSwGK28DHEPaQoYmSIi/sgg4uCRPtWljj1mxz3Mafw=
+sigs.k8s.io/kustomize/kustomize/v3 v3.3.0 h1:1uBNN0OUo7jKHIyvvTYtjNIZNFcyG3d4i2xbWr3dKVc=
+sigs.k8s.io/kustomize/kustomize/v3 v3.3.0/go.mod h1:eMtSZo5hDlU1358uVNF+sj2fhc2Jod0MxESFemKpFe8=
+sigs.k8s.io/kustomize/v3 v3.2.0/go.mod h1:ztX4zYc/QIww3gSripwF7TBOarBTm5BvyAMem0kCzOE=
+sigs.k8s.io/kustomize/v3 v3.3.0/go.mod h1:STVEDXXV/PoFIQPMI8uVcrYME/YnMIp1+lyBnK4xgik=
+sigs.k8s.io/kustomize/v3 v3.3.1/go.mod h1:2ojB+51Z+YIBpEOknAFX3U8f0XXa94PFcfXPccDxAfg=
+sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
+sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
+sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
+sigs.k8s.io/testing_frameworks v0.1.2 h1:vK0+tvjF0BZ/RYFeZ1E6BYBwHJJXhjuZ3TdsEKH+UQM=
+sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w=
+sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
+sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt
new file mode 100644 (file)
index 0000000..b92001f
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+
+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.
+*/
\ No newline at end of file
diff --git a/main.go b/main.go
new file mode 100644 (file)
index 0000000..be54ae6
--- /dev/null
+++ b/main.go
@@ -0,0 +1,96 @@
+/*
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+       "flag"
+       "os"
+
+       "k8s.io/apimachinery/pkg/runtime"
+       clientgoscheme "k8s.io/client-go/kubernetes/scheme"
+       _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
+       batchv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"
+       "sdewan.akraino.org/sdewan/controllers"
+       ctrl "sigs.k8s.io/controller-runtime"
+       "sigs.k8s.io/controller-runtime/pkg/log/zap"
+       // +kubebuilder:scaffold:imports
+)
+
+var (
+       scheme   = runtime.NewScheme()
+       setupLog = ctrl.Log.WithName("setup")
+)
+
+func init() {
+       _ = clientgoscheme.AddToScheme(scheme)
+
+       _ = batchv1alpha1.AddToScheme(scheme)
+       // +kubebuilder:scaffold:scheme
+}
+
+func main() {
+       var metricsAddr string
+       var enableLeaderElection bool
+       flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
+       flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
+               "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
+       flag.Parse()
+
+       ctrl.SetLogger(zap.New(func(o *zap.Options) {
+               o.Development = true
+       }))
+
+       mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
+               Scheme:             scheme,
+               MetricsBindAddress: metricsAddr,
+               LeaderElection:     enableLeaderElection,
+               Port:               9443,
+       })
+       if err != nil {
+               setupLog.Error(err, "unable to start manager")
+               os.Exit(1)
+       }
+
+       if err = (&controllers.SdewanReconciler{
+               Client: mgr.GetClient(),
+               Log:    ctrl.Log.WithName("controllers").WithName("Sdewan"),
+               Scheme: mgr.GetScheme(),
+       }).SetupWithManager(mgr); err != nil {
+               setupLog.Error(err, "unable to create controller", "controller", "Sdewan")
+               os.Exit(1)
+       }
+       if err = (&controllers.Mwan3ConfReconciler{
+               Client: mgr.GetClient(),
+               Log:    ctrl.Log.WithName("controllers").WithName("Mwan3Conf"),
+               Scheme: mgr.GetScheme(),
+       }).SetupWithManager(mgr); err != nil {
+               setupLog.Error(err, "unable to create controller", "controller", "Mwan3Conf")
+               os.Exit(1)
+       }
+       if os.Getenv("ENABLE_WEBHOOKS") != "false" {
+               if err = (&batchv1alpha1.Mwan3Conf{}).SetupWebhookWithManager(mgr); err != nil {
+                       setupLog.Error(err, "unable to create webhook", "webhook", "Mwan3Conf")
+                       os.Exit(1)
+               }
+       }
+       // +kubebuilder:scaffold:builder
+
+       setupLog.Info("starting manager")
+       if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
+               setupLog.Error(err, "problem running manager")
+               os.Exit(1)
+       }
+}
diff --git a/openwrt/mwan3.go b/openwrt/mwan3.go
new file mode 100644 (file)
index 0000000..5860e3d
--- /dev/null
@@ -0,0 +1,242 @@
+package openwrt
+
+import (
+       "encoding/json"
+)
+
+const (
+       mwan3BaseURL = "sdewan/mwan3/v1/"
+)
+
+type Mwan3Client struct {
+       OpenwrtClient *openwrtClient
+}
+
+// MWAN3 interface status
+type IpStatus struct {
+       Status     string `json:"status"`
+       Latency    int    `json:"latency"`
+       Packetloss int    `json:"packetloss"`
+       Ip         string `json:"ip"`
+}
+
+type WanInterfaceStatus struct {
+       Running bool       `json:"running"`
+       Score   int        `json:"score"`
+       Lost    int        `json:"lost"`
+       Status  string     `json:"status"`
+       Age     int        `json:"age"`
+       Turn    int        `json:"turn"`
+       Ips     []IpStatus `json:"track_ip"`
+}
+
+type InterfaceStatus struct {
+       Interfaces map[string]WanInterfaceStatus `json:"interfaces"`
+       Connected  map[string][]string           `json:"connected"`
+}
+
+// MWAN3 Policy
+type SdewanMember struct {
+       Interface string `json:"interface"`
+       Metric    string `json:"metric"`
+       Weight    string `json:"weight"`
+}
+
+type SdewanPolicy struct {
+       Name    string         `json:"name"`
+       Members []SdewanMember `json:"members"`
+}
+
+type SdewanPolicies struct {
+       Policies []SdewanPolicy `json:"policies"`
+}
+
+// MWAN3 Rule
+type SdewanRule struct {
+       Name     string `json:"name"`
+       Policy   string `json:"policy"`
+       SrcIp    string `json:"src_ip"`
+       SrcPort  string `json:"src_port"`
+       DestIp   string `json:"dest_ip"`
+       DestPort string `json:"dest_port"`
+       Proto    string `json:"proto"`
+       Family   string `json:"family"`
+       Sticky   string `json:"sticky"`
+       Timeout  string `json:"timeout"`
+}
+
+type SdewanRules struct {
+       Rules []SdewanRule `json:"rules"`
+}
+
+// get interface status
+func (m *Mwan3Client) GetInterfaceStatus() (*InterfaceStatus, error) {
+       response, err := m.OpenwrtClient.Get("admin/status/mwan/interface_status")
+       if err != nil {
+               return nil, err
+       }
+
+       var interfaceStatus InterfaceStatus
+       err2 := json.Unmarshal([]byte(response), &interfaceStatus)
+       if err2 != nil {
+               return nil, err2
+       }
+
+       return &interfaceStatus, nil
+}
+
+// Policy APIs
+// get policies
+func (m *Mwan3Client) GetPolicies() (*SdewanPolicies, error) {
+       response, err := m.OpenwrtClient.Get(mwan3BaseURL + "policies")
+       if err != nil {
+               return nil, err
+       }
+
+       var sdewanPolicies SdewanPolicies
+       err2 := json.Unmarshal([]byte(response), &sdewanPolicies)
+       if err2 != nil {
+               return nil, err2
+       }
+
+       return &sdewanPolicies, nil
+}
+
+// get policy
+func (m *Mwan3Client) GetPolicy(policy_name string) (*SdewanPolicy, error) {
+       response, err := m.OpenwrtClient.Get(mwan3BaseURL + "policy/" + policy_name)
+       if err != nil {
+               return nil, err
+       }
+
+       var sdewanPolicy SdewanPolicy
+       err2 := json.Unmarshal([]byte(response), &sdewanPolicy)
+       if err2 != nil {
+               return nil, err2
+       }
+
+       return &sdewanPolicy, nil
+}
+
+// create policy
+func (m *Mwan3Client) CreatePolicy(policy SdewanPolicy) (*SdewanPolicy, error) {
+       policy_obj, _ := json.Marshal(policy)
+       response, err := m.OpenwrtClient.Post(mwan3BaseURL+"policy", string(policy_obj))
+       if err != nil {
+               return nil, err
+       }
+
+       var sdewanPolicy SdewanPolicy
+       err2 := json.Unmarshal([]byte(response), &sdewanPolicy)
+       if err2 != nil {
+               return nil, err2
+       }
+
+       return &sdewanPolicy, nil
+}
+
+// delete policy
+func (m *Mwan3Client) DeletePolicy(policy_name string) error {
+       _, err := m.OpenwrtClient.Delete(mwan3BaseURL + "policy/" + policy_name)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// update policy
+func (m *Mwan3Client) UpdatePolicy(policy SdewanPolicy) (*SdewanPolicy, error) {
+       policy_obj, _ := json.Marshal(policy)
+       policy_name := policy.Name
+       response, err := m.OpenwrtClient.Put(mwan3BaseURL+"policy/"+policy_name, string(policy_obj))
+       if err != nil {
+               return nil, err
+       }
+
+       var sdewanPolicy SdewanPolicy
+       err2 := json.Unmarshal([]byte(response), &sdewanPolicy)
+       if err2 != nil {
+               return nil, err2
+       }
+
+       return &sdewanPolicy, nil
+}
+
+// Rule APIs
+// get rules
+func (m *Mwan3Client) GetRules() (*SdewanRules, error) {
+       response, err := m.OpenwrtClient.Get(mwan3BaseURL + "rules")
+       if err != nil {
+               return nil, err
+       }
+
+       var sdewanRules SdewanRules
+       err2 := json.Unmarshal([]byte(response), &sdewanRules)
+       if err2 != nil {
+               return nil, err2
+       }
+
+       return &sdewanRules, nil
+}
+
+// get rule
+func (m *Mwan3Client) GetRule(rule string) (*SdewanRule, error) {
+       response, err := m.OpenwrtClient.Get(mwan3BaseURL + "rule/" + rule)
+       if err != nil {
+               return nil, err
+       }
+
+       var sdewanRule SdewanRule
+       err2 := json.Unmarshal([]byte(response), &sdewanRule)
+       if err2 != nil {
+               return nil, err2
+       }
+
+       return &sdewanRule, nil
+}
+
+// create rule
+func (m *Mwan3Client) CreateRule(rule SdewanRule) (*SdewanRule, error) {
+       rule_obj, _ := json.Marshal(rule)
+       response, err := m.OpenwrtClient.Post(mwan3BaseURL+"rule", string(rule_obj))
+       if err != nil {
+               return nil, err
+       }
+
+       var sdewanRule SdewanRule
+       err2 := json.Unmarshal([]byte(response), &sdewanRule)
+       if err2 != nil {
+               return nil, err2
+       }
+
+       return &sdewanRule, nil
+}
+
+// delete rule
+func (m *Mwan3Client) DeleteRule(rule_name string) error {
+       _, err := m.OpenwrtClient.Delete(mwan3BaseURL + "rule/" + rule_name)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// update rule
+func (m *Mwan3Client) UpdateRule(rule SdewanRule) (*SdewanRule, error) {
+       rule_obj, _ := json.Marshal(rule)
+       rule_name := rule.Name
+       response, err := m.OpenwrtClient.Put(mwan3BaseURL+"rule/"+rule_name, string(rule_obj))
+       if err != nil {
+               return nil, err
+       }
+
+       var sdewanRule SdewanRule
+       err2 := json.Unmarshal([]byte(response), &sdewanRule)
+       if err2 != nil {
+               return nil, err2
+       }
+
+       return &sdewanRule, nil
+}
diff --git a/openwrt/openwrtclient.go b/openwrt/openwrtclient.go
new file mode 100644 (file)
index 0000000..a187da0
--- /dev/null
@@ -0,0 +1,148 @@
+package openwrt
+
+import (
+       "bytes"
+       "fmt"
+       "io/ioutil"
+       "net/http"
+       "strings"
+)
+
+type OpenwrtError struct {
+       Code    int
+       Message string
+}
+
+func (e *OpenwrtError) Error() string {
+       return fmt.Sprintf("Error Code: %d, Error Message: %s", e.Code, e.Message)
+}
+
+type openwrtClient struct {
+       ip       string
+       user     string
+       password string
+       token    string
+}
+
+func NewOpenwrtClient(ip string, user string, password string) *openwrtClient {
+       client := &openwrtClient{
+               ip:       ip,
+               user:     user,
+               password: password,
+               token:    "",
+       }
+
+       return client
+}
+
+// openwrt base URL
+func (o *openwrtClient) getBaseURL() string {
+       return "http://" + o.ip + "/cgi-bin/luci/"
+}
+
+// login to openwrt http server
+func (o *openwrtClient) login() error {
+       client := &http.Client{
+               // block redirect
+               CheckRedirect: func(req *http.Request, via []*http.Request) error {
+                       return http.ErrUseLastResponse
+               },
+       }
+
+       // login
+       login_info := "luci_username=" + o.user + "&luci_password=" + o.password
+       var req_body = []byte(login_info)
+       req, _ := http.NewRequest("POST", o.getBaseURL(), bytes.NewBuffer(req_body))
+       req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+
+       resp, err := client.Do(req)
+       if resp != nil {
+               defer resp.Body.Close()
+       }
+
+       if err != nil {
+               return err
+       } else if resp.StatusCode != 302 {
+               // fail to auth
+               return &OpenwrtError{Code: resp.StatusCode, Message: "Unauthorized"}
+       } else {
+               // get token
+               res_cookie := resp.Header["Set-Cookie"][0]
+               res_cookies := strings.Split(res_cookie, ";")
+               for _, cookie := range res_cookies {
+                       cookie := strings.TrimSpace(cookie)
+                       index := strings.Index(cookie, "=")
+                       var key = cookie
+                       var value = ""
+                       if index != -1 {
+                               key = cookie[:index]
+                               value = cookie[index+1:]
+                       }
+
+                       if key == "sysauth" {
+                               o.token = value
+                               break
+                       }
+               }
+       }
+
+       return nil
+}
+
+// call openwrt restful API
+func (o *openwrtClient) call(method string, url string, request string) (string, error) {
+       for i := 0; i < 2; i++ {
+               if o.token == "" {
+                       err := o.login()
+                       if err != nil {
+                               return "", err
+                       }
+               }
+
+               client := &http.Client{}
+               req_body := bytes.NewBuffer([]byte(request))
+               req, _ := http.NewRequest(method, o.getBaseURL()+url, req_body)
+               req.Header.Add("Cookie", "sysauth="+o.token)
+               resp, err := client.Do(req)
+               if err != nil {
+                       return "", err
+               }
+               defer resp.Body.Close()
+
+               body, _ := ioutil.ReadAll(resp.Body)
+               if resp.StatusCode >= 400 {
+                       if resp.StatusCode == 403 {
+                               // token expired, retry
+                               o.token = ""
+                               continue
+                       } else {
+                               // error request
+                               return "", &OpenwrtError{Code: resp.StatusCode, Message: string(body)}
+                       }
+               }
+
+               return string(body), nil
+       }
+
+       return "", nil
+}
+
+// call openwrt Get restful API
+func (o *openwrtClient) Get(url string) (string, error) {
+       return o.call("GET", url, "")
+}
+
+// call openwrt restful API
+func (o *openwrtClient) Post(url string, request string) (string, error) {
+       return o.call("POST", url, request)
+}
+
+// call openwrt restful API
+func (o *openwrtClient) Put(url string, request string) (string, error) {
+       return o.call("PUT", url, request)
+}
+
+// call openwrt restful API
+func (o *openwrtClient) Delete(url string) (string, error) {
+       return o.call("DELETE", url, "")
+}
diff --git a/openwrt/service.go b/openwrt/service.go
new file mode 100644 (file)
index 0000000..7580e94
--- /dev/null
@@ -0,0 +1,54 @@
+package openwrt
+
+import (
+       "encoding/json"
+)
+
+const (
+       serviceBaseURL = "sdewan/v1/"
+)
+
+var available_Services = []string{"mwan3", "firewall", "ipsec"}
+
+type ServiceClient struct {
+       OpenwrtClient *openwrtClient
+}
+
+// Service API struct
+type AvailableServices struct {
+       Services []string `json:"services"`
+}
+
+// get available services
+func (s *ServiceClient) GetAvailableServices() (*AvailableServices, error) {
+       response, err := s.OpenwrtClient.Get(serviceBaseURL + "services")
+       if err != nil {
+               return nil, err
+       }
+
+       var servs AvailableServices
+       err2 := json.Unmarshal([]byte(response), &servs)
+       if err2 != nil {
+               return nil, err2
+       }
+
+       return &servs, nil
+}
+
+func (s *ServiceClient) formatExecuteServiceBody(operation string) string {
+       return "{\"action\":\"" + operation + "\"}"
+}
+
+// execute operation on service
+func (s *ServiceClient) ExecuteService(service string, operation string) (bool, error) {
+       if !IsContained(available_Services, service) {
+               return false, &OpenwrtError{Code: 400, Message: "Bad Request: not supported service(" + service + ")"}
+       }
+
+       _, err := s.OpenwrtClient.Put(serviceBaseURL+"service/"+service, s.formatExecuteServiceBody(operation))
+       if err != nil {
+               return false, err
+       }
+
+       return true, nil
+}
diff --git a/openwrt/utils.go b/openwrt/utils.go
new file mode 100644 (file)
index 0000000..c37631d
--- /dev/null
@@ -0,0 +1,22 @@
+package openwrt
+
+import (
+       "reflect"
+)
+
+// util function to check whether items contains item
+func IsContained(items interface{}, item interface{}) bool {
+       switch reflect.TypeOf(items).Kind() {
+       case reflect.Slice:
+               v := reflect.ValueOf(items)
+               for i := 0; i < v.Len(); i++ {
+                       if reflect.DeepEqual(item, v.Index(i).Interface()) {
+                               return true
+                       }
+               }
+       default:
+               return false
+       }
+
+       return false
+}
diff --git a/readme.md b/readme.md
new file mode 100644 (file)
index 0000000..04e35aa
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,57 @@
+# Sdewan operator
+
+The sdewan operator is developed under kubebuilder framework
+
+We define two CRDs in this patch: Sdewan and Mwan3Conf.
+
+Sdewan defines the CNF base info, which node we should deploy the CNF on,
+which network should the CNF use with multus CNI, etc.
+
+The Mwan3Conf defines the mwan3 rules. In the next step, we are going to
+develop the firewall and the ipsec functions. Mwan3Conf is validated by k8s
+api admission webhook.
+
+For each created Sdewan instance, the controller creates a pod, a configmap
+and a service for the instance. The pod runs openswrt which provides network
+services, i.e. sdwan, firewall, ipsec etc.
+
+The configmap stores the network interface information and the entrypoint.sh.
+The network interface information has the following format:
+```
+[
+  {
+    "name": "ovn-priv-net",
+    "isProvider": false,
+    "interface": "net0",
+    "defaultGateway": false
+  }
+]
+```
+
+The service created by the controller is used for openwrt api access.
+We call this svc to apply rules, get openwrt info, restart openwrt service.
+
+After the openwrt pod ready, the Sdewan controller apply the configured mwan3 rules.
+mwan3 rule details are configured in Mwan3Conf CR, which is referenced by Sdewan.Spec.Mwan3Conf
+Every time the Mwan3Conf instance changes, the controller re-apply the new rules by calling opwnrt
+api. We can also change the rule refernce at the runtime.
+
+## Deployment
+
+The API admission webhook depends on cert-manager so we need to install cert-manager first.
+
+To install the CRD and the controller, we can follow this guide.
+https://book.kubebuilder.io/cronjob-tutorial/running-webhook.html
+
+We have the image built and published at `integratedcloudnative/sdewan-controller:dev`. The openwrt
+docker image we used for test is at `integratedcloudnative/openwrt:dev`. To use some other images,
+we need to make configuration in `config/default/manager_image_patch.yaml`
+
+The simple installation steps:
+1. kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v0.11.0/cert-manager.yaml
+2. kubectl apply -f sdewan-deploy.yaml
+
+## References
+
+- https://book.kubebuilder.io/
+- https://openwrt.org/
diff --git a/sdewan-deploy.yaml b/sdewan-deploy.yaml
new file mode 100644 (file)
index 0000000..8b583ca
--- /dev/null
@@ -0,0 +1,506 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+  labels:
+    control-plane: controller-manager
+  name: sdwan-system
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.2.5
+  creationTimestamp: null
+  name: mwan3confs.batch.sdewan.akraino.org
+spec:
+  group: batch.sdewan.akraino.org
+  names:
+    kind: Mwan3Conf
+    listKind: Mwan3ConfList
+    plural: mwan3confs
+    singular: mwan3conf
+  scope: Namespaced
+  subresources:
+    status: {}
+  validation:
+    openAPIV3Schema:
+      description: Mwan3Conf is the Schema for the mwan3confs 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: Mwan3ConfSpec defines the desired state of Mwan3Conf
+          properties:
+            policy:
+              additionalProperties:
+                properties:
+                  members:
+                    items:
+                      properties:
+                        metric:
+                          type: integer
+                        network:
+                          type: string
+                        weight:
+                          type: integer
+                      required:
+                      - metric
+                      - network
+                      - weight
+                      type: object
+                    type: array
+                required:
+                - members
+                type: object
+              type: object
+            rule:
+              additionalProperties:
+                properties:
+                  dest_ip:
+                    type: string
+                  dest_port:
+                    type: string
+                  family:
+                    type: string
+                  proto:
+                    type: string
+                  src_IP:
+                    type: string
+                  src_port:
+                    type: string
+                  sticky:
+                    type: string
+                  timeout:
+                    type: string
+                  use_policy:
+                    type: string
+                required:
+                - use_policy
+                type: object
+              type: object
+          required:
+          - policy
+          - rule
+          type: object
+        status:
+          description: Mwan3ConfStatus defines the observed state of Mwan3Conf
+          type: object
+      type: object
+  version: v1alpha1
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+status:
+  acceptedNames:
+    kind: ""
+    plural: ""
+  conditions: []
+  storedVersions: []
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.2.5
+  creationTimestamp: null
+  name: sdewans.batch.sdewan.akraino.org
+spec:
+  group: batch.sdewan.akraino.org
+  names:
+    kind: Sdewan
+    listKind: SdewanList
+    plural: sdewans
+    singular: sdewan
+  scope: Namespaced
+  subresources:
+    status: {}
+  validation:
+    openAPIV3Schema:
+      description: Sdewan is the Schema for the sdewans 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: SdewanSpec defines the desired state of Sdewan
+          properties:
+            mwan3Conf:
+              type: string
+            networks:
+              items:
+                properties:
+                  defaultGateway:
+                    type: boolean
+                  interface:
+                    type: string
+                  isProvider:
+                    type: boolean
+                  name:
+                    type: string
+                required:
+                - name
+                type: object
+              type: array
+            node:
+              description: Foo is an example field of Sdewan. Edit Sdewan_types.go
+                to remove/update
+              type: string
+          required:
+          - networks
+          - node
+          type: object
+        status:
+          description: SdewanStatus defines the observed state of Sdewan
+          properties:
+            mwan3Status:
+              description: 'INSERT ADDITIONAL STATUS FIELD - define observed state
+                of cluster Important: Run "make" to regenerate code after modifying
+                this file'
+              properties:
+                appliedTime:
+                  format: date-time
+                  nullable: true
+                  type: string
+                isApplied:
+                  type: boolean
+                name:
+                  type: string
+              required:
+              - isApplied
+              - name
+              type: object
+          type: object
+      type: object
+  version: v1alpha1
+  versions:
+  - name: v1alpha1
+    served: true
+    storage: true
+status:
+  acceptedNames:
+    kind: ""
+    plural: ""
+  conditions: []
+  storedVersions: []
+---
+apiVersion: admissionregistration.k8s.io/v1beta1
+kind: MutatingWebhookConfiguration
+metadata:
+  annotations:
+    cert-manager.io/inject-ca-from: sdwan-system/sdwan-serving-cert
+  creationTimestamp: null
+  name: sdwan-mutating-webhook-configuration
+webhooks:
+- clientConfig:
+    caBundle: Cg==
+    service:
+      name: sdwan-webhook-service
+      namespace: sdwan-system
+      path: /mutate-batch-sdewan-akraino-org-v1alpha1-mwan3conf
+  failurePolicy: Fail
+  name: mmwan3conf.kb.io
+  rules:
+  - apiGroups:
+    - batch.sdewan.akraino.org
+    apiVersions:
+    - v1alpha1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - mwan3confs
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: sdwan-leader-election-role
+  namespace: sdwan-system
+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
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  creationTimestamp: null
+  name: sdwan-manager-role
+rules:
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - mwan3confs
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - mwan3confs/status
+  verbs:
+  - get
+  - patch
+  - update
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - sdewans
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - batch.sdewan.akraino.org
+  resources:
+  - sdewans/status
+  verbs:
+  - get
+  - patch
+  - update
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: sdwan-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: RoleBinding
+metadata:
+  name: sdwan-leader-election-rolebinding
+  namespace: sdwan-system
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: sdwan-leader-election-role
+subjects:
+- kind: ServiceAccount
+  name: default
+  namespace: sdwan-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: sdwan-manager-rolebinding
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: sdwan-manager-role
+subjects:
+- kind: ServiceAccount
+  name: default
+  namespace: sdwan-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: sdwan-proxy-rolebinding
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: sdwan-proxy-role
+subjects:
+- kind: ServiceAccount
+  name: default
+  namespace: sdwan-system
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    control-plane: controller-manager
+  name: sdwan-controller-manager-metrics-service
+  namespace: sdwan-system
+spec:
+  ports:
+  - name: https
+    port: 8443
+    targetPort: https
+  selector:
+    control-plane: controller-manager
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: sdwan-webhook-service
+  namespace: sdwan-system
+spec:
+  ports:
+  - port: 443
+    targetPort: 9443
+  selector:
+    control-plane: controller-manager
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  labels:
+    control-plane: controller-manager
+  name: sdwan-controller-manager
+  namespace: sdwan-system
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      control-plane: controller-manager
+  template:
+    metadata:
+      labels:
+        control-plane: controller-manager
+    spec:
+      containers:
+      - args:
+        - --secure-listen-address=0.0.0.0:8443
+        - --upstream=http://127.0.0.1:8080/
+        - --logtostderr=true
+        - --v=10
+        image: gcr.io/kubebuilder/kube-rbac-proxy:v0.4.1
+        name: kube-rbac-proxy
+        ports:
+        - containerPort: 8443
+          name: https
+      - args:
+        - --metrics-addr=127.0.0.1:8080
+        - --enable-leader-election
+        command:
+        - /manager
+        env:
+        - name: OPENWRT_IMAGE
+          value: integratedcloudnative/openwrt:dev
+        image: integratedcloudnative/sdewan-controller:dev
+        imagePullPolicy: IfNotPresent
+        name: manager
+        ports:
+        - containerPort: 9443
+          name: webhook-server
+          protocol: TCP
+        resources:
+          limits:
+            cpu: 100m
+            memory: 30Mi
+          requests:
+            cpu: 100m
+            memory: 20Mi
+        volumeMounts:
+        - mountPath: /tmp/k8s-webhook-server/serving-certs
+          name: cert
+          readOnly: true
+      terminationGracePeriodSeconds: 10
+      volumes:
+      - name: cert
+        secret:
+          defaultMode: 420
+          secretName: webhook-server-cert
+---
+apiVersion: cert-manager.io/v1alpha2
+kind: Certificate
+metadata:
+  name: sdwan-serving-cert
+  namespace: sdwan-system
+spec:
+  dnsNames:
+  - sdwan-webhook-service.sdwan-system.svc
+  - sdwan-webhook-service.sdwan-system.svc.cluster.local
+  issuerRef:
+    kind: Issuer
+    name: sdwan-selfsigned-issuer
+  secretName: webhook-server-cert
+---
+apiVersion: cert-manager.io/v1alpha2
+kind: Issuer
+metadata:
+  name: sdwan-selfsigned-issuer
+  namespace: sdwan-system
+spec:
+  selfSigned: {}
+---
+apiVersion: admissionregistration.k8s.io/v1beta1
+kind: ValidatingWebhookConfiguration
+metadata:
+  annotations:
+    cert-manager.io/inject-ca-from: sdwan-system/sdwan-serving-cert
+  creationTimestamp: null
+  name: sdwan-validating-webhook-configuration
+webhooks:
+- clientConfig:
+    caBundle: Cg==
+    service:
+      name: sdwan-webhook-service
+      namespace: sdwan-system
+      path: /validate-batch-sdewan-akraino-org-v1alpha1-mwan3conf
+  failurePolicy: Fail
+  name: vmwan3conf.kb.io
+  rules:
+  - apiGroups:
+    - batch.sdewan.akraino.org
+    apiVersions:
+    - v1alpha1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - mwan3confs
diff --git a/wrtprovider/wrtprovider.go b/wrtprovider/wrtprovider.go
new file mode 100644 (file)
index 0000000..634932e
--- /dev/null
@@ -0,0 +1,179 @@
+package wrtprovider
+
+import (
+       "fmt"
+       "reflect"
+       sdewanv1alpha1 "sdewan.akraino.org/sdewan/api/v1alpha1"
+       "sdewan.akraino.org/sdewan/openwrt"
+       logf "sigs.k8s.io/controller-runtime/pkg/log"
+)
+
+var log = logf.Log.WithName("controller_sdewan")
+
+func NetworkInterfaceMap(instance *sdewanv1alpha1.Sdewan) map[string]string {
+       ifMap := make(map[string]string)
+       for i, network := range instance.Spec.Networks {
+               prefix := "lan_"
+               if network.IsProvider {
+                       prefix = "wan_"
+               }
+               if network.Interface == "" {
+                       network.Interface = fmt.Sprintf("net%d", i)
+               }
+               ifMap[network.Name] = prefix + fmt.Sprintf("net%d", i)
+       }
+       return ifMap
+}
+
+func Mwan3ReplacePolicies(policies []openwrt.SdewanPolicy, existOnes []openwrt.SdewanPolicy, client *openwrt.Mwan3Client) error {
+       // create/update new policies
+       for _, policy := range policies {
+               found := false
+               for _, p := range existOnes {
+                       if p.Name == policy.Name {
+                               if !reflect.DeepEqual(policy, p) {
+                                       _, err := client.UpdatePolicy(policy)
+                                       if err != nil {
+                                               return err
+                                       }
+                               }
+                               found = true
+                               break
+                       }
+               }
+               if found == false {
+                       _, err := client.CreatePolicy(policy)
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+
+       // remove old policies
+       for _, p := range existOnes {
+               found := false
+               for _, policy := range policies {
+                       if p.Name == policy.Name {
+                               found = true
+                               break
+                       }
+               }
+               if found == false {
+                       err := client.DeletePolicy(p.Name)
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+
+       return nil
+}
+
+func Mwan3ReplaceRules(rules []openwrt.SdewanRule, existOnes []openwrt.SdewanRule, client *openwrt.Mwan3Client) error {
+       // create/update new rules
+       for _, rule := range rules {
+               found := false
+               for _, r := range existOnes {
+                       if r.Name == rule.Name {
+                               if !reflect.DeepEqual(rule, r) {
+                                       _, err := client.UpdateRule(rule)
+                                       if err != nil {
+                                               return err
+                                       }
+                               }
+                               found = true
+                               break
+                       }
+               }
+               if found == false {
+                       _, err := client.CreateRule(rule)
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+
+       // remove old rules
+       for _, r := range existOnes {
+               found := false
+               for _, rule := range rules {
+                       if r.Name == rule.Name {
+                               found = true
+                               break
+                       }
+               }
+               if found == false {
+                       err := client.DeleteRule(r.Name)
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+
+       return nil
+}
+
+// apply policy and rules
+func Mwan3Apply(mwan3Conf *sdewanv1alpha1.Mwan3Conf, sdewan *sdewanv1alpha1.Sdewan) error {
+       reqLogger := log.WithValues("Mwan3Provider", mwan3Conf.Name, "Sdewan", sdewan.Name)
+       openwrtClient := openwrt.NewOpenwrtClient(sdewan.Name, "root", "")
+       mwan3 := openwrt.Mwan3Client{OpenwrtClient: openwrtClient}
+       service := openwrt.ServiceClient{OpenwrtClient: openwrtClient}
+       netMap := NetworkInterfaceMap(sdewan)
+       var policies []openwrt.SdewanPolicy
+       for policyName, members := range mwan3Conf.Spec.Policies {
+               openwrtMembers := make([]openwrt.SdewanMember, len(members.Members))
+               for i, member := range members.Members {
+                       openwrtMembers[i] = openwrt.SdewanMember{
+                               Interface: netMap[member.Network],
+                               Metric:    fmt.Sprintf("%d", member.Metric),
+                               Weight:    fmt.Sprintf("%d", member.Weight),
+                       }
+               }
+               policies = append(policies, openwrt.SdewanPolicy{
+                       Name:    policyName,
+                       Members: openwrtMembers})
+       }
+       existPolicies, err := mwan3.GetPolicies()
+       if err != nil {
+               reqLogger.Error(err, "Failed to fetch existing policies")
+               return err
+       }
+       err = Mwan3ReplacePolicies(policies, existPolicies.Policies, &mwan3)
+       if err != nil {
+               reqLogger.Error(err, "Failed to apply Policies")
+               return err
+       }
+       var rules []openwrt.SdewanRule
+       for ruleName, rule := range mwan3Conf.Spec.Rules {
+               openwrtRule := openwrt.SdewanRule{
+                       Name:     ruleName,
+                       Policy:   rule.UsePolicy,
+                       SrcIp:    rule.SrcIP,
+                       SrcPort:  rule.SrcPort,
+                       DestIp:   rule.DestIP,
+                       DestPort: rule.DestPort,
+                       Proto:    rule.Proto,
+                       Family:   rule.Family,
+                       Sticky:   rule.Sticky,
+                       Timeout:  rule.Timeout,
+               }
+               rules = append(rules, openwrtRule)
+       }
+       existRules, err := mwan3.GetRules()
+       if err != nil {
+               reqLogger.Error(err, "Failed to fetch existing rules")
+               return err
+       }
+       err = Mwan3ReplaceRules(rules, existRules.Rules, &mwan3)
+       if err != nil {
+               reqLogger.Error(err, "Failed to apply rules")
+               return err
+       }
+       _, err = service.ExecuteService("mwan3", "restart")
+       if err != nil {
+               reqLogger.Error(err, "Failed to restart mwan3 service")
+               return err
+       }
+       return nil
+}