From a3b9ed126d481bdd2268feccd2d923d76191fa52 Mon Sep 17 00:00:00 2001 From: nmlc Date: Wed, 25 Nov 2020 07:50:54 +0500 Subject: [PATCH 1/9] [traefik] Api changes & codegen --- hack/update-codegen.sh | 2 +- pkg/apis/flagger/v1beta1/canary.go | 4 + pkg/apis/flagger/v1beta1/provider.go | 1 + .../flagger/v1beta1/zz_generated.deepcopy.go | 5 + pkg/apis/traefik/register.go | 5 + pkg/apis/traefik/v1alpha1/doc.go | 5 + pkg/apis/traefik/v1alpha1/register.go | 36 ++++ pkg/apis/traefik/v1alpha1/types.go | 47 +++++ .../traefik/v1alpha1/zz_generated.deepcopy.go | 143 ++++++++++++++ pkg/client/clientset/versioned/clientset.go | 14 ++ .../versioned/fake/clientset_generated.go | 7 + .../clientset/versioned/fake/register.go | 2 + .../clientset/versioned/scheme/register.go | 2 + .../versioned/typed/traefik/v1alpha1/doc.go | 20 ++ .../typed/traefik/v1alpha1/fake/doc.go | 20 ++ .../v1alpha1/fake/fake_traefik_client.go | 40 ++++ .../v1alpha1/fake/fake_traefikservice.go | 130 +++++++++++++ .../traefik/v1alpha1/generated_expansion.go | 21 +++ .../typed/traefik/v1alpha1/traefik_client.go | 89 +++++++++ .../typed/traefik/v1alpha1/traefikservice.go | 178 ++++++++++++++++++ .../informers/externalversions/factory.go | 6 + .../informers/externalversions/generic.go | 5 + .../externalversions/traefik/interface.go | 46 +++++ .../traefik/v1alpha1/interface.go | 45 +++++ .../traefik/v1alpha1/traefikservice.go | 90 +++++++++ .../traefik/v1alpha1/expansion_generated.go | 27 +++ .../traefik/v1alpha1/traefikservice.go | 94 +++++++++ 27 files changed, 1083 insertions(+), 1 deletion(-) create mode 100644 pkg/apis/traefik/register.go create mode 100644 pkg/apis/traefik/v1alpha1/doc.go create mode 100755 pkg/apis/traefik/v1alpha1/register.go create mode 100644 pkg/apis/traefik/v1alpha1/types.go create mode 100644 pkg/apis/traefik/v1alpha1/zz_generated.deepcopy.go create mode 100644 pkg/client/clientset/versioned/typed/traefik/v1alpha1/doc.go create mode 100644 pkg/client/clientset/versioned/typed/traefik/v1alpha1/fake/doc.go create mode 100644 pkg/client/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefik_client.go create mode 100644 pkg/client/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefikservice.go create mode 100644 pkg/client/clientset/versioned/typed/traefik/v1alpha1/generated_expansion.go create mode 100644 pkg/client/clientset/versioned/typed/traefik/v1alpha1/traefik_client.go create mode 100644 pkg/client/clientset/versioned/typed/traefik/v1alpha1/traefikservice.go create mode 100644 pkg/client/informers/externalversions/traefik/interface.go create mode 100644 pkg/client/informers/externalversions/traefik/v1alpha1/interface.go create mode 100644 pkg/client/informers/externalversions/traefik/v1alpha1/traefikservice.go create mode 100644 pkg/client/listers/traefik/v1alpha1/expansion_generated.go create mode 100644 pkg/client/listers/traefik/v1alpha1/traefikservice.go diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index eaed5c604..5faf79771 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -30,7 +30,7 @@ chmod +x ${CODEGEN_PKG}/generate-groups.sh ${CODEGEN_PKG}/generate-groups.sh all \ github.com/weaveworks/flagger/pkg/client github.com/weaveworks/flagger/pkg/apis \ - "flagger:v1beta1 appmesh:v1beta2 appmesh:v1beta1 istio:v1alpha3 smi:v1alpha1 smi:v1alpha2 gloo:v1 projectcontour:v1" \ + "flagger:v1beta1 appmesh:v1beta2 appmesh:v1beta1 istio:v1alpha3 smi:v1alpha1 smi:v1alpha2 gloo:v1 projectcontour:v1 traefik:v1alpha1" \ --output-base "${TEMP_DIR}" \ --go-header-file ${SCRIPT_ROOT}/hack/boilerplate.go.txt diff --git a/pkg/apis/flagger/v1beta1/canary.go b/pkg/apis/flagger/v1beta1/canary.go index 38b90761f..fca2e54a4 100644 --- a/pkg/apis/flagger/v1beta1/canary.go +++ b/pkg/apis/flagger/v1beta1/canary.go @@ -175,6 +175,10 @@ type CanaryService struct { // +optional Backends []string `json:"backends,omitempty"` + // TraefikService is metadata to add to the traefik service + // +optional + TraefikService *CustomMetadata `json:"traefikService,omitempty"` + // Apex is metadata to add to the apex service // +optional Apex *CustomMetadata `json:"apex,omitempty"` diff --git a/pkg/apis/flagger/v1beta1/provider.go b/pkg/apis/flagger/v1beta1/provider.go index a27bbc885..e81018ed0 100644 --- a/pkg/apis/flagger/v1beta1/provider.go +++ b/pkg/apis/flagger/v1beta1/provider.go @@ -10,4 +10,5 @@ const ( NGINXProvider string = "nginx" KubernetesProvider string = "kubernetes" SkipperProvider string = "skipper" + TraefikProvider string = "traefik" ) diff --git a/pkg/apis/flagger/v1beta1/zz_generated.deepcopy.go b/pkg/apis/flagger/v1beta1/zz_generated.deepcopy.go index 5a8788081..41f9a065b 100644 --- a/pkg/apis/flagger/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/flagger/v1beta1/zz_generated.deepcopy.go @@ -369,6 +369,11 @@ func (in *CanaryService) DeepCopyInto(out *CanaryService) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.TraefikService != nil { + in, out := &in.TraefikService, &out.TraefikService + *out = new(CustomMetadata) + (*in).DeepCopyInto(*out) + } if in.Apex != nil { in, out := &in.Apex, &out.Apex *out = new(CustomMetadata) diff --git a/pkg/apis/traefik/register.go b/pkg/apis/traefik/register.go new file mode 100644 index 000000000..d5c31e73e --- /dev/null +++ b/pkg/apis/traefik/register.go @@ -0,0 +1,5 @@ +package traefik + +const ( + GroupName = "traefik.containo.us" +) diff --git a/pkg/apis/traefik/v1alpha1/doc.go b/pkg/apis/traefik/v1alpha1/doc.go new file mode 100644 index 000000000..71b5219b4 --- /dev/null +++ b/pkg/apis/traefik/v1alpha1/doc.go @@ -0,0 +1,5 @@ +// +k8s:deepcopy-gen=package + +// Package v1alpha1 is the v1alpha1 version of the API. +// +groupName=traefik.containo.us +package v1alpha1 diff --git a/pkg/apis/traefik/v1alpha1/register.go b/pkg/apis/traefik/v1alpha1/register.go new file mode 100755 index 000000000..19604fec9 --- /dev/null +++ b/pkg/apis/traefik/v1alpha1/register.go @@ -0,0 +1,36 @@ +package v1alpha1 + +import ( + "github.com/weaveworks/flagger/pkg/apis/traefik" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: traefik.GroupName, Version: "v1alpha1"} + +// Kind takes an unqualified kind and returns back a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// Adds the list of known types to Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &TraefikService{}, + &TraefikServiceList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/pkg/apis/traefik/v1alpha1/types.go b/pkg/apis/traefik/v1alpha1/types.go new file mode 100644 index 000000000..2794d7bce --- /dev/null +++ b/pkg/apis/traefik/v1alpha1/types.go @@ -0,0 +1,47 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// TraefikService is the specification for a service (that an IngressRoute refers +// to) that is usually not a terminal service (i.e. not a pod of servers), as +// opposed to a Kubernetes Service. That is to say, it usually refers to other +// (children) services, which themselves can be TraefikServices or Services. +type TraefikService struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + + Spec ServiceSpec `json:"spec"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// TraefikServiceList is a list of TraefikService resources. +type TraefikServiceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []TraefikService `json:"items"` +} + +// ServiceSpec defines whether a TraefikService is a load-balancer of services or a +// mirroring service. +type ServiceSpec struct { + Weighted *WeightedRoundRobin `json:"weighted,omitempty"` +} + +// WeightedRoundRobin defines a load-balancer of services. +type WeightedRoundRobin struct { + Services []Service `json:"services,omitempty"` +} + +type Service struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + Port int32 `json:"port"` + Weight uint `json:"weight,omitempty"` +} diff --git a/pkg/apis/traefik/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/traefik/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 000000000..7b07d795f --- /dev/null +++ b/pkg/apis/traefik/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,143 @@ +// +build !ignore_autogenerated + +/* +Copyright The Flagger Authors. + +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 deepcopy-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Service) DeepCopyInto(out *Service) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Service. +func (in *Service) DeepCopy() *Service { + if in == nil { + return nil + } + out := new(Service) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) { + *out = *in + if in.Weighted != nil { + in, out := &in.Weighted, &out.Weighted + *out = new(WeightedRoundRobin) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceSpec. +func (in *ServiceSpec) DeepCopy() *ServiceSpec { + if in == nil { + return nil + } + out := new(ServiceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TraefikService) DeepCopyInto(out *TraefikService) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TraefikService. +func (in *TraefikService) DeepCopy() *TraefikService { + if in == nil { + return nil + } + out := new(TraefikService) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TraefikService) 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 *TraefikServiceList) DeepCopyInto(out *TraefikServiceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TraefikService, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TraefikServiceList. +func (in *TraefikServiceList) DeepCopy() *TraefikServiceList { + if in == nil { + return nil + } + out := new(TraefikServiceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TraefikServiceList) 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 *WeightedRoundRobin) DeepCopyInto(out *WeightedRoundRobin) { + *out = *in + if in.Services != nil { + in, out := &in.Services, &out.Services + *out = make([]Service, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WeightedRoundRobin. +func (in *WeightedRoundRobin) DeepCopy() *WeightedRoundRobin { + if in == nil { + return nil + } + out := new(WeightedRoundRobin) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go index fc0b22c30..81853527b 100644 --- a/pkg/client/clientset/versioned/clientset.go +++ b/pkg/client/clientset/versioned/clientset.go @@ -29,6 +29,7 @@ import ( projectcontourv1 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/projectcontour/v1" splitv1alpha1 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/smi/v1alpha1" splitv1alpha2 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/smi/v1alpha2" + traefikv1alpha1 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/traefik/v1alpha1" discovery "k8s.io/client-go/discovery" rest "k8s.io/client-go/rest" flowcontrol "k8s.io/client-go/util/flowcontrol" @@ -44,6 +45,7 @@ type Interface interface { ProjectcontourV1() projectcontourv1.ProjectcontourV1Interface SplitV1alpha1() splitv1alpha1.SplitV1alpha1Interface SplitV1alpha2() splitv1alpha2.SplitV1alpha2Interface + TraefikV1alpha1() traefikv1alpha1.TraefikV1alpha1Interface } // Clientset contains the clients for groups. Each group has exactly one @@ -58,6 +60,7 @@ type Clientset struct { projectcontourV1 *projectcontourv1.ProjectcontourV1Client splitV1alpha1 *splitv1alpha1.SplitV1alpha1Client splitV1alpha2 *splitv1alpha2.SplitV1alpha2Client + traefikV1alpha1 *traefikv1alpha1.TraefikV1alpha1Client } // AppmeshV1beta2 retrieves the AppmeshV1beta2Client @@ -100,6 +103,11 @@ func (c *Clientset) SplitV1alpha2() splitv1alpha2.SplitV1alpha2Interface { return c.splitV1alpha2 } +// TraefikV1alpha1 retrieves the TraefikV1alpha1Client +func (c *Clientset) TraefikV1alpha1() traefikv1alpha1.TraefikV1alpha1Interface { + return c.traefikV1alpha1 +} + // Discovery retrieves the DiscoveryClient func (c *Clientset) Discovery() discovery.DiscoveryInterface { if c == nil { @@ -153,6 +161,10 @@ func NewForConfig(c *rest.Config) (*Clientset, error) { if err != nil { return nil, err } + cs.traefikV1alpha1, err = traefikv1alpha1.NewForConfig(&configShallowCopy) + if err != nil { + return nil, err + } cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy) if err != nil { @@ -173,6 +185,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset { cs.projectcontourV1 = projectcontourv1.NewForConfigOrDie(c) cs.splitV1alpha1 = splitv1alpha1.NewForConfigOrDie(c) cs.splitV1alpha2 = splitv1alpha2.NewForConfigOrDie(c) + cs.traefikV1alpha1 = traefikv1alpha1.NewForConfigOrDie(c) cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) return &cs @@ -189,6 +202,7 @@ func New(c rest.Interface) *Clientset { cs.projectcontourV1 = projectcontourv1.New(c) cs.splitV1alpha1 = splitv1alpha1.New(c) cs.splitV1alpha2 = splitv1alpha2.New(c) + cs.traefikV1alpha1 = traefikv1alpha1.New(c) cs.DiscoveryClient = discovery.NewDiscoveryClient(c) return &cs diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go index d68d11428..dcb551b9f 100644 --- a/pkg/client/clientset/versioned/fake/clientset_generated.go +++ b/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -36,6 +36,8 @@ import ( fakesplitv1alpha1 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/smi/v1alpha1/fake" splitv1alpha2 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/smi/v1alpha2" fakesplitv1alpha2 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/smi/v1alpha2/fake" + traefikv1alpha1 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/traefik/v1alpha1" + faketraefikv1alpha1 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/traefik/v1alpha1/fake" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/discovery" @@ -129,3 +131,8 @@ func (c *Clientset) SplitV1alpha1() splitv1alpha1.SplitV1alpha1Interface { func (c *Clientset) SplitV1alpha2() splitv1alpha2.SplitV1alpha2Interface { return &fakesplitv1alpha2.FakeSplitV1alpha2{Fake: &c.Fake} } + +// TraefikV1alpha1 retrieves the TraefikV1alpha1Client +func (c *Clientset) TraefikV1alpha1() traefikv1alpha1.TraefikV1alpha1Interface { + return &faketraefikv1alpha1.FakeTraefikV1alpha1{Fake: &c.Fake} +} diff --git a/pkg/client/clientset/versioned/fake/register.go b/pkg/client/clientset/versioned/fake/register.go index e5ab88c5e..b7feb7ce5 100644 --- a/pkg/client/clientset/versioned/fake/register.go +++ b/pkg/client/clientset/versioned/fake/register.go @@ -27,6 +27,7 @@ import ( projectcontourv1 "github.com/weaveworks/flagger/pkg/apis/projectcontour/v1" splitv1alpha1 "github.com/weaveworks/flagger/pkg/apis/smi/v1alpha1" splitv1alpha2 "github.com/weaveworks/flagger/pkg/apis/smi/v1alpha2" + traefikv1alpha1 "github.com/weaveworks/flagger/pkg/apis/traefik/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" @@ -46,6 +47,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{ projectcontourv1.AddToScheme, splitv1alpha1.AddToScheme, splitv1alpha2.AddToScheme, + traefikv1alpha1.AddToScheme, } // AddToScheme adds all types of this clientset into the given scheme. This allows composition diff --git a/pkg/client/clientset/versioned/scheme/register.go b/pkg/client/clientset/versioned/scheme/register.go index 19b1677cf..3623b9775 100644 --- a/pkg/client/clientset/versioned/scheme/register.go +++ b/pkg/client/clientset/versioned/scheme/register.go @@ -27,6 +27,7 @@ import ( projectcontourv1 "github.com/weaveworks/flagger/pkg/apis/projectcontour/v1" splitv1alpha1 "github.com/weaveworks/flagger/pkg/apis/smi/v1alpha1" splitv1alpha2 "github.com/weaveworks/flagger/pkg/apis/smi/v1alpha2" + traefikv1alpha1 "github.com/weaveworks/flagger/pkg/apis/traefik/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" @@ -46,6 +47,7 @@ var localSchemeBuilder = runtime.SchemeBuilder{ projectcontourv1.AddToScheme, splitv1alpha1.AddToScheme, splitv1alpha2.AddToScheme, + traefikv1alpha1.AddToScheme, } // AddToScheme adds all types of this clientset into the given scheme. This allows composition diff --git a/pkg/client/clientset/versioned/typed/traefik/v1alpha1/doc.go b/pkg/client/clientset/versioned/typed/traefik/v1alpha1/doc.go new file mode 100644 index 000000000..20b3d7fd1 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/traefik/v1alpha1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The Flagger Authors. + +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 client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/pkg/client/clientset/versioned/typed/traefik/v1alpha1/fake/doc.go b/pkg/client/clientset/versioned/typed/traefik/v1alpha1/fake/doc.go new file mode 100644 index 000000000..7a3b19cb7 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/traefik/v1alpha1/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The Flagger Authors. + +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 client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/pkg/client/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefik_client.go b/pkg/client/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefik_client.go new file mode 100644 index 000000000..7b1f12952 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefik_client.go @@ -0,0 +1,40 @@ +/* +Copyright The Flagger Authors. + +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 client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/weaveworks/flagger/pkg/client/clientset/versioned/typed/traefik/v1alpha1" + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" +) + +type FakeTraefikV1alpha1 struct { + *testing.Fake +} + +func (c *FakeTraefikV1alpha1) TraefikServices(namespace string) v1alpha1.TraefikServiceInterface { + return &FakeTraefikServices{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeTraefikV1alpha1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/client/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefikservice.go b/pkg/client/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefikservice.go new file mode 100644 index 000000000..c12862bc8 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/traefik/v1alpha1/fake/fake_traefikservice.go @@ -0,0 +1,130 @@ +/* +Copyright The Flagger Authors. + +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 client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/weaveworks/flagger/pkg/apis/traefik/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeTraefikServices implements TraefikServiceInterface +type FakeTraefikServices struct { + Fake *FakeTraefikV1alpha1 + ns string +} + +var traefikservicesResource = schema.GroupVersionResource{Group: "traefik.containo.us", Version: "v1alpha1", Resource: "traefikservices"} + +var traefikservicesKind = schema.GroupVersionKind{Group: "traefik.containo.us", Version: "v1alpha1", Kind: "TraefikService"} + +// Get takes name of the traefikService, and returns the corresponding traefikService object, and an error if there is any. +func (c *FakeTraefikServices) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TraefikService, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(traefikservicesResource, c.ns, name), &v1alpha1.TraefikService{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TraefikService), err +} + +// List takes label and field selectors, and returns the list of TraefikServices that match those selectors. +func (c *FakeTraefikServices) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.TraefikServiceList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(traefikservicesResource, traefikservicesKind, c.ns, opts), &v1alpha1.TraefikServiceList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.TraefikServiceList{ListMeta: obj.(*v1alpha1.TraefikServiceList).ListMeta} + for _, item := range obj.(*v1alpha1.TraefikServiceList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested traefikServices. +func (c *FakeTraefikServices) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(traefikservicesResource, c.ns, opts)) + +} + +// Create takes the representation of a traefikService and creates it. Returns the server's representation of the traefikService, and an error, if there is any. +func (c *FakeTraefikServices) Create(ctx context.Context, traefikService *v1alpha1.TraefikService, opts v1.CreateOptions) (result *v1alpha1.TraefikService, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(traefikservicesResource, c.ns, traefikService), &v1alpha1.TraefikService{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TraefikService), err +} + +// Update takes the representation of a traefikService and updates it. Returns the server's representation of the traefikService, and an error, if there is any. +func (c *FakeTraefikServices) Update(ctx context.Context, traefikService *v1alpha1.TraefikService, opts v1.UpdateOptions) (result *v1alpha1.TraefikService, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(traefikservicesResource, c.ns, traefikService), &v1alpha1.TraefikService{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TraefikService), err +} + +// Delete takes name of the traefikService and deletes it. Returns an error if one occurs. +func (c *FakeTraefikServices) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(traefikservicesResource, c.ns, name), &v1alpha1.TraefikService{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeTraefikServices) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(traefikservicesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.TraefikServiceList{}) + return err +} + +// Patch applies the patch and returns the patched traefikService. +func (c *FakeTraefikServices) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TraefikService, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(traefikservicesResource, c.ns, name, pt, data, subresources...), &v1alpha1.TraefikService{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.TraefikService), err +} diff --git a/pkg/client/clientset/versioned/typed/traefik/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/traefik/v1alpha1/generated_expansion.go new file mode 100644 index 000000000..9a4fb62ad --- /dev/null +++ b/pkg/client/clientset/versioned/typed/traefik/v1alpha1/generated_expansion.go @@ -0,0 +1,21 @@ +/* +Copyright The Flagger Authors. + +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 client-gen. DO NOT EDIT. + +package v1alpha1 + +type TraefikServiceExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/traefik/v1alpha1/traefik_client.go b/pkg/client/clientset/versioned/typed/traefik/v1alpha1/traefik_client.go new file mode 100644 index 000000000..966bc6de8 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/traefik/v1alpha1/traefik_client.go @@ -0,0 +1,89 @@ +/* +Copyright The Flagger Authors. + +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 client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/weaveworks/flagger/pkg/apis/traefik/v1alpha1" + "github.com/weaveworks/flagger/pkg/client/clientset/versioned/scheme" + rest "k8s.io/client-go/rest" +) + +type TraefikV1alpha1Interface interface { + RESTClient() rest.Interface + TraefikServicesGetter +} + +// TraefikV1alpha1Client is used to interact with features provided by the traefik.containo.us group. +type TraefikV1alpha1Client struct { + restClient rest.Interface +} + +func (c *TraefikV1alpha1Client) TraefikServices(namespace string) TraefikServiceInterface { + return newTraefikServices(c, namespace) +} + +// NewForConfig creates a new TraefikV1alpha1Client for the given config. +func NewForConfig(c *rest.Config) (*TraefikV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &TraefikV1alpha1Client{client}, nil +} + +// NewForConfigOrDie creates a new TraefikV1alpha1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *TraefikV1alpha1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new TraefikV1alpha1Client for the given RESTClient. +func New(c rest.Interface) *TraefikV1alpha1Client { + return &TraefikV1alpha1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1alpha1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *TraefikV1alpha1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/client/clientset/versioned/typed/traefik/v1alpha1/traefikservice.go b/pkg/client/clientset/versioned/typed/traefik/v1alpha1/traefikservice.go new file mode 100644 index 000000000..cfc5a75ae --- /dev/null +++ b/pkg/client/clientset/versioned/typed/traefik/v1alpha1/traefikservice.go @@ -0,0 +1,178 @@ +/* +Copyright The Flagger Authors. + +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 client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/weaveworks/flagger/pkg/apis/traefik/v1alpha1" + scheme "github.com/weaveworks/flagger/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// TraefikServicesGetter has a method to return a TraefikServiceInterface. +// A group's client should implement this interface. +type TraefikServicesGetter interface { + TraefikServices(namespace string) TraefikServiceInterface +} + +// TraefikServiceInterface has methods to work with TraefikService resources. +type TraefikServiceInterface interface { + Create(ctx context.Context, traefikService *v1alpha1.TraefikService, opts v1.CreateOptions) (*v1alpha1.TraefikService, error) + Update(ctx context.Context, traefikService *v1alpha1.TraefikService, opts v1.UpdateOptions) (*v1alpha1.TraefikService, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.TraefikService, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.TraefikServiceList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TraefikService, err error) + TraefikServiceExpansion +} + +// traefikServices implements TraefikServiceInterface +type traefikServices struct { + client rest.Interface + ns string +} + +// newTraefikServices returns a TraefikServices +func newTraefikServices(c *TraefikV1alpha1Client, namespace string) *traefikServices { + return &traefikServices{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the traefikService, and returns the corresponding traefikService object, and an error if there is any. +func (c *traefikServices) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.TraefikService, err error) { + result = &v1alpha1.TraefikService{} + err = c.client.Get(). + Namespace(c.ns). + Resource("traefikservices"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of TraefikServices that match those selectors. +func (c *traefikServices) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.TraefikServiceList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.TraefikServiceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("traefikservices"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested traefikServices. +func (c *traefikServices) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("traefikservices"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a traefikService and creates it. Returns the server's representation of the traefikService, and an error, if there is any. +func (c *traefikServices) Create(ctx context.Context, traefikService *v1alpha1.TraefikService, opts v1.CreateOptions) (result *v1alpha1.TraefikService, err error) { + result = &v1alpha1.TraefikService{} + err = c.client.Post(). + Namespace(c.ns). + Resource("traefikservices"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(traefikService). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a traefikService and updates it. Returns the server's representation of the traefikService, and an error, if there is any. +func (c *traefikServices) Update(ctx context.Context, traefikService *v1alpha1.TraefikService, opts v1.UpdateOptions) (result *v1alpha1.TraefikService, err error) { + result = &v1alpha1.TraefikService{} + err = c.client.Put(). + Namespace(c.ns). + Resource("traefikservices"). + Name(traefikService.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(traefikService). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the traefikService and deletes it. Returns an error if one occurs. +func (c *traefikServices) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("traefikservices"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *traefikServices) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("traefikservices"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched traefikService. +func (c *traefikServices) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.TraefikService, err error) { + result = &v1alpha1.TraefikService{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("traefikservices"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go index 22eddf1cb..91ed5fc81 100644 --- a/pkg/client/informers/externalversions/factory.go +++ b/pkg/client/informers/externalversions/factory.go @@ -31,6 +31,7 @@ import ( istio "github.com/weaveworks/flagger/pkg/client/informers/externalversions/istio" projectcontour "github.com/weaveworks/flagger/pkg/client/informers/externalversions/projectcontour" smi "github.com/weaveworks/flagger/pkg/client/informers/externalversions/smi" + traefik "github.com/weaveworks/flagger/pkg/client/informers/externalversions/traefik" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" @@ -183,6 +184,7 @@ type SharedInformerFactory interface { Networking() istio.Interface Projectcontour() projectcontour.Interface Split() smi.Interface + Traefik() traefik.Interface } func (f *sharedInformerFactory) Appmesh() appmesh.Interface { @@ -208,3 +210,7 @@ func (f *sharedInformerFactory) Projectcontour() projectcontour.Interface { func (f *sharedInformerFactory) Split() smi.Interface { return smi.New(f, f.namespace, f.tweakListOptions) } + +func (f *sharedInformerFactory) Traefik() traefik.Interface { + return traefik.New(f, f.namespace, f.tweakListOptions) +} diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index ceda6adf9..849d78815 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -29,6 +29,7 @@ import ( projectcontourv1 "github.com/weaveworks/flagger/pkg/apis/projectcontour/v1" v1alpha1 "github.com/weaveworks/flagger/pkg/apis/smi/v1alpha1" v1alpha2 "github.com/weaveworks/flagger/pkg/apis/smi/v1alpha2" + traefikv1alpha1 "github.com/weaveworks/flagger/pkg/apis/traefik/v1alpha1" schema "k8s.io/apimachinery/pkg/runtime/schema" cache "k8s.io/client-go/tools/cache" ) @@ -105,6 +106,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case v1alpha2.SchemeGroupVersion.WithResource("trafficsplits"): return &genericInformer{resource: resource.GroupResource(), informer: f.Split().V1alpha2().TrafficSplits().Informer()}, nil + // Group=traefik.containo.us, Version=v1alpha1 + case traefikv1alpha1.SchemeGroupVersion.WithResource("traefikservices"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Traefik().V1alpha1().TraefikServices().Informer()}, nil + } return nil, fmt.Errorf("no informer found for %v", resource) diff --git a/pkg/client/informers/externalversions/traefik/interface.go b/pkg/client/informers/externalversions/traefik/interface.go new file mode 100644 index 000000000..101413351 --- /dev/null +++ b/pkg/client/informers/externalversions/traefik/interface.go @@ -0,0 +1,46 @@ +/* +Copyright The Flagger Authors. + +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 informer-gen. DO NOT EDIT. + +package traefik + +import ( + internalinterfaces "github.com/weaveworks/flagger/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/weaveworks/flagger/pkg/client/informers/externalversions/traefik/v1alpha1" +) + +// Interface provides access to each of this group's versions. +type Interface interface { + // V1alpha1 provides access to shared informers for resources in V1alpha1. + V1alpha1() v1alpha1.Interface +} + +type group struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// V1alpha1 returns a new v1alpha1.Interface. +func (g *group) V1alpha1() v1alpha1.Interface { + return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/pkg/client/informers/externalversions/traefik/v1alpha1/interface.go b/pkg/client/informers/externalversions/traefik/v1alpha1/interface.go new file mode 100644 index 000000000..2be6cd651 --- /dev/null +++ b/pkg/client/informers/externalversions/traefik/v1alpha1/interface.go @@ -0,0 +1,45 @@ +/* +Copyright The Flagger Authors. + +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 informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + internalinterfaces "github.com/weaveworks/flagger/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // TraefikServices returns a TraefikServiceInformer. + TraefikServices() TraefikServiceInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// TraefikServices returns a TraefikServiceInformer. +func (v *version) TraefikServices() TraefikServiceInformer { + return &traefikServiceInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/traefik/v1alpha1/traefikservice.go b/pkg/client/informers/externalversions/traefik/v1alpha1/traefikservice.go new file mode 100644 index 000000000..6a0437326 --- /dev/null +++ b/pkg/client/informers/externalversions/traefik/v1alpha1/traefikservice.go @@ -0,0 +1,90 @@ +/* +Copyright The Flagger Authors. + +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 informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + traefikv1alpha1 "github.com/weaveworks/flagger/pkg/apis/traefik/v1alpha1" + versioned "github.com/weaveworks/flagger/pkg/client/clientset/versioned" + internalinterfaces "github.com/weaveworks/flagger/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/weaveworks/flagger/pkg/client/listers/traefik/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// TraefikServiceInformer provides access to a shared informer and lister for +// TraefikServices. +type TraefikServiceInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.TraefikServiceLister +} + +type traefikServiceInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewTraefikServiceInformer constructs a new informer for TraefikService type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewTraefikServiceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredTraefikServiceInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredTraefikServiceInformer constructs a new informer for TraefikService type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredTraefikServiceInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.TraefikV1alpha1().TraefikServices(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.TraefikV1alpha1().TraefikServices(namespace).Watch(context.TODO(), options) + }, + }, + &traefikv1alpha1.TraefikService{}, + resyncPeriod, + indexers, + ) +} + +func (f *traefikServiceInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredTraefikServiceInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *traefikServiceInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&traefikv1alpha1.TraefikService{}, f.defaultInformer) +} + +func (f *traefikServiceInformer) Lister() v1alpha1.TraefikServiceLister { + return v1alpha1.NewTraefikServiceLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/listers/traefik/v1alpha1/expansion_generated.go b/pkg/client/listers/traefik/v1alpha1/expansion_generated.go new file mode 100644 index 000000000..cb41928e2 --- /dev/null +++ b/pkg/client/listers/traefik/v1alpha1/expansion_generated.go @@ -0,0 +1,27 @@ +/* +Copyright The Flagger Authors. + +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 lister-gen. DO NOT EDIT. + +package v1alpha1 + +// TraefikServiceListerExpansion allows custom methods to be added to +// TraefikServiceLister. +type TraefikServiceListerExpansion interface{} + +// TraefikServiceNamespaceListerExpansion allows custom methods to be added to +// TraefikServiceNamespaceLister. +type TraefikServiceNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/traefik/v1alpha1/traefikservice.go b/pkg/client/listers/traefik/v1alpha1/traefikservice.go new file mode 100644 index 000000000..bf200ce81 --- /dev/null +++ b/pkg/client/listers/traefik/v1alpha1/traefikservice.go @@ -0,0 +1,94 @@ +/* +Copyright The Flagger Authors. + +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 lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/weaveworks/flagger/pkg/apis/traefik/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// TraefikServiceLister helps list TraefikServices. +type TraefikServiceLister interface { + // List lists all TraefikServices in the indexer. + List(selector labels.Selector) (ret []*v1alpha1.TraefikService, err error) + // TraefikServices returns an object that can list and get TraefikServices. + TraefikServices(namespace string) TraefikServiceNamespaceLister + TraefikServiceListerExpansion +} + +// traefikServiceLister implements the TraefikServiceLister interface. +type traefikServiceLister struct { + indexer cache.Indexer +} + +// NewTraefikServiceLister returns a new TraefikServiceLister. +func NewTraefikServiceLister(indexer cache.Indexer) TraefikServiceLister { + return &traefikServiceLister{indexer: indexer} +} + +// List lists all TraefikServices in the indexer. +func (s *traefikServiceLister) List(selector labels.Selector) (ret []*v1alpha1.TraefikService, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.TraefikService)) + }) + return ret, err +} + +// TraefikServices returns an object that can list and get TraefikServices. +func (s *traefikServiceLister) TraefikServices(namespace string) TraefikServiceNamespaceLister { + return traefikServiceNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// TraefikServiceNamespaceLister helps list and get TraefikServices. +type TraefikServiceNamespaceLister interface { + // List lists all TraefikServices in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1alpha1.TraefikService, err error) + // Get retrieves the TraefikService from the indexer for a given namespace and name. + Get(name string) (*v1alpha1.TraefikService, error) + TraefikServiceNamespaceListerExpansion +} + +// traefikServiceNamespaceLister implements the TraefikServiceNamespaceLister +// interface. +type traefikServiceNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all TraefikServices in the indexer for a given namespace. +func (s traefikServiceNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.TraefikService, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.TraefikService)) + }) + return ret, err +} + +// Get retrieves the TraefikService from the indexer for a given namespace and name. +func (s traefikServiceNamespaceLister) Get(name string) (*v1alpha1.TraefikService, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("traefikservice"), name) + } + return obj.(*v1alpha1.TraefikService), nil +} From 2c1d998c435217a1eccc52b30bca55710d7323da Mon Sep 17 00:00:00 2001 From: nmlc Date: Wed, 25 Nov 2020 07:51:49 +0500 Subject: [PATCH 2/9] [traefik] Implement router interface --- pkg/router/factory.go | 5 + pkg/router/traefik.go | 194 +++++++++++++++++++++++++++++++++++++ pkg/router/traefik_test.go | 142 +++++++++++++++++++++++++++ 3 files changed, 341 insertions(+) create mode 100644 pkg/router/traefik.go create mode 100644 pkg/router/traefik_test.go diff --git a/pkg/router/factory.go b/pkg/router/factory.go index cd1835b37..2d85aedfb 100644 --- a/pkg/router/factory.go +++ b/pkg/router/factory.go @@ -128,6 +128,11 @@ func (factory *Factory) MeshRouter(provider string, labelSelector string) Interf logger: factory.logger, kubeClient: factory.kubeClient, } + case provider == flaggerv1.TraefikProvider: + return &TraefikRouter{ + logger: factory.logger, + traefikClient: factory.meshClient, + } case provider == flaggerv1.KubernetesProvider: return &NopRouter{} default: diff --git a/pkg/router/traefik.go b/pkg/router/traefik.go new file mode 100644 index 000000000..7e57316ab --- /dev/null +++ b/pkg/router/traefik.go @@ -0,0 +1,194 @@ +package router + +import ( + "context" + "fmt" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1" + traefikv1alpha1 "github.com/weaveworks/flagger/pkg/apis/traefik/v1alpha1" + clientset "github.com/weaveworks/flagger/pkg/client/clientset/versioned" + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// TraefikRouter is managing Traefik service +type TraefikRouter struct { + traefikClient clientset.Interface + logger *zap.SugaredLogger +} + +// Reconcile creates or updates the Traefik service +func (tr *TraefikRouter) Reconcile(canary *flaggerv1.Canary) error { + apexName, primaryName, canaryName := canary.GetServiceNames() + + newSpec := traefikv1alpha1.ServiceSpec{ + Weighted: &traefikv1alpha1.WeightedRoundRobin{ + Services: []traefikv1alpha1.Service{ + { + Name: primaryName, + Namespace: canary.Namespace, + Port: canary.Spec.Service.Port, + Weight: 100, + }, + }, + }, + } + + traefikService, err := tr.traefikClient.TraefikV1alpha1().TraefikServices(canary.Namespace).Get(context.TODO(), apexName, metav1.GetOptions{}) + if errors.IsNotFound(err) { + tsMetadata := canary.Spec.Service.TraefikService + if tsMetadata == nil { + tsMetadata = &flaggerv1.CustomMetadata{} + } + if tsMetadata.Labels == nil { + tsMetadata.Labels = make(map[string]string) + } + if tsMetadata.Annotations == nil { + tsMetadata.Annotations = make(map[string]string) + } + + traefikService = &traefikv1alpha1.TraefikService{ + ObjectMeta: metav1.ObjectMeta{ + Name: apexName, + Namespace: canary.Namespace, + Labels: tsMetadata.Labels, + Annotations: tsMetadata.Annotations, + OwnerReferences: []metav1.OwnerReference{ + *metav1.NewControllerRef(canary, schema.GroupVersionKind{ + Group: flaggerv1.SchemeGroupVersion.Group, + Version: flaggerv1.SchemeGroupVersion.Version, + Kind: flaggerv1.CanaryKind, + }), + }, + }, + Spec: newSpec, + } + + _, err = tr.traefikClient.TraefikV1alpha1().TraefikServices(canary.Namespace).Create(context.TODO(), traefikService, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("TraefikService %s.%s create error: %w", apexName, canary.Namespace, err) + } + tr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)). + Infof("TraefikService %s.%s created", traefikService.GetName(), canary.Namespace) + return nil + } else if err != nil { + return fmt.Errorf("TraefikService %s.%s get query error: %w", apexName, canary.Namespace, err) + } + + // update TraefikService but keep the original service weights + if traefikService != nil { + if len(traefikService.Spec.Weighted.Services) == 2 { + newSpec.Weighted.Services = append( + newSpec.Weighted.Services, + traefikv1alpha1.Service{ + Name: canaryName, + Namespace: canary.Namespace, + Port: canary.Spec.Service.Port, + Weight: 100, + }, + ) + } + + if diff := cmp.Diff( + newSpec, + traefikService.Spec, + cmpopts.IgnoreFields(traefikv1alpha1.Service{}, "Weight"), + ); diff != "" { + + clone := traefikService.DeepCopy() + clone.Spec = newSpec + + _, err = tr.traefikClient.TraefikV1alpha1().TraefikServices(canary.Namespace).Update(context.TODO(), clone, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("TraefikService %s.%s update error: %w", apexName, canary.Namespace, err) + } + tr.logger.With("canary", fmt.Sprintf("%s.%s", canary.Name, canary.Namespace)). + Infof("TraefikService %s.%s updated", traefikService.GetName(), canary.Namespace) + } + } + + return nil +} + +// GetRoutes returns the destinations weight for primary and canary +func (tr *TraefikRouter) GetRoutes(canary *flaggerv1.Canary) ( + primaryWeight int, + canaryWeight int, + mirrored bool, + err error, +) { + apexName, primaryName, _ := canary.GetServiceNames() + + traefikService, err := tr.traefikClient.TraefikV1alpha1().TraefikServices(canary.Namespace).Get(context.TODO(), apexName, metav1.GetOptions{}) + if err != nil { + err = fmt.Errorf("TraefikService %s.%s query error: %w", apexName, canary.Namespace, err) + return + } + + if len(traefikService.Spec.Weighted.Services) < 1 { + err = fmt.Errorf("TraefikService %s.%s services not found", apexName, canary.Namespace) + return + } + + for _, s := range traefikService.Spec.Weighted.Services { + if s.Name == primaryName { + primaryWeight = int(s.Weight) + canaryWeight = 100 - primaryWeight + return + + } + } + + return +} + +// SetRoutes updates the destinations weight for primary and canary +func (tr *TraefikRouter) SetRoutes( + canary *flaggerv1.Canary, + primaryWeight int, + canaryWeight int, + _ bool, +) error { + apexName, primaryName, canaryName := canary.GetServiceNames() + + if primaryWeight == 0 && canaryWeight == 0 { + return fmt.Errorf("RoutingRule %s.%s update failed: no valid weights", apexName, canary.Namespace) + } + traefikService, err := tr.traefikClient.TraefikV1alpha1().TraefikServices(canary.Namespace).Get(context.TODO(), apexName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("TraefikService %s.%s query error: %w", apexName, canary.Namespace, err) + } + + services := []traefikv1alpha1.Service{ + { + Name: primaryName, + Namespace: canary.Namespace, + Port: canary.Spec.Service.Port, + Weight: uint(primaryWeight), + }, + } + if canaryWeight > 0 { + services = append(services, traefikv1alpha1.Service{ + Name: canaryName, + Namespace: canary.Namespace, + Port: canary.Spec.Service.Port, + Weight: uint(canaryWeight), + }) + } + + traefikService.Spec.Weighted.Services = services + + _, err = tr.traefikClient.TraefikV1alpha1().TraefikServices(canary.Namespace).Update(context.TODO(), traefikService, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("TraefikService %s.%s update error: %w", apexName, canary.Namespace, err) + } + return nil +} + +func (tr *TraefikRouter) Finalize(_ *flaggerv1.Canary) error { + return nil +} diff --git a/pkg/router/traefik_test.go b/pkg/router/traefik_test.go new file mode 100644 index 000000000..e4c84dbc3 --- /dev/null +++ b/pkg/router/traefik_test.go @@ -0,0 +1,142 @@ +package router + +import ( + "context" + "testing" + + flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestTraefikRouter_Reconcile(t *testing.T) { + mocks := newFixture(nil) + mocks.canary.Spec.Service.TraefikService = &flaggerv1.CustomMetadata{ + Labels: map[string]string{ + "test": "label", + }, + Annotations: map[string]string{ + "test": "annotation", + }, + } + + router := &TraefikRouter{ + traefikClient: mocks.meshClient, + logger: mocks.logger, + } + + assert.NoError(t, router.Reconcile(mocks.canary)) + ts, err := router.traefikClient.TraefikV1alpha1().TraefikServices("default").Get(context.TODO(), "podinfo", metav1.GetOptions{}) + assert.NoError(t, err) + + services := ts.Spec.Weighted.Services + assert.Len(t, services, 1) + assert.Equal(t, uint(100), services[0].Weight) + + assert.Equal(t, ts.ObjectMeta.Labels, mocks.canary.Spec.Service.TraefikService.Labels) + assert.Equal(t, ts.ObjectMeta.Annotations, mocks.canary.Spec.Service.TraefikService.Annotations) + + for _, tt := range []struct { + name string + primary int + canary int + servicesLen int + }{ + { + name: "should not change weights when canary is progressing", + primary: 60, + canary: 40, + servicesLen: 2, + }, + { + name: "should not change weights when canary isn't progressing", + primary: 100, + canary: 0, + servicesLen: 1, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert.NoError(t, router.Reconcile(mocks.canary)) + assert.NoError(t, router.SetRoutes(mocks.canary, tt.primary, tt.canary, false)) + assert.NoError(t, router.Reconcile(mocks.canary)) + + ts, err := router.traefikClient.TraefikV1alpha1().TraefikServices("default").Get(context.TODO(), "podinfo", metav1.GetOptions{}) + assert.NoError(t, err) + + services := ts.Spec.Weighted.Services + assert.Len(t, services, tt.servicesLen) + assert.Equal(t, uint(tt.primary), services[0].Weight) + if tt.canary > 0 { + assert.Equal(t, uint(tt.canary), services[1].Weight) + } + }) + + } +} + +func TestTraefikRouter_SetRoutes(t *testing.T) { + mocks := newFixture(nil) + router := &TraefikRouter{ + traefikClient: mocks.meshClient, + logger: mocks.logger, + } + + err := router.Reconcile(mocks.canary) + require.NoError(t, err) + + _, _, _, err = router.GetRoutes(mocks.canary) + require.NoError(t, err) + + for _, tt := range []struct { + name string + primary int + canary int + servicesLen int + }{ + {name: "0%", primary: 100, canary: 0, servicesLen: 1}, + {name: "20%", primary: 80, canary: 20, servicesLen: 2}, + {name: "40%", primary: 60, canary: 40, servicesLen: 2}, + {name: "60%", primary: 40, canary: 60, servicesLen: 2}, + {name: "80%", primary: 20, canary: 80, servicesLen: 2}, + {name: "100%", primary: 0, canary: 100, servicesLen: 2}, + {name: "0% (promote)", primary: 100, canary: 0, servicesLen: 1}, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + err = router.SetRoutes(mocks.canary, tt.primary, tt.canary, false) + require.NoError(t, err) + + ts, err := router.traefikClient.TraefikV1alpha1().TraefikServices("default").Get(context.TODO(), "podinfo", metav1.GetOptions{}) + assert.NoError(t, err) + + services := ts.Spec.Weighted.Services + assert.Len(t, services, tt.servicesLen) + assert.Equal(t, uint(tt.primary), services[0].Weight) + if tt.canary > 0 { + assert.Equal(t, uint(tt.canary), services[1].Weight) + } + + }) + } +} + +func TestTraefikRouter_GetRoutes(t *testing.T) { + mocks := newFixture(nil) + router := &TraefikRouter{ + traefikClient: mocks.meshClient, + logger: mocks.logger, + } + + err := router.Reconcile(mocks.canary) + require.NoError(t, err) + + p, c, m, err := router.GetRoutes(mocks.canary) + require.NoError(t, err) + + assert.Equal(t, 100, p) + assert.Equal(t, 0, c) + assert.False(t, m) +} From 642d3678ec4701b0d642748f4e8cafc62cd5efbb Mon Sep 17 00:00:00 2001 From: nmlc Date: Wed, 25 Nov 2020 07:52:13 +0500 Subject: [PATCH 3/9] [traefik] Implement observer interface --- pkg/metrics/observers/factory.go | 4 + pkg/metrics/observers/traefik.go | 76 +++++++++++++++++++ pkg/metrics/observers/traefik_test.go | 102 ++++++++++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 pkg/metrics/observers/traefik.go create mode 100644 pkg/metrics/observers/traefik_test.go diff --git a/pkg/metrics/observers/factory.go b/pkg/metrics/observers/factory.go index ac20884dc..42a47c9c0 100644 --- a/pkg/metrics/observers/factory.go +++ b/pkg/metrics/observers/factory.go @@ -60,6 +60,10 @@ func (factory Factory) Observer(provider string) Interface { return &SkipperObserver{ client: factory.Client, } + case provider == flaggerv1.TraefikProvider: + return &TraefikObserver{ + client: factory.Client, + } default: return &IstioObserver{ client: factory.Client, diff --git a/pkg/metrics/observers/traefik.go b/pkg/metrics/observers/traefik.go new file mode 100644 index 000000000..4cba7fca0 --- /dev/null +++ b/pkg/metrics/observers/traefik.go @@ -0,0 +1,76 @@ +package observers + +import ( + "fmt" + "time" + + flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1" + "github.com/weaveworks/flagger/pkg/metrics/providers" +) + +var traefikQueries = map[string]string{ + "request-success-rate": ` + sum( + rate( + traefik_service_request_duration_seconds_bucket{ + service=~"{{ namespace }}-{{ target }}-canary-[0-9a-zA-Z-]+@kubernetescrd", + code!~"5..", + le="+Inf" + }[{{ interval }}] + ) + ) + / + sum( + rate( + traefik_service_request_duration_seconds_bucket{ + service=~"{{ namespace }}-{{ target }}-canary-[0-9a-zA-Z-]+@kubernetescrd", + le="+Inf" + }[{{ interval }}] + ) + ) * 100`, + "request-duration": ` + histogram_quantile( + 0.99, + sum( + rate( + traefik_service_request_duration_seconds_bucket{ + service=~"{{ namespace }}-{{ target }}-canary-[0-9a-zA-Z-]+@kubernetescrd" + }[{{ interval }}] + ) + ) by (le) + )`, +} + +type TraefikObserver struct { + client providers.Interface +} + +func (ob *TraefikObserver) GetRequestSuccessRate(model flaggerv1.MetricTemplateModel) (float64, error) { + + query, err := RenderQuery(traefikQueries["request-success-rate"], model) + if err != nil { + return 0, fmt.Errorf("rendering query failed: %w", err) + } + + value, err := ob.client.RunQuery(query) + if err != nil { + return 0, fmt.Errorf("running query failed: %w", err) + } + + return value, nil +} + +func (ob *TraefikObserver) GetRequestDuration(model flaggerv1.MetricTemplateModel) (time.Duration, error) { + query, err := RenderQuery(traefikQueries["request-duration"], model) + if err != nil { + return 0, fmt.Errorf("rendering query failed: %w", err) + } + + value, err := ob.client.RunQuery(query) + if err != nil { + return 0, fmt.Errorf("running query failed: %w", err) + } + + ms := time.Duration(int64(value)) * time.Millisecond + return ms, nil +} diff --git a/pkg/metrics/observers/traefik_test.go b/pkg/metrics/observers/traefik_test.go new file mode 100644 index 000000000..355a8e2ef --- /dev/null +++ b/pkg/metrics/observers/traefik_test.go @@ -0,0 +1,102 @@ +package observers + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + flaggerv1 "github.com/weaveworks/flagger/pkg/apis/flagger/v1beta1" + "github.com/weaveworks/flagger/pkg/metrics/providers" +) + +func TestTraefikObserver_GetRequestSuccessRate(t *testing.T) { + t.Run("ok", func(t *testing.T) { + expected := ` sum( rate( traefik_service_request_duration_seconds_bucket{ service=~"default-podinfo-canary-[0-9a-zA-Z-]+@kubernetescrd", code!~"5..", le="+Inf" }[1m] ) ) / sum( rate( traefik_service_request_duration_seconds_bucket{ service=~"default-podinfo-canary-[0-9a-zA-Z-]+@kubernetescrd", le="+Inf" }[1m] ) ) * 100` + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + promql := r.URL.Query()["query"][0] + assert.Equal(t, expected, promql) + + json := `{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1,"100"]}]}}` + w.Write([]byte(json)) + })) + defer ts.Close() + + client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{ + Type: "prometheus", + Address: ts.URL, + SecretRef: nil, + }, nil) + require.NoError(t, err) + + observer := &TraefikObserver{client: client} + + val, err := observer.GetRequestSuccessRate(flaggerv1.MetricTemplateModel{ + Name: "podinfo", + Namespace: "default", + Target: "podinfo", + Service: "podinfo", + Interval: "1m", + }) + require.NoError(t, err) + + assert.Equal(t, float64(100), val) + }) + + t.Run("no values", func(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + json := `{"status":"success","data":{"resultType":"vector","result":[]}}` + w.Write([]byte(json)) + })) + defer ts.Close() + + client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{ + Type: "prometheus", + Address: ts.URL, + SecretRef: nil, + }, nil) + require.NoError(t, err) + + observer := &TraefikObserver{client: client} + _, err = observer.GetRequestSuccessRate(flaggerv1.MetricTemplateModel{}) + require.True(t, errors.Is(err, providers.ErrNoValuesFound)) + }) +} + +func TestTraefikObserver_GetRequestDuration(t *testing.T) { + expected := ` histogram_quantile( 0.99, sum( rate( traefik_service_request_duration_seconds_bucket{ service=~"default-podinfo-canary-[0-9a-zA-Z-]+@kubernetescrd" }[1m] ) ) by (le) )` + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + promql := r.URL.Query()["query"][0] + assert.Equal(t, expected, promql) + + json := `{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1,"100"]}]}}` + w.Write([]byte(json)) + })) + defer ts.Close() + + client, err := providers.NewPrometheusProvider(flaggerv1.MetricTemplateProvider{ + Type: "prometheus", + Address: ts.URL, + SecretRef: nil, + }, nil) + require.NoError(t, err) + + observer := &TraefikObserver{client: client} + + val, err := observer.GetRequestDuration(flaggerv1.MetricTemplateModel{ + Name: "podinfo", + Namespace: "default", + Target: "podinfo", + Service: "podinfo", + Interval: "1m", + }) + require.NoError(t, err) + + assert.Equal(t, 100*time.Millisecond, val) +} From 9c4edc602a326692f860cfc10f35d36bfb2021fa Mon Sep 17 00:00:00 2001 From: nmlc Date: Wed, 25 Nov 2020 07:52:45 +0500 Subject: [PATCH 4/9] [traefik] Update chart: crd & rbac --- artifacts/flagger/crd.yaml | 12 ++++++++++++ charts/flagger/crds/crd.yaml | 12 ++++++++++++ charts/flagger/templates/rbac.yaml | 12 ++++++++++++ kustomize/base/flagger/crd.yaml | 12 ++++++++++++ 4 files changed, 48 insertions(+) diff --git a/artifacts/flagger/crd.yaml b/artifacts/flagger/crd.yaml index 1eebd4be3..6d25ab39e 100644 --- a/artifacts/flagger/crd.yaml +++ b/artifacts/flagger/crd.yaml @@ -542,6 +542,18 @@ spec: type: object additionalProperties: type: string + traefikService: + description: Metadata to add to the TraefikService + type: object + properties: + labels: + type: object + additionalProperties: + type: string + annotations: + type: object + additionalProperties: + type: string skipAnalysis: description: Skip analysis and promote canary type: boolean diff --git a/charts/flagger/crds/crd.yaml b/charts/flagger/crds/crd.yaml index 1eebd4be3..6d25ab39e 100644 --- a/charts/flagger/crds/crd.yaml +++ b/charts/flagger/crds/crd.yaml @@ -542,6 +542,18 @@ spec: type: object additionalProperties: type: string + traefikService: + description: Metadata to add to the TraefikService + type: object + properties: + labels: + type: object + additionalProperties: + type: string + annotations: + type: object + additionalProperties: + type: string skipAnalysis: description: Skip analysis and promote canary type: boolean diff --git a/charts/flagger/templates/rbac.yaml b/charts/flagger/templates/rbac.yaml index 506c17838..16a9151f7 100644 --- a/charts/flagger/templates/rbac.yaml +++ b/charts/flagger/templates/rbac.yaml @@ -172,6 +172,18 @@ rules: - update - patch - delete + - apiGroups: + - traefik.containo.us + resources: + - traefikservices + verbs: + - get + - list + - watch + - create + - update + - patch + - delete - nonResourceURLs: - /version verbs: diff --git a/kustomize/base/flagger/crd.yaml b/kustomize/base/flagger/crd.yaml index 1eebd4be3..6d25ab39e 100644 --- a/kustomize/base/flagger/crd.yaml +++ b/kustomize/base/flagger/crd.yaml @@ -542,6 +542,18 @@ spec: type: object additionalProperties: type: string + traefikService: + description: Metadata to add to the TraefikService + type: object + properties: + labels: + type: object + additionalProperties: + type: string + annotations: + type: object + additionalProperties: + type: string skipAnalysis: description: Skip analysis and promote canary type: boolean From adeb585de1548c3411a2eb6c2c78d28ab6a4536e Mon Sep 17 00:00:00 2001 From: nmlc Date: Wed, 25 Nov 2020 07:55:05 +0500 Subject: [PATCH 5/9] [traefik] add e2e test --- test/e2e-traefik-tests.sh | 156 ++++++++++++++++++++++++++++++++++++++ test/e2e-traefik.sh | 37 +++++++++ 2 files changed, 193 insertions(+) create mode 100755 test/e2e-traefik-tests.sh create mode 100755 test/e2e-traefik.sh diff --git a/test/e2e-traefik-tests.sh b/test/e2e-traefik-tests.sh new file mode 100755 index 000000000..e983d6197 --- /dev/null +++ b/test/e2e-traefik-tests.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash + +# This script runs e2e tests for Canary initialization, analysis and promotion +# Prerequisites: Kubernetes Kind, Helm and Traefik ingress controller + +set -o errexit + +REPO_ROOT=$(git rev-parse --show-toplevel) + +echo '>>> Creating test namespace' +kubectl create namespace test + +echo '>>> Installing load tester' +kubectl apply -k ${REPO_ROOT}/kustomize/tester +kubectl -n test rollout status deployment/flagger-loadtester + +echo '>>> Initialising canary' +kubectl apply -f ${REPO_ROOT}/test/e2e-workload.yaml + +cat <>> Waiting for primary to be ready' +retries=50 +count=0 +ok=false +until ${ok}; do + kubectl -n test get canary/podinfo | grep 'Initialized' && ok=true || ok=false + sleep 5 + count=$(($count + 1)) + if [[ ${count} -eq ${retries} ]]; then + kubectl -n traefik logs deployment/flagger + echo "No more retries left" + exit 1 + fi +done + +echo '✔ Canary initialization test passed' + +passed=$(kubectl -n test get svc/podinfo -o jsonpath='{.spec.selector.app}' 2>&1 | { grep podinfo-primary || true; }) +if [ -z "$passed" ]; then + echo -e '\u2716 podinfo selector test failed' + exit 1 +fi +passed=$(kubectl -n test get traefikservice/podinfo -o jsonpath='{.metadata.labels}' 2>&1 | { grep test-label || true; }) +if [ -z "$passed" ]; then + echo -e '\u2716 TraefikService does not have required labels' + exit 1 +fi +passed=$(kubectl -n test get traefikservice/podinfo -o jsonpath='{.metadata.annotations}' 2>&1 | { grep test-annotation || true; }) +if [ -z "$passed" ]; then + echo -e '\u2716 TraefikService does not have required annotations' + exit 1 +fi + +echo '✔ Canary service custom metadata test passed' + +echo '>>> Triggering canary deployment' +kubectl -n test set image deployment/podinfo podinfod=stefanprodan/podinfo:3.1.1 + +echo '>>> Waiting for canary promotion' +retries=60 +count=0 +ok=false +until ${ok}; do + kubectl -n test describe deployment/podinfo-primary | grep '3.1.1' && ok=true || ok=false + sleep 10 + kubectl -n traefik logs deployment/flagger --tail 1 + count=$(($count + 1)) + if [[ ${count} -eq ${retries} ]]; then + kubectl -n test describe deployment/podinfo + kubectl -n test describe deployment/podinfo-primary + kubectl -n test logs deployment/flagger-loadtester + kubectl -n traefik logs deployment/flagger + kubectl -n traefik get all + echo "No more retries left" + exit 1 + fi +done + +echo '✔ Canary promotion test passed' diff --git a/test/e2e-traefik.sh b/test/e2e-traefik.sh new file mode 100755 index 000000000..74b9ec9dd --- /dev/null +++ b/test/e2e-traefik.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +set -o errexit + +REPO_ROOT=$(git rev-parse --show-toplevel) +TRAEFIK_CHART_VERSION="9.11.0" # traefik 2.3.3 + +echo '>>> Creating traefik namespace' +kubectl create ns traefik + +echo '>>> Installing Traefik' +# helm repo add traefik https://helm.traefik.io/traefik +cat <>> Loading Flagger image' +kind load docker-image test/flagger:latest + +echo '>>> Installing Flagger' +helm upgrade -i flagger ${REPO_ROOT}/charts/flagger \ +--set crd.create=false \ +--namespace traefik \ +--set prometheus.install=true \ +--set meshProvider=traefik \ +--set image.repository=test\/flagger \ +--set image.tag=latest \ + +kubectl -n traefik rollout status deployment/flagger From 746507dcc9bf88fcc30f16525dfcff8300d2aa65 Mon Sep 17 00:00:00 2001 From: nmlc Date: Thu, 26 Nov 2020 05:28:38 +0500 Subject: [PATCH 6/9] [traefik] Remove TraefikService metadata from canary spec --- artifacts/flagger/crd.yaml | 12 ------------ charts/flagger/crds/crd.yaml | 12 ------------ kustomize/base/flagger/crd.yaml | 12 ------------ pkg/apis/flagger/v1beta1/canary.go | 4 ---- pkg/apis/flagger/v1beta1/zz_generated.deepcopy.go | 5 ----- pkg/router/traefik.go | 2 +- pkg/router/traefik_test.go | 6 +++--- test/e2e-traefik-tests.sh | 2 +- 8 files changed, 5 insertions(+), 50 deletions(-) diff --git a/artifacts/flagger/crd.yaml b/artifacts/flagger/crd.yaml index 6d25ab39e..1eebd4be3 100644 --- a/artifacts/flagger/crd.yaml +++ b/artifacts/flagger/crd.yaml @@ -542,18 +542,6 @@ spec: type: object additionalProperties: type: string - traefikService: - description: Metadata to add to the TraefikService - type: object - properties: - labels: - type: object - additionalProperties: - type: string - annotations: - type: object - additionalProperties: - type: string skipAnalysis: description: Skip analysis and promote canary type: boolean diff --git a/charts/flagger/crds/crd.yaml b/charts/flagger/crds/crd.yaml index 6d25ab39e..1eebd4be3 100644 --- a/charts/flagger/crds/crd.yaml +++ b/charts/flagger/crds/crd.yaml @@ -542,18 +542,6 @@ spec: type: object additionalProperties: type: string - traefikService: - description: Metadata to add to the TraefikService - type: object - properties: - labels: - type: object - additionalProperties: - type: string - annotations: - type: object - additionalProperties: - type: string skipAnalysis: description: Skip analysis and promote canary type: boolean diff --git a/kustomize/base/flagger/crd.yaml b/kustomize/base/flagger/crd.yaml index 6d25ab39e..1eebd4be3 100644 --- a/kustomize/base/flagger/crd.yaml +++ b/kustomize/base/flagger/crd.yaml @@ -542,18 +542,6 @@ spec: type: object additionalProperties: type: string - traefikService: - description: Metadata to add to the TraefikService - type: object - properties: - labels: - type: object - additionalProperties: - type: string - annotations: - type: object - additionalProperties: - type: string skipAnalysis: description: Skip analysis and promote canary type: boolean diff --git a/pkg/apis/flagger/v1beta1/canary.go b/pkg/apis/flagger/v1beta1/canary.go index fca2e54a4..38b90761f 100644 --- a/pkg/apis/flagger/v1beta1/canary.go +++ b/pkg/apis/flagger/v1beta1/canary.go @@ -175,10 +175,6 @@ type CanaryService struct { // +optional Backends []string `json:"backends,omitempty"` - // TraefikService is metadata to add to the traefik service - // +optional - TraefikService *CustomMetadata `json:"traefikService,omitempty"` - // Apex is metadata to add to the apex service // +optional Apex *CustomMetadata `json:"apex,omitempty"` diff --git a/pkg/apis/flagger/v1beta1/zz_generated.deepcopy.go b/pkg/apis/flagger/v1beta1/zz_generated.deepcopy.go index 41f9a065b..5a8788081 100644 --- a/pkg/apis/flagger/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/flagger/v1beta1/zz_generated.deepcopy.go @@ -369,11 +369,6 @@ func (in *CanaryService) DeepCopyInto(out *CanaryService) { *out = make([]string, len(*in)) copy(*out, *in) } - if in.TraefikService != nil { - in, out := &in.TraefikService, &out.TraefikService - *out = new(CustomMetadata) - (*in).DeepCopyInto(*out) - } if in.Apex != nil { in, out := &in.Apex, &out.Apex *out = new(CustomMetadata) diff --git a/pkg/router/traefik.go b/pkg/router/traefik.go index 7e57316ab..04081e374 100644 --- a/pkg/router/traefik.go +++ b/pkg/router/traefik.go @@ -40,7 +40,7 @@ func (tr *TraefikRouter) Reconcile(canary *flaggerv1.Canary) error { traefikService, err := tr.traefikClient.TraefikV1alpha1().TraefikServices(canary.Namespace).Get(context.TODO(), apexName, metav1.GetOptions{}) if errors.IsNotFound(err) { - tsMetadata := canary.Spec.Service.TraefikService + tsMetadata := canary.Spec.Service.Apex if tsMetadata == nil { tsMetadata = &flaggerv1.CustomMetadata{} } diff --git a/pkg/router/traefik_test.go b/pkg/router/traefik_test.go index e4c84dbc3..7c5cde960 100644 --- a/pkg/router/traefik_test.go +++ b/pkg/router/traefik_test.go @@ -13,7 +13,7 @@ import ( func TestTraefikRouter_Reconcile(t *testing.T) { mocks := newFixture(nil) - mocks.canary.Spec.Service.TraefikService = &flaggerv1.CustomMetadata{ + mocks.canary.Spec.Service.Apex = &flaggerv1.CustomMetadata{ Labels: map[string]string{ "test": "label", }, @@ -35,8 +35,8 @@ func TestTraefikRouter_Reconcile(t *testing.T) { assert.Len(t, services, 1) assert.Equal(t, uint(100), services[0].Weight) - assert.Equal(t, ts.ObjectMeta.Labels, mocks.canary.Spec.Service.TraefikService.Labels) - assert.Equal(t, ts.ObjectMeta.Annotations, mocks.canary.Spec.Service.TraefikService.Annotations) + assert.Equal(t, ts.ObjectMeta.Labels, mocks.canary.Spec.Service.Apex.Labels) + assert.Equal(t, ts.ObjectMeta.Annotations, mocks.canary.Spec.Service.Apex.Annotations) for _, tt := range []struct { name string diff --git a/test/e2e-traefik-tests.sh b/test/e2e-traefik-tests.sh index e983d6197..f927aca7a 100755 --- a/test/e2e-traefik-tests.sh +++ b/test/e2e-traefik-tests.sh @@ -51,7 +51,7 @@ spec: service: port: 80 targetPort: 9898 - traefikService: + apex: labels: test: test-label annotations: From 635bc83259add0ddd5cb08e2cdcb6993e9384d70 Mon Sep 17 00:00:00 2001 From: nmlc Date: Thu, 26 Nov 2020 05:53:09 +0500 Subject: [PATCH 7/9] [traefik] Add CircleCI tests --- .circleci/config.yml | 16 ++++++++++++++++ test/e2e-traefik.sh | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0e2ab4034..39f303530 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -160,6 +160,18 @@ jobs: - run: test/e2e-skipper-tests.sh - run: test/e2e-skipper-cleanup.sh + e2e-traefik-testing: + machine: true + steps: + - checkout + - attach_workspace: + at: /tmp/bin + - run: test/container-build.sh + - run: test/e2e-kind.sh + - run: test/e2e-traefik.sh + - run: test/e2e-traefik-tests.sh + - run: test/e2e-skipper-cleanup.sh + push-helm-charts: docker: - image: circleci/golang:1.14 @@ -232,6 +244,9 @@ workflows: - e2e-skipper-testing: requires: - build-binary + - e2e-traefik-testing: + requires: + - build-binary - push-container: requires: - build-binary @@ -241,6 +256,7 @@ workflows: - e2e-nginx-testing - e2e-linkerd-testing - e2e-skipper-testing + - e2e-traefik-testing filters: branches: only: diff --git a/test/e2e-traefik.sh b/test/e2e-traefik.sh index 74b9ec9dd..5405465cf 100755 --- a/test/e2e-traefik.sh +++ b/test/e2e-traefik.sh @@ -9,7 +9,7 @@ echo '>>> Creating traefik namespace' kubectl create ns traefik echo '>>> Installing Traefik' -# helm repo add traefik https://helm.traefik.io/traefik +helm repo add traefik https://helm.traefik.io/traefik cat < Date: Tue, 1 Dec 2020 05:17:33 +0500 Subject: [PATCH 8/9] [traefik] Add documentation --- .gitbook.yaml | 1 + README.md | 25 +- charts/flagger/README.md | 2 +- cmd/flagger/main.go | 2 +- docs/gitbook/README.md | 3 +- docs/gitbook/SUMMARY.md | 1 + .../install/flagger-install-on-kubernetes.md | 5 +- .../tutorials/traefik-progressive-delivery.md | 375 ++++++++++++++++++ docs/gitbook/usage/deployment-strategies.md | 2 +- test/README.md | 14 + 10 files changed, 412 insertions(+), 18 deletions(-) create mode 100644 docs/gitbook/tutorials/traefik-progressive-delivery.md diff --git a/.gitbook.yaml b/.gitbook.yaml index 1d974eca0..d13970bf7 100644 --- a/.gitbook.yaml +++ b/.gitbook.yaml @@ -12,3 +12,4 @@ redirects: usage/nginx-progressive-delivery: tutorials/nginx-progressive-delivery.md usage/skipper-progressive-delivery: tutorials/skipper-progressive-delivery.md usage/crossover-progressive-delivery: tutorials/crossover-progressive-delivery.md + usage/traefik-progressive-delivery: tutorials/traefik-progressive-delivery.md diff --git a/README.md b/README.md index 100d943a8..8aeafee79 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ by gradually shifting traffic to the new version while measuring metrics and run ![flagger-overview](https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-canary-overview.png) Flagger implements several deployment strategies (Canary releases, A/B testing, Blue/Green mirroring) -using a service mesh (App Mesh, Istio, Linkerd) or an ingress controller (Contour, Gloo, NGINX, Skipper) for traffic routing. +using a service mesh (App Mesh, Istio, Linkerd) or an ingress controller (Contour, Gloo, NGINX, Skipper, Traefik) for traffic routing. For release analysis, Flagger can query Prometheus, Datadog or CloudWatch and for alerting it uses Slack, MS Teams, Discord and Rocket. @@ -38,6 +38,7 @@ Flagger documentation can be found at [docs.flagger.app](https://docs.flagger.ap * [Gloo](https://docs.flagger.app/tutorials/gloo-progressive-delivery) * [NGINX Ingress](https://docs.flagger.app/tutorials/nginx-progressive-delivery) * [Skipper](https://docs.flagger.app/tutorials/skipper-progressive-delivery) + * [Traefik](https://docs.flagger.app/tutorials/traefik-progressive-delivery) * [Kubernetes Blue/Green](https://docs.flagger.app/tutorials/kubernetes-blue-green) ### Who is using Flagger @@ -73,7 +74,7 @@ metadata: namespace: test spec: # service mesh provider (optional) - # can be: kubernetes, istio, linkerd, appmesh, nginx, skipper, contour, gloo, supergloo + # can be: kubernetes, istio, linkerd, appmesh, nginx, skipper, contour, gloo, supergloo, traefik provider: istio # deployment reference targetRef: @@ -198,16 +199,16 @@ For more details on how the canary analysis and promotion works please [read the **Ingress** -| Feature | Contour | Gloo | NGINX | Skipper | -| ------------------------------------------ | ------------------ | ------------------ | ------------------ | ------------------ | -| Canary deployments (weighted traffic) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| A/B testing (headers and cookies routing) | :heavy_check_mark: | :heavy_minus_sign: | :heavy_check_mark: | :heavy_minus_sign: | -| Blue/Green deployments (traffic switch) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Webhooks (acceptance/load testing) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Manual gating (approve/pause/resume) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Request success rate check (L7 metric) | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_check_mark: | -| Request duration check (L7 metric) | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_check_mark: | -| Custom metric checks | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Feature | Contour | Gloo | NGINX | Skipper | Traefik | +| ------------------------------------------ | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | +| Canary deployments (weighted traffic) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| A/B testing (headers and cookies routing) | :heavy_check_mark: | :heavy_minus_sign: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | +| Blue/Green deployments (traffic switch) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Webhooks (acceptance/load testing) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Manual gating (approve/pause/resume) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Request success rate check (L7 metric) | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_check_mark: | :heavy_check_mark: | +| Request duration check (L7 metric) | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_check_mark: | :heavy_check_mark: | +| Custom metric checks | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | ### Roadmap diff --git a/charts/flagger/README.md b/charts/flagger/README.md index 0c684acc1..d7d516311 100644 --- a/charts/flagger/README.md +++ b/charts/flagger/README.md @@ -7,7 +7,7 @@ Flagger can run automated application analysis, testing, promotion and rollback * A/B Testing (HTTP headers and cookies traffic routing) * Blue/Green (traffic switching and mirroring) -Flagger works with service mesh solutions (Istio, Linkerd, AWS App Mesh) and with Kubernetes ingress controllers (NGINX, Skipper, Gloo, Contour). +Flagger works with service mesh solutions (Istio, Linkerd, AWS App Mesh) and with Kubernetes ingress controllers (NGINX, Skipper, Gloo, Contour, Traefik). Flagger can be configured to send alerts to various chat platforms such as Slack, Microsoft Teams, Discord and Rocket. ## Prerequisites diff --git a/cmd/flagger/main.go b/cmd/flagger/main.go index d0c54d77b..933d73a00 100644 --- a/cmd/flagger/main.go +++ b/cmd/flagger/main.go @@ -84,7 +84,7 @@ func init() { flag.BoolVar(&zapReplaceGlobals, "zap-replace-globals", false, "Whether to change the logging level of the global zap logger.") flag.StringVar(&zapEncoding, "zap-encoding", "json", "Zap logger encoding.") flag.StringVar(&namespace, "namespace", "", "Namespace that flagger would watch canary object.") - flag.StringVar(&meshProvider, "mesh-provider", "istio", "Service mesh provider, can be istio, linkerd, appmesh, contour, gloo, nginx or skipper.") + flag.StringVar(&meshProvider, "mesh-provider", "istio", "Service mesh provider, can be istio, linkerd, appmesh, contour, gloo, nginx, skipper or traefik.") flag.StringVar(&selectorLabels, "selector-labels", "app,name,app.kubernetes.io/name", "List of pod labels that Flagger uses to create pod selectors.") flag.StringVar(&ingressAnnotationsPrefix, "ingress-annotations-prefix", "nginx.ingress.kubernetes.io", "Annotations prefix for NGINX ingresses.") flag.StringVar(&ingressClass, "ingress-class", "", "Ingress class used for annotating HTTPProxy objects.") diff --git a/docs/gitbook/README.md b/docs/gitbook/README.md index fae6f42f1..b8c37f5c4 100644 --- a/docs/gitbook/README.md +++ b/docs/gitbook/README.md @@ -5,7 +5,7 @@ description: Flagger is a progressive delivery Kubernetes operator # Introduction [Flagger](https://github.com/weaveworks/flagger) is a **Kubernetes** operator that automates the promotion of -canary deployments using **Istio**, **Linkerd**, **App Mesh**, **NGINX**, **Skipper**, **Contour** or **Gloo** routing for +canary deployments using **Istio**, **Linkerd**, **App Mesh**, **NGINX**, **Skipper**, **Contour**, **Gloo** or **Traefik** routing for traffic shifting and **Prometheus** metrics for canary analysis. The canary analysis can be extended with webhooks for running system integration/acceptance tests, load tests, or any other custom validation. @@ -40,6 +40,7 @@ After install Flagger, you can follow one of the tutorials: * [Gloo](tutorials/gloo-progressive-delivery.md) * [NGINX Ingress](tutorials/nginx-progressive-delivery.md) * [Skipper Ingress](tutorials/skipper-progressive-delivery.md) +* [Traefik](tutorials/traefik-progressive-delivery.md) **Hands-on GitOps workshops** diff --git a/docs/gitbook/SUMMARY.md b/docs/gitbook/SUMMARY.md index 843019843..0fcd3038e 100644 --- a/docs/gitbook/SUMMARY.md +++ b/docs/gitbook/SUMMARY.md @@ -28,6 +28,7 @@ * [Gloo Canary Deployments](tutorials/gloo-progressive-delivery.md) * [NGINX Canary Deployments](tutorials/nginx-progressive-delivery.md) * [Skipper Canary Deployments](tutorials/skipper-progressive-delivery.md) +* [Traefik Canary Deployments](tutorials/traefik-progressive-delivery.md) * [Blue/Green Deployments](tutorials/kubernetes-blue-green.md) * [Crossover Canary Deployments](tutorials/crossover-progressive-delivery.md) * [Canary analysis with Prometheus Operator](tutorials/prometheus-operator.md) diff --git a/docs/gitbook/install/flagger-install-on-kubernetes.md b/docs/gitbook/install/flagger-install-on-kubernetes.md index 07b040d11..40045bccd 100644 --- a/docs/gitbook/install/flagger-install-on-kubernetes.md +++ b/docs/gitbook/install/flagger-install-on-kubernetes.md @@ -78,6 +78,7 @@ For ingress controllers, the install instructions are: * [Gloo](https://docs.flagger.app/tutorials/gloo-progressive-delivery) * [NGINX](https://docs.flagger.app/tutorials/nginx-progressive-delivery) * [Skipper](https://docs.flagger.app/tutorials/skipper-progressive-delivery) +* [Traefik](https://docs.flagger.app/tutorials/traefik-progressive-delivery) Enable **Slack** notifications: @@ -199,7 +200,7 @@ kustomize build https://github.com/weaveworks/flagger/kustomize/linkerd?ref=v1.0 **Generic installer** -Install Flagger and Prometheus for Contour, Gloo, NGINX or Skipper ingress: +Install Flagger and Prometheus for Contour, Gloo, NGINX, Skipper, or Traefik ingress: ```bash kustomize build https://github.com/weaveworks/flagger/kustomize/kubernetes | kubectl apply -f - @@ -220,7 +221,7 @@ metadata: name: app namespace: test spec: - # can be: kubernetes, istio, linkerd, appmesh, nginx, skipper, gloo + # can be: kubernetes, istio, linkerd, appmesh, nginx, skipper, gloo, traefik # use the kubernetes provider for Blue/Green style deployments provider: nginx ``` diff --git a/docs/gitbook/tutorials/traefik-progressive-delivery.md b/docs/gitbook/tutorials/traefik-progressive-delivery.md new file mode 100644 index 000000000..e15bc6ff2 --- /dev/null +++ b/docs/gitbook/tutorials/traefik-progressive-delivery.md @@ -0,0 +1,375 @@ +# Traefik Canary Deployments + +This guide shows you how to use the [Traefik](https://doc.traefik.io/traefik/) and Flagger to automate canary deployments. + +## Prerequisites + +Flagger requires a Kubernetes cluster **v1.14** or newer and Traefik **v1.14** or newer. + +Install Traefik with Helm v3: + +```bash +helm repo add traefik https://helm.traefik.io/traefik +kubectl create ns traefik +helm upgrade -i traefik traefik/traefik \ +--namespace traefik \ +--set additionalArguments="--metrics.prometheus=true" +``` + +Install Flagger and the Prometheus add-on in the same namespace as Traefik: + +```bash +helm repo add flagger https://flagger.app + +helm upgrade -i flagger flagger/flagger \ +--namespace traefik \ +--set prometheus.install=true \ +--set meshProvider=traefik +``` + +## Bootstrap + +Flagger takes a Kubernetes deployment and optionally a horizontal pod autoscaler (HPA), +then creates a series of objects (Kubernetes deployments, ClusterIP services and TraefikService). +These objects expose the application outside the cluster and drive the canary analysis and promotion. + +Create a test namespace: + +```bash +kubectl create ns test +``` + +Create a deployment and a horizontal pod autoscaler: + +```bash +kubectl apply -k github.com/weaveworks/flagger//kustomize/podinfo +``` + +Deploy the load testing service to generate traffic during the canary analysis: + +```bash +helm upgrade -i flagger-loadtester flagger/loadtester \ +--namespace=test +``` + +Create Traefik IngressRoute that references TraefikService generated by Flagger +(replace `app.example.com` with your own domain): + +```yaml +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: podinfo + namespace: test +spec: + entryPoints: + - web + routes: + - match: Host(`app.example.com`) + kind: Rule + services: + - name: podinfo + kind: TraefikService + port: 80 +``` + +Save the above resource as podinfo-ingressroute.yaml and then apply it: + +```bash +kubectl apply -f ./podinfo-ingressroute.yaml +``` + +Create a canary custom resource (replace `app.example.com` with your own domain): + +```yaml +apiVersion: flagger.app/v1beta1 +kind: Canary +metadata: + name: podinfo + namespace: test +spec: + provider: traefik + # deployment reference + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: podinfo + # ingress reference + ingressRef: + apiVersion: networking.k8s.io/v1beta1 + kind: Ingress + name: podinfo + # HPA reference (optional) + autoscalerRef: + apiVersion: autoscaling/v2beta1 + kind: HorizontalPodAutoscaler + name: podinfo + # the maximum time in seconds for the canary deployment + # to make progress before it is rollback (default 600s) + progressDeadlineSeconds: 60 + service: + # ClusterIP port number + port: 80 + # container port number or name + targetPort: 9898 + analysis: + # schedule interval (default 60s) + interval: 10s + # max number of failed metric checks before rollback + threshold: 10 + # max traffic percentage routed to canary + # percentage (0-100) + maxWeight: 50 + # canary increment step + # percentage (0-100) + stepWeight: 5 + # Traefik Prometheus checks + metrics: + - name: request-success-rate + interval: 1m + # minimum req success rate (non 5xx responses) + # percentage (0-100) + thresholdRange: + min: 99 + - name: request-duration + interval: 1m + # maximum req duration P99 + # milliseconds + thresholdRange: + max: 500 + webhooks: + - name: gate + type: confirm-rollout + url: http://flagger-loadtester.test/gate/approve + - name: acceptance-test + type: pre-rollout + url: http://flagger-loadtester.test/ + timeout: 10s + metadata: + type: bash + cmd: "curl -sd 'test' http://podinfo-canary.test/token | grep token" + - name: load-test + type: rollout + url: http://flagger-loadtester.test/ + timeout: 5s + metadata: + type: cmd + cmd: "hey -z 10m -q 10 -c 2 -host app.example.com http://traefik.traefik" + logCmdOutput: "true" +``` + +Save the above resource as podinfo-canary.yaml and then apply it: + +```bash +kubectl apply -f ./podinfo-canary.yaml +``` + +After a couple of seconds Flagger will create the canary objects: + +```bash +# applied +deployment.apps/podinfo +horizontalpodautoscaler.autoscaling/podinfo +canary.flagger.app/podinfo + +# generated +deployment.apps/podinfo-primary +horizontalpodautoscaler.autoscaling/podinfo-primary +service/podinfo +service/podinfo-canary +service/podinfo-primary +traefikservice.traefik.containo.us/podinfo +``` + +## Automated canary promotion + +Flagger implements a control loop that gradually shifts traffic to the canary while measuring +key performance indicators like HTTP requests success rate, requests average duration and pod health. +Based on analysis of the KPIs a canary is promoted or aborted, and the analysis result is published to Slack or MS Teams. + +![Flagger Canary Stages](https://raw.githubusercontent.com/weaveworks/flagger/master/docs/diagrams/flagger-canary-steps.png) + +Trigger a canary deployment by updating the container image: + +```bash +kubectl -n test set image deployment/podinfo \ +podinfod=stefanprodan/podinfo:4.0.6 +``` + +Flagger detects that the deployment revision changed and starts a new rollout: + +```text +kubectl -n test describe canary/podinfo + +Status: + Canary Weight: 0 + Failed Checks: 0 + Phase: Succeeded +Events: + New revision detected! Scaling up podinfo.test + Waiting for podinfo.test rollout to finish: 0 of 1 updated replicas are available + Pre-rollout check acceptance-test passed + Advance podinfo.test canary weight 5 + Advance podinfo.test canary weight 10 + Advance podinfo.test canary weight 15 + Advance podinfo.test canary weight 20 + Advance podinfo.test canary weight 25 + Advance podinfo.test canary weight 30 + Advance podinfo.test canary weight 35 + Advance podinfo.test canary weight 40 + Advance podinfo.test canary weight 45 + Advance podinfo.test canary weight 50 + Copying podinfo.test template spec to podinfo-primary.test + Waiting for podinfo-primary.test rollout to finish: 1 of 2 updated replicas are available + Routing all traffic to primary + Promotion completed! Scaling down podinfo.test +``` + +**Note** that if you apply new changes to the deployment during the canary analysis, Flagger will restart the analysis. + +You can monitor all canaries with: + +```bash +watch kubectl get canaries --all-namespaces + +NAMESPACE NAME STATUS WEIGHT LASTTRANSITIONTIME +test podinfo-2 Progressing 30 2020-08-14T12:32:12Z +test podinfo Succeeded 0 2020-08-14T11:23:88Z +``` + +## Automated rollback + +During the canary analysis you can generate HTTP 500 errors to test if Flagger pauses and rolls back the faulted version. + +Trigger another canary deployment: + +```bash +kubectl -n test set image deployment/podinfo \ +podinfod=stefanprodan/podinfo:4.0.6 +``` + +Exec into the load tester pod with: + +```bash +kubectl -n test exec -it deploy/flagger-loadtester bash +``` + +Generate HTTP 500 errors: + +```bash +hey -z 1m -c 5 -q 5 http://app.example.com/status/500 +``` + +Generate latency: + +```bash +watch -n 1 curl http://app.example.com/delay/1 +``` + +When the number of failed checks reaches the canary analysis threshold, the traffic is routed back to the primary, +the canary is scaled to zero and the rollout is marked as failed. + +```text +kubectl -n traefik logs deploy/flagger -f | jq .msg + +New revision detected! Scaling up podinfo.test +Canary deployment podinfo.test not ready: waiting for rollout to finish: 0 of 1 updated replicas are available +Starting canary analysis for podinfo.test +Pre-rollout check acceptance-test passed +Advance podinfo.test canary weight 5 +Advance podinfo.test canary weight 10 +Advance podinfo.test canary weight 15 +Advance podinfo.test canary weight 20 +Halt podinfo.test advancement success rate 53.42% < 99% +Halt podinfo.test advancement success rate 53.19% < 99% +Halt podinfo.test advancement success rate 48.05% < 99% +Rolling back podinfo.test failed checks threshold reached 3 +Canary failed! Scaling down podinfo.test +``` + +## Custom metrics + +The canary analysis can be extended with Prometheus queries. + +Create a metric template and apply it on the cluster: + +```yaml +apiVersion: flagger.app/v1beta1 +kind: MetricTemplate +metadata: + name: not-found-percentage + namespace: test +spec: + provider: + type: prometheus + address: http://flagger-prometheus.traefik:9090 + query: | + sum( + rate( + traefik_service_request_duration_seconds_bucket{ + service=~"{{ namespace }}-{{ target }}-canary-[0-9a-zA-Z-]+@kubernetescrd", + code!="403", + }[{{ interval }}] + ) + ) + / + sum( + rate( + traefik_service_request_duration_seconds_bucket{ + service=~"{{ namespace }}-{{ target }}-canary-[0-9a-zA-Z-]+@kubernetescrd", + }[{{ interval }}] + ) + ) * 100 +``` + +Edit the canary analysis and add the latency check: + +```yaml + analysis: + metrics: + - name: "404s percentage" + templateRef: + name: not-found-percentage + thresholdRange: + max: 5 + interval: 1m +``` + +The above configuration validates the canary by checking if the HTTP 404 req/sec percentage is +below 5 percent of the total traffic. If the 404s rate reaches the 5% threshold, then the canary fails. + +Trigger a canary deployment by updating the container image: + +```bash +kubectl -n test set image deployment/podinfo \ +podinfod=stefanprodan/podinfo:4.0.6 +``` + +Generate 404s: + +```bash +watch curl http://app.example.com/status/400 +``` + +Watch Flagger logs: + +```text +kubectl -n traefik logs deployment/flagger -f | jq .msg + +Starting canary deployment for podinfo.test +Advance podinfo.test canary weight 5 +Advance podinfo.test canary weight 10 +Advance podinfo.test canary weight 15 +Halt podinfo.test advancement 404s percentage 6.20 > 5 +Halt podinfo.test advancement 404s percentage 6.45 > 5 +Halt podinfo.test advancement 404s percentage 7.60 > 5 +Halt podinfo.test advancement 404s percentage 8.69 > 5 +Halt podinfo.test advancement 404s percentage 9.70 > 5 +Rolling back podinfo.test failed checks threshold reached 5 +Canary failed! Scaling down podinfo.test +``` + +If you have [alerting](../usage/alerting.md) configured, +Flagger will send a notification with the reason why the canary failed. + +For an in-depth look at the analysis process read the [usage docs](../usage/how-it-works.md). diff --git a/docs/gitbook/usage/deployment-strategies.md b/docs/gitbook/usage/deployment-strategies.md index fdbadf1d0..e96a12e67 100644 --- a/docs/gitbook/usage/deployment-strategies.md +++ b/docs/gitbook/usage/deployment-strategies.md @@ -2,7 +2,7 @@ Flagger can run automated application analysis, promotion and rollback for the following deployment strategies: * **Canary Release** (progressive traffic shifting) - * Istio, Linkerd, App Mesh, NGINX, Skipper, Contour, Gloo + * Istio, Linkerd, App Mesh, NGINX, Skipper, Contour, Gloo, Traefik * **A/B Testing** (HTTP headers and cookies traffic routing) * Istio, App Mesh, NGINX, Contour * **Blue/Green** (traffic switching) diff --git a/test/README.md b/test/README.md index cff6cd0ff..e0f7167ea 100644 --- a/test/README.md +++ b/test/README.md @@ -69,3 +69,17 @@ The e2e testing infrastructure is powered by CircleCI and [Kubernetes Kind](http * test the canary initialization [e2e-skipper-tests.sh](e2e-skipper-tests.sh) * test the canary analysis and promotion using weighted traffic and the load testing webhook [e2e-skipper-tests.sh]e2e-skipper-tests.sh) * cleanup test environment [e2e-skipper-cleanup.sh](e2e-skipper-cleanup.sh) + +### CircleCI e2e Traefik workflow + +* install latest stable kubectl [e2e-kind.sh](e2e-kind.sh) +* install Kubernetes Kind [e2e-kind.sh](e2e-kind.sh) +* create local Kubernetes cluster with kind [e2e-kind.sh](e2e-kind.sh) +* install Traeik with Helm [e2e-traefik.sh](e2e-traefik.sh) +* load Flagger image onto the local cluster [e2e-traefik.sh](e2e-traefik.sh) +* install Flagger and Prometheus in the traefik namespace [e2e-traefik.sh](e2e-traefik.sh) +* create a test namespace [e2e-traefik-tests.sh](e2e-traefik-tests.sh) +* deploy the load tester in the test namespace [e2e-traefik-tests.sh](e2e-traefik-tests.sh) +* deploy the demo workload (podinfo) and ingress in the test namespace [e2e-traefik-tests.sh](e2e-traefik-tests.sh) +* test the canary initialization [e2e-traefik-tests.sh](e2e-traefik-tests.sh) +* test the canary analysis and promotion using weighted traffic and the load testing webhook [e2e-traefik-tests.sh]e2e-traefik-tests.sh) From 578361a2b0a410622e291df784f238dd349f107a Mon Sep 17 00:00:00 2001 From: nmlc Date: Wed, 2 Dec 2020 05:22:50 +0500 Subject: [PATCH 9/9] [traefik] Fix documentation --- .../tutorials/traefik-progressive-delivery.md | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/docs/gitbook/tutorials/traefik-progressive-delivery.md b/docs/gitbook/tutorials/traefik-progressive-delivery.md index e15bc6ff2..77d956699 100644 --- a/docs/gitbook/tutorials/traefik-progressive-delivery.md +++ b/docs/gitbook/tutorials/traefik-progressive-delivery.md @@ -4,7 +4,7 @@ This guide shows you how to use the [Traefik](https://doc.traefik.io/traefik/) a ## Prerequisites -Flagger requires a Kubernetes cluster **v1.14** or newer and Traefik **v1.14** or newer. +Flagger requires a Kubernetes cluster **v1.14** or newer and Traefik **v2.3** or newer. Install Traefik with Helm v3: @@ -94,11 +94,6 @@ spec: apiVersion: apps/v1 kind: Deployment name: podinfo - # ingress reference - ingressRef: - apiVersion: networking.k8s.io/v1beta1 - kind: Ingress - name: podinfo # HPA reference (optional) autoscalerRef: apiVersion: autoscaling/v2beta1 @@ -138,9 +133,6 @@ spec: thresholdRange: max: 500 webhooks: - - name: gate - type: confirm-rollout - url: http://flagger-loadtester.test/gate/approve - name: acceptance-test type: pre-rollout url: http://flagger-loadtester.test/ @@ -308,7 +300,7 @@ spec: rate( traefik_service_request_duration_seconds_bucket{ service=~"{{ namespace }}-{{ target }}-canary-[0-9a-zA-Z-]+@kubernetescrd", - code!="403", + code!="404", }[{{ interval }}] ) ) @@ -322,7 +314,7 @@ spec: ) * 100 ``` -Edit the canary analysis and add the latency check: +Edit the canary analysis and add the not found error rate check: ```yaml analysis: