Merge pull request #18 from stefanprodan/webhooks

Add external checks to canary analysis
This commit is contained in:
Stefan Prodan
2019-01-04 13:24:49 +02:00
committed by GitHub
45 changed files with 679 additions and 207 deletions
+1 -1
View File
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2018 Weaveworks. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+30 -5
View File
@@ -73,7 +73,7 @@ You can change the canary analysis _max weight_ and the _step weight_ percentage
For a deployment named _podinfo_, a canary promotion can be defined using Flagger's custom resource:
```yaml
apiVersion: flagger.app/v1alpha1
apiVersion: flagger.app/v1alpha2
kind: Canary
metadata:
name: podinfo
@@ -87,7 +87,7 @@ spec:
# the maximum time in seconds for the canary deployment
# to make progress before it is rollback (default 600s)
progressDeadlineSeconds: 60
# hpa reference (optional)
# HPA reference (optional)
autoscalerRef:
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
@@ -100,16 +100,17 @@ spec:
- public-gateway.istio-system.svc.cluster.local
# Istio virtual service host names (optional)
hosts:
- app.istio.weavedx.com
- app.iowa.weavedx.com
canaryAnalysis:
# max number of failed metric checks before rollback
threshold: 5
threshold: 10
# max traffic percentage routed to canary
# percentage (0-100)
maxWeight: 50
# canary increment step
# percentage (0-100)
stepWeight: 10
stepWeight: 5
# Istio Prometheus checks
metrics:
- name: istio_requests_total
# minimum req success rate (non 5xx responses)
@@ -121,6 +122,14 @@ spec:
# milliseconds
threshold: 500
interval: 30s
# external checks (optional)
webhooks:
- name: integration-tests
url: http://podinfo.test:9898/echo
timeout: 1m
metadata:
test: "all"
token: "16688eb5e9f289f1991c"
```
The canary analysis is using the following promql queries:
@@ -166,6 +175,22 @@ histogram_quantile(0.99,
)
```
The canary analysis can be extended with webhooks.
Flagger would call a URL (HTTP POST) and determine from the response status code (HTTP 2xx) if the canary is failing or not.
Webhook payload:
```json
{
"name": "podinfo",
"namespace": "test",
"metadata": {
"test": "all",
"token": "16688eb5e9f289f1991c"
}
}
```
### Automated canary analysis, promotions and rollbacks
Create a test namespace with Istio sidecar injection enabled:
+10 -1
View File
@@ -1,4 +1,4 @@
apiVersion: flagger.app/v1alpha1
apiVersion: flagger.app/v1alpha2
kind: Canary
metadata:
name: podinfo
@@ -35,6 +35,7 @@ spec:
# canary increment step
# percentage (0-100)
stepWeight: 5
# Istio Prometheus checks
metrics:
- name: istio_requests_total
# minimum req success rate (non 5xx responses)
@@ -46,3 +47,11 @@ spec:
# milliseconds
threshold: 500
interval: 30s
# external checks (optional)
webhooks:
- name: integration-tests
url: http://podinfo.test:9898/echo
timeout: 1m
metadata:
test: "all"
token: "16688eb5e9f289f1991c"
+27 -2
View File
@@ -4,11 +4,14 @@ metadata:
name: canaries.flagger.app
spec:
group: flagger.app
version: v1alpha1
version: v1alpha2
versions:
- name: v1alpha1
- name: v1alpha2
served: true
storage: true
- name: v1alpha1
served: true
storage: false
names:
plural: canaries
singular: canary
@@ -26,6 +29,8 @@ spec:
progressDeadlineSeconds:
type: number
targetRef:
type: object
required: ['apiVersion', 'kind', 'name']
properties:
apiVersion:
type: string
@@ -34,6 +39,8 @@ spec:
name:
type: string
autoscalerRef:
type: object
required: ['apiVersion', 'kind', 'name']
properties:
apiVersion:
type: string
@@ -42,6 +49,8 @@ spec:
name:
type: string
service:
type: object
required: ['port']
properties:
port:
type: number
@@ -58,6 +67,7 @@ spec:
properties:
items:
type: object
required: ['name', 'interval', 'threshold']
properties:
name:
type: string
@@ -66,3 +76,18 @@ spec:
pattern: "^[0-9]+(m)"
threshold:
type: number
webhooks:
type: array
properties:
items:
type: object
required: ['name', 'url', 'timeout']
properties:
name:
type: string
url:
type: string
format: url
timeout:
type: string
pattern: "^[0-9]+(s)"
+1 -1
View File
@@ -22,7 +22,7 @@ spec:
serviceAccountName: flagger
containers:
- name: flagger
image: quay.io/stefanprodan/flagger:0.1.2
image: quay.io/stefanprodan/flagger:0.2.0-alpha.1
imagePullPolicy: Always
ports:
- name: http
+1 -1
View File
@@ -1,7 +1,7 @@
apiVersion: v1
name: flagger
version: 0.1.2
appVersion: 0.1.2
appVersion: 0.2.0-alpha.1
kubeVersion: ">=1.9.0-0"
engine: gotpl
description: Flagger is a Kubernetes operator that automates the promotion of canary deployments using Istio routing for traffic shifting and Prometheus metrics for canary analysis.
+31 -7
View File
@@ -3,15 +3,16 @@ apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: canaries.flagger.app
annotations:
"helm.sh/resource-policy": keep
spec:
group: flagger.app
version: v1alpha1
version: v1alpha2
versions:
- name: v1alpha1
- name: v1alpha2
served: true
storage: true
- name: v1alpha1
served: true
storage: false
names:
plural: canaries
singular: canary
@@ -22,13 +23,15 @@ spec:
properties:
spec:
required:
- targetRef
- service
- canaryAnalysis
- targetRef
- service
- canaryAnalysis
properties:
progressDeadlineSeconds:
type: number
targetRef:
type: object
required: ['apiVersion', 'kind', 'name']
properties:
apiVersion:
type: string
@@ -37,6 +40,8 @@ spec:
name:
type: string
autoscalerRef:
type: object
required: ['apiVersion', 'kind', 'name']
properties:
apiVersion:
type: string
@@ -45,6 +50,8 @@ spec:
name:
type: string
service:
type: object
required: ['port']
properties:
port:
type: number
@@ -61,6 +68,7 @@ spec:
properties:
items:
type: object
required: ['name', 'interval', 'threshold']
properties:
name:
type: string
@@ -69,4 +77,20 @@ spec:
pattern: "^[0-9]+(m)"
threshold:
type: number
webhooks:
type: array
properties:
items:
type: object
required: ['name', 'url', 'timeout']
properties:
name:
type: string
url:
type: string
format: url
timeout:
type: string
pattern: "^[0-9]+(s)"
{{- end }}
+1 -1
View File
@@ -2,7 +2,7 @@
image:
repository: quay.io/stefanprodan/flagger
tag: 0.1.2
tag: 0.2.0-alpha.1
pullPolicy: IfNotPresent
controlLoopInterval: "10s"
+1 -1
View File
@@ -77,7 +77,7 @@ func main() {
}
flaggerInformerFactory := informers.NewSharedInformerFactory(flaggerClient, time.Second*30)
canaryInformer := flaggerInformerFactory.Flagger().V1alpha1().Canaries()
canaryInformer := flaggerInformerFactory.Flagger().V1alpha2().Canaries()
logger.Infof("Starting flagger version %s revision %s", version.VERSION, version.REVISION)
+111 -8
View File
@@ -9,7 +9,7 @@
For a deployment named _podinfo_, a canary promotion can be defined using Flagger's custom resource:
```yaml
apiVersion: flagger.app/v1alpha1
apiVersion: flagger.app/v1alpha2
kind: Canary
metadata:
name: podinfo
@@ -23,7 +23,7 @@ spec:
# the maximum time in seconds for the canary deployment
# to make progress before it is rollback (default 600s)
progressDeadlineSeconds: 60
# hpa reference (optional)
# HPA reference (optional)
autoscalerRef:
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
@@ -36,16 +36,17 @@ spec:
- public-gateway.istio-system.svc.cluster.local
# Istio virtual service host names (optional)
hosts:
- app.istio.weavedx.com
- app.iowa.weavedx.com
canaryAnalysis:
# max number of failed metric checks before rollback
threshold: 5
threshold: 10
# max traffic percentage routed to canary
# percentage (0-100)
maxWeight: 50
# canary increment step
# percentage (0-100)
stepWeight: 10
stepWeight: 5
# Istio Prometheus checks
metrics:
- name: istio_requests_total
# minimum req success rate (non 5xx responses)
@@ -57,7 +58,14 @@ spec:
# milliseconds
threshold: 500
interval: 30s
# external checks (optional)
webhooks:
- name: integration-tests
url: http://podinfo.test:9898/echo
timeout: 1m
metadata:
test: "all"
token: "16688eb5e9f289f1991c"
```
### Canary Deployment
@@ -96,12 +104,54 @@ Gated canary promotion stages:
You can change the canary analysis _max weight_ and the _step weight_ percentage in the Flagger's custom resource.
### Canary Analisys
### Canary Analysis
The canary analysis is using the following Prometheus queries:
Spec:
```yaml
canaryAnalysis:
# 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
```
You can determine the minimum time that it takes to validate and promote a canary deployment using this formula:
```
controlLoopInterval * (maxWeight / stepWeight)
```
And the time it takes for a canary to be rollback:
```
controlLoopInterval * threshold
```
### HTTP Metrics
The canary analysis is using the following Prometheus queries:
**HTTP requests success rate percentage**
Spec:
```yaml
canaryAnalysis:
metrics:
- name: istio_requests_total
# minimum req success rate (non 5xx responses)
# percentage (0-100)
threshold: 99
interval: 1m
```
Query:
```javascript
sum(
rate(
@@ -127,6 +177,20 @@ sum(
**HTTP requests milliseconds duration P99**
Spec:
```yaml
canaryAnalysis:
metrics:
- name: istio_request_duration_seconds_bucket
# maximum req duration P99
# milliseconds
threshold: 500
interval: 1m
```
Query:
```javascript
histogram_quantile(0.99,
sum(
@@ -141,3 +205,42 @@ histogram_quantile(0.99,
)
```
### Webhooks
The canary analysis can be extended with webhooks.
Flagger would call a URL (HTTP POST) and determine from the response status code (HTTP 2xx) if the canary is failing or not.
Spec:
```yaml
canaryAnalysis:
webhooks:
- name: integration-tests
url: http://podinfo.test:9898/echo
timeout: 1m
metadata:
test: "all"
token: "16688eb5e9f289f1991c"
```
Webhook payload:
```json
{
"name": "podinfo",
"namespace": "test",
"metadata": {
"test": "all",
"token": "16688eb5e9f289f1991c"
}
}
```
Response status codes:
* 200-202 - advance canary by increasing the traffic weight
* timeout or non-2xx - halt advancement and increment failed checks
On a non-2xx response Flagger will include the response body (if any) in the failed checks log and Kubernetes events.
+2 -2
View File
@@ -333,7 +333,7 @@ kubectl create secret generic cert-manager-credentials \
Create a letsencrypt issuer for CloudDNS \(replace `email@example.com` with a valid email address and `my-gcp-project`with your project ID\):
```yaml
apiVersion: certmanager.k8s.io/v1alpha1
apiVersion: v1alpha2
kind: Issuer
metadata:
name: letsencrypt-prod
@@ -363,7 +363,7 @@ kubectl apply -f ./letsencrypt-issuer.yaml
Create a wildcard certificate \(replace `example.com` with your domain\):
```yaml
apiVersion: certmanager.k8s.io/v1alpha1
apiVersion: v1alpha2
kind: Certificate
metadata:
name: istio-gateway
+1 -1
View File
@@ -20,7 +20,7 @@ kubectl apply -f ${REPO}/artifacts/canaries/hpa.yaml
Create a canary custom resource \(replace example.com with your own domain\):
```yaml
apiVersion: flagger.app/v1alpha1
apiVersion: v1alpha2
kind: Canary
metadata:
name: podinfo
+1 -1
View File
@@ -23,6 +23,6 @@ CODEGEN_PKG=${CODEGEN_PKG:-$(cd ${SCRIPT_ROOT}; ls -d -1 ./vendor/k8s.io/code-ge
${CODEGEN_PKG}/generate-groups.sh "deepcopy,client,informer,lister" \
github.com/stefanprodan/flagger/pkg/client github.com/stefanprodan/flagger/pkg/apis \
flagger:v1alpha1 \
flagger:v1alpha2 \
--go-header-file ${SCRIPT_ROOT}/hack/boilerplate.go.txt
@@ -16,6 +16,6 @@ limitations under the License.
// +k8s:deepcopy-gen=package
// Package v1alpha1 is the v1alpha1 version of the API.
// Package v1alpha2 is the v1alpha2 version of the API.
// +groupName=flagger.app
package v1alpha1
package v1alpha2
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
package v1alpha2
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -25,7 +25,7 @@ import (
)
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: rollout.GroupName, Version: "v1alpha1"}
var SchemeGroupVersion = schema.GroupVersion{Group: rollout.GroupName, Version: "v1alpha2"}
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
package v1alpha2
import (
hpav1 "k8s.io/api/autoscaling/v1"
@@ -96,19 +96,37 @@ type CanaryService struct {
// CanaryAnalysis is used to describe how the analysis should be done
type CanaryAnalysis struct {
Threshold int `json:"threshold"`
MaxWeight int `json:"maxWeight"`
StepWeight int `json:"stepWeight"`
Metrics []CanaryMetric `json:"metrics"`
Threshold int `json:"threshold"`
MaxWeight int `json:"maxWeight"`
StepWeight int `json:"stepWeight"`
Metrics []CanaryMetric `json:"metrics"`
Webhooks []CanaryWebhook `json:"webhooks,omitempty"`
}
// CanaryMetric hold the reference to Istio metrics used for canary analysis
// CanaryMetric holds the reference to Istio metrics used for canary analysis
type CanaryMetric struct {
Name string `json:"name"`
Interval string `json:"interval"`
Threshold int `json:"threshold"`
}
// CanaryWebhook holds the reference to external checks used for canary analysis
type CanaryWebhook struct {
Name string `json:"name"`
URL string `json:"url"`
Timeout string `json:"timeout"`
// +optional
Metadata *map[string]string `json:"metadata,omitempty"`
}
// CanaryWebhookPayload holds the deployment info and metadata sent to webhooks
type CanaryWebhookPayload struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Metadata *map[string]string `json:"metadata,omitempty"`
}
// GetProgressDeadlineSeconds returns the progress deadline (default 600s)
func (c *Canary) GetProgressDeadlineSeconds() int {
if c.Spec.ProgressDeadlineSeconds != nil {
return int(*c.Spec.ProgressDeadlineSeconds)
@@ -18,7 +18,7 @@ limitations under the License.
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha1
package v1alpha2
import (
runtime "k8s.io/apimachinery/pkg/runtime"
@@ -60,6 +60,13 @@ func (in *CanaryAnalysis) DeepCopyInto(out *CanaryAnalysis) {
*out = make([]CanaryMetric, len(*in))
copy(*out, *in)
}
if in.Webhooks != nil {
in, out := &in.Webhooks, &out.Webhooks
*out = make([]CanaryWebhook, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
@@ -189,3 +196,57 @@ func (in *CanaryStatus) DeepCopy() *CanaryStatus {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CanaryWebhook) DeepCopyInto(out *CanaryWebhook) {
*out = *in
if in.Metadata != nil {
in, out := &in.Metadata, &out.Metadata
*out = new(map[string]string)
if **in != nil {
in, out := *in, *out
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CanaryWebhook.
func (in *CanaryWebhook) DeepCopy() *CanaryWebhook {
if in == nil {
return nil
}
out := new(CanaryWebhook)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CanaryWebhookPayload) DeepCopyInto(out *CanaryWebhookPayload) {
*out = *in
if in.Metadata != nil {
in, out := &in.Metadata, &out.Metadata
*out = new(map[string]string)
if **in != nil {
in, out := *in, *out
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CanaryWebhookPayload.
func (in *CanaryWebhookPayload) DeepCopy() *CanaryWebhookPayload {
if in == nil {
return nil
}
out := new(CanaryWebhookPayload)
in.DeepCopyInto(out)
return out
}
+12 -12
View File
@@ -19,7 +19,7 @@ limitations under the License.
package versioned
import (
flaggerv1alpha1 "github.com/stefanprodan/flagger/pkg/client/clientset/versioned/typed/flagger/v1alpha1"
flaggerv1alpha2 "github.com/stefanprodan/flagger/pkg/client/clientset/versioned/typed/flagger/v1alpha2"
discovery "k8s.io/client-go/discovery"
rest "k8s.io/client-go/rest"
flowcontrol "k8s.io/client-go/util/flowcontrol"
@@ -27,27 +27,27 @@ import (
type Interface interface {
Discovery() discovery.DiscoveryInterface
FlaggerV1alpha1() flaggerv1alpha1.FlaggerV1alpha1Interface
FlaggerV1alpha2() flaggerv1alpha2.FlaggerV1alpha2Interface
// Deprecated: please explicitly pick a version if possible.
Flagger() flaggerv1alpha1.FlaggerV1alpha1Interface
Flagger() flaggerv1alpha2.FlaggerV1alpha2Interface
}
// Clientset contains the clients for groups. Each group has exactly one
// version included in a Clientset.
type Clientset struct {
*discovery.DiscoveryClient
flaggerV1alpha1 *flaggerv1alpha1.FlaggerV1alpha1Client
flaggerV1alpha2 *flaggerv1alpha2.FlaggerV1alpha2Client
}
// FlaggerV1alpha1 retrieves the FlaggerV1alpha1Client
func (c *Clientset) FlaggerV1alpha1() flaggerv1alpha1.FlaggerV1alpha1Interface {
return c.flaggerV1alpha1
// FlaggerV1alpha2 retrieves the FlaggerV1alpha2Client
func (c *Clientset) FlaggerV1alpha2() flaggerv1alpha2.FlaggerV1alpha2Interface {
return c.flaggerV1alpha2
}
// Deprecated: Flagger retrieves the default version of FlaggerClient.
// Please explicitly pick a version.
func (c *Clientset) Flagger() flaggerv1alpha1.FlaggerV1alpha1Interface {
return c.flaggerV1alpha1
func (c *Clientset) Flagger() flaggerv1alpha2.FlaggerV1alpha2Interface {
return c.flaggerV1alpha2
}
// Discovery retrieves the DiscoveryClient
@@ -66,7 +66,7 @@ func NewForConfig(c *rest.Config) (*Clientset, error) {
}
var cs Clientset
var err error
cs.flaggerV1alpha1, err = flaggerv1alpha1.NewForConfig(&configShallowCopy)
cs.flaggerV1alpha2, err = flaggerv1alpha2.NewForConfig(&configShallowCopy)
if err != nil {
return nil, err
}
@@ -82,7 +82,7 @@ func NewForConfig(c *rest.Config) (*Clientset, error) {
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *Clientset {
var cs Clientset
cs.flaggerV1alpha1 = flaggerv1alpha1.NewForConfigOrDie(c)
cs.flaggerV1alpha2 = flaggerv1alpha2.NewForConfigOrDie(c)
cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c)
return &cs
@@ -91,7 +91,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset {
// New creates a new Clientset for the given RESTClient.
func New(c rest.Interface) *Clientset {
var cs Clientset
cs.flaggerV1alpha1 = flaggerv1alpha1.New(c)
cs.flaggerV1alpha2 = flaggerv1alpha2.New(c)
cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
return &cs
@@ -20,8 +20,8 @@ package fake
import (
clientset "github.com/stefanprodan/flagger/pkg/client/clientset/versioned"
flaggerv1alpha1 "github.com/stefanprodan/flagger/pkg/client/clientset/versioned/typed/flagger/v1alpha1"
fakeflaggerv1alpha1 "github.com/stefanprodan/flagger/pkg/client/clientset/versioned/typed/flagger/v1alpha1/fake"
flaggerv1alpha2 "github.com/stefanprodan/flagger/pkg/client/clientset/versioned/typed/flagger/v1alpha2"
fakeflaggerv1alpha2 "github.com/stefanprodan/flagger/pkg/client/clientset/versioned/typed/flagger/v1alpha2/fake"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/discovery"
@@ -71,12 +71,12 @@ func (c *Clientset) Discovery() discovery.DiscoveryInterface {
var _ clientset.Interface = &Clientset{}
// FlaggerV1alpha1 retrieves the FlaggerV1alpha1Client
func (c *Clientset) FlaggerV1alpha1() flaggerv1alpha1.FlaggerV1alpha1Interface {
return &fakeflaggerv1alpha1.FakeFlaggerV1alpha1{Fake: &c.Fake}
// FlaggerV1alpha2 retrieves the FlaggerV1alpha2Client
func (c *Clientset) FlaggerV1alpha2() flaggerv1alpha2.FlaggerV1alpha2Interface {
return &fakeflaggerv1alpha2.FakeFlaggerV1alpha2{Fake: &c.Fake}
}
// Flagger retrieves the FlaggerV1alpha1Client
func (c *Clientset) Flagger() flaggerv1alpha1.FlaggerV1alpha1Interface {
return &fakeflaggerv1alpha1.FakeFlaggerV1alpha1{Fake: &c.Fake}
// Flagger retrieves the FlaggerV1alpha2Client
func (c *Clientset) Flagger() flaggerv1alpha2.FlaggerV1alpha2Interface {
return &fakeflaggerv1alpha2.FakeFlaggerV1alpha2{Fake: &c.Fake}
}
@@ -19,7 +19,7 @@ limitations under the License.
package fake
import (
flaggerv1alpha1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha1"
flaggerv1alpha2 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
schema "k8s.io/apimachinery/pkg/runtime/schema"
@@ -50,5 +50,5 @@ func init() {
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
// correctly.
func AddToScheme(scheme *runtime.Scheme) {
flaggerv1alpha1.AddToScheme(scheme)
flaggerv1alpha2.AddToScheme(scheme)
}
@@ -19,7 +19,7 @@ limitations under the License.
package scheme
import (
flaggerv1alpha1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha1"
flaggerv1alpha2 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
schema "k8s.io/apimachinery/pkg/runtime/schema"
@@ -50,5 +50,5 @@ func init() {
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
// correctly.
func AddToScheme(scheme *runtime.Scheme) {
flaggerv1alpha1.AddToScheme(scheme)
flaggerv1alpha2.AddToScheme(scheme)
}
@@ -16,10 +16,10 @@ limitations under the License.
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
package v1alpha2
import (
v1alpha1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha1"
v1alpha2 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
scheme "github.com/stefanprodan/flagger/pkg/client/clientset/versioned/scheme"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
@@ -35,15 +35,15 @@ type CanariesGetter interface {
// CanaryInterface has methods to work with Canary resources.
type CanaryInterface interface {
Create(*v1alpha1.Canary) (*v1alpha1.Canary, error)
Update(*v1alpha1.Canary) (*v1alpha1.Canary, error)
UpdateStatus(*v1alpha1.Canary) (*v1alpha1.Canary, error)
Create(*v1alpha2.Canary) (*v1alpha2.Canary, error)
Update(*v1alpha2.Canary) (*v1alpha2.Canary, error)
UpdateStatus(*v1alpha2.Canary) (*v1alpha2.Canary, error)
Delete(name string, options *v1.DeleteOptions) error
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
Get(name string, options v1.GetOptions) (*v1alpha1.Canary, error)
List(opts v1.ListOptions) (*v1alpha1.CanaryList, error)
Get(name string, options v1.GetOptions) (*v1alpha2.Canary, error)
List(opts v1.ListOptions) (*v1alpha2.CanaryList, error)
Watch(opts v1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Canary, err error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.Canary, err error)
CanaryExpansion
}
@@ -54,7 +54,7 @@ type canaries struct {
}
// newCanaries returns a Canaries
func newCanaries(c *FlaggerV1alpha1Client, namespace string) *canaries {
func newCanaries(c *FlaggerV1alpha2Client, namespace string) *canaries {
return &canaries{
client: c.RESTClient(),
ns: namespace,
@@ -62,8 +62,8 @@ func newCanaries(c *FlaggerV1alpha1Client, namespace string) *canaries {
}
// Get takes name of the canary, and returns the corresponding canary object, and an error if there is any.
func (c *canaries) Get(name string, options v1.GetOptions) (result *v1alpha1.Canary, err error) {
result = &v1alpha1.Canary{}
func (c *canaries) Get(name string, options v1.GetOptions) (result *v1alpha2.Canary, err error) {
result = &v1alpha2.Canary{}
err = c.client.Get().
Namespace(c.ns).
Resource("canaries").
@@ -75,8 +75,8 @@ func (c *canaries) Get(name string, options v1.GetOptions) (result *v1alpha1.Can
}
// List takes label and field selectors, and returns the list of Canaries that match those selectors.
func (c *canaries) List(opts v1.ListOptions) (result *v1alpha1.CanaryList, err error) {
result = &v1alpha1.CanaryList{}
func (c *canaries) List(opts v1.ListOptions) (result *v1alpha2.CanaryList, err error) {
result = &v1alpha2.CanaryList{}
err = c.client.Get().
Namespace(c.ns).
Resource("canaries").
@@ -97,8 +97,8 @@ func (c *canaries) Watch(opts v1.ListOptions) (watch.Interface, error) {
}
// Create takes the representation of a canary and creates it. Returns the server's representation of the canary, and an error, if there is any.
func (c *canaries) Create(canary *v1alpha1.Canary) (result *v1alpha1.Canary, err error) {
result = &v1alpha1.Canary{}
func (c *canaries) Create(canary *v1alpha2.Canary) (result *v1alpha2.Canary, err error) {
result = &v1alpha2.Canary{}
err = c.client.Post().
Namespace(c.ns).
Resource("canaries").
@@ -109,8 +109,8 @@ func (c *canaries) Create(canary *v1alpha1.Canary) (result *v1alpha1.Canary, err
}
// Update takes the representation of a canary and updates it. Returns the server's representation of the canary, and an error, if there is any.
func (c *canaries) Update(canary *v1alpha1.Canary) (result *v1alpha1.Canary, err error) {
result = &v1alpha1.Canary{}
func (c *canaries) Update(canary *v1alpha2.Canary) (result *v1alpha2.Canary, err error) {
result = &v1alpha2.Canary{}
err = c.client.Put().
Namespace(c.ns).
Resource("canaries").
@@ -124,8 +124,8 @@ func (c *canaries) Update(canary *v1alpha1.Canary) (result *v1alpha1.Canary, err
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *canaries) UpdateStatus(canary *v1alpha1.Canary) (result *v1alpha1.Canary, err error) {
result = &v1alpha1.Canary{}
func (c *canaries) UpdateStatus(canary *v1alpha2.Canary) (result *v1alpha2.Canary, err error) {
result = &v1alpha2.Canary{}
err = c.client.Put().
Namespace(c.ns).
Resource("canaries").
@@ -160,8 +160,8 @@ func (c *canaries) DeleteCollection(options *v1.DeleteOptions, listOptions v1.Li
}
// Patch applies the patch and returns the patched canary.
func (c *canaries) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Canary, err error) {
result = &v1alpha1.Canary{}
func (c *canaries) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.Canary, err error) {
result = &v1alpha2.Canary{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("canaries").
@@ -17,4 +17,4 @@ limitations under the License.
// Code generated by client-gen. DO NOT EDIT.
// This package has the automatically generated typed clients.
package v1alpha1
package v1alpha2
@@ -19,7 +19,7 @@ limitations under the License.
package fake
import (
v1alpha1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha1"
v1alpha2 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
@@ -30,29 +30,29 @@ import (
// FakeCanaries implements CanaryInterface
type FakeCanaries struct {
Fake *FakeFlaggerV1alpha1
Fake *FakeFlaggerV1alpha2
ns string
}
var canariesResource = schema.GroupVersionResource{Group: "flagger.app", Version: "v1alpha1", Resource: "canaries"}
var canariesResource = schema.GroupVersionResource{Group: "flagger.app", Version: "v1alpha2", Resource: "canaries"}
var canariesKind = schema.GroupVersionKind{Group: "flagger.app", Version: "v1alpha1", Kind: "Canary"}
var canariesKind = schema.GroupVersionKind{Group: "flagger.app", Version: "v1alpha2", Kind: "Canary"}
// Get takes name of the canary, and returns the corresponding canary object, and an error if there is any.
func (c *FakeCanaries) Get(name string, options v1.GetOptions) (result *v1alpha1.Canary, err error) {
func (c *FakeCanaries) Get(name string, options v1.GetOptions) (result *v1alpha2.Canary, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(canariesResource, c.ns, name), &v1alpha1.Canary{})
Invokes(testing.NewGetAction(canariesResource, c.ns, name), &v1alpha2.Canary{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.Canary), err
return obj.(*v1alpha2.Canary), err
}
// List takes label and field selectors, and returns the list of Canaries that match those selectors.
func (c *FakeCanaries) List(opts v1.ListOptions) (result *v1alpha1.CanaryList, err error) {
func (c *FakeCanaries) List(opts v1.ListOptions) (result *v1alpha2.CanaryList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(canariesResource, canariesKind, c.ns, opts), &v1alpha1.CanaryList{})
Invokes(testing.NewListAction(canariesResource, canariesKind, c.ns, opts), &v1alpha2.CanaryList{})
if obj == nil {
return nil, err
@@ -62,8 +62,8 @@ func (c *FakeCanaries) List(opts v1.ListOptions) (result *v1alpha1.CanaryList, e
if label == nil {
label = labels.Everything()
}
list := &v1alpha1.CanaryList{ListMeta: obj.(*v1alpha1.CanaryList).ListMeta}
for _, item := range obj.(*v1alpha1.CanaryList).Items {
list := &v1alpha2.CanaryList{ListMeta: obj.(*v1alpha2.CanaryList).ListMeta}
for _, item := range obj.(*v1alpha2.CanaryList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
@@ -79,43 +79,43 @@ func (c *FakeCanaries) Watch(opts v1.ListOptions) (watch.Interface, error) {
}
// Create takes the representation of a canary and creates it. Returns the server's representation of the canary, and an error, if there is any.
func (c *FakeCanaries) Create(canary *v1alpha1.Canary) (result *v1alpha1.Canary, err error) {
func (c *FakeCanaries) Create(canary *v1alpha2.Canary) (result *v1alpha2.Canary, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(canariesResource, c.ns, canary), &v1alpha1.Canary{})
Invokes(testing.NewCreateAction(canariesResource, c.ns, canary), &v1alpha2.Canary{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.Canary), err
return obj.(*v1alpha2.Canary), err
}
// Update takes the representation of a canary and updates it. Returns the server's representation of the canary, and an error, if there is any.
func (c *FakeCanaries) Update(canary *v1alpha1.Canary) (result *v1alpha1.Canary, err error) {
func (c *FakeCanaries) Update(canary *v1alpha2.Canary) (result *v1alpha2.Canary, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(canariesResource, c.ns, canary), &v1alpha1.Canary{})
Invokes(testing.NewUpdateAction(canariesResource, c.ns, canary), &v1alpha2.Canary{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.Canary), err
return obj.(*v1alpha2.Canary), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeCanaries) UpdateStatus(canary *v1alpha1.Canary) (*v1alpha1.Canary, error) {
func (c *FakeCanaries) UpdateStatus(canary *v1alpha2.Canary) (*v1alpha2.Canary, error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceAction(canariesResource, "status", c.ns, canary), &v1alpha1.Canary{})
Invokes(testing.NewUpdateSubresourceAction(canariesResource, "status", c.ns, canary), &v1alpha2.Canary{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.Canary), err
return obj.(*v1alpha2.Canary), err
}
// Delete takes name of the canary and deletes it. Returns an error if one occurs.
func (c *FakeCanaries) Delete(name string, options *v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(canariesResource, c.ns, name), &v1alpha1.Canary{})
Invokes(testing.NewDeleteAction(canariesResource, c.ns, name), &v1alpha2.Canary{})
return err
}
@@ -124,17 +124,17 @@ func (c *FakeCanaries) Delete(name string, options *v1.DeleteOptions) error {
func (c *FakeCanaries) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(canariesResource, c.ns, listOptions)
_, err := c.Fake.Invokes(action, &v1alpha1.CanaryList{})
_, err := c.Fake.Invokes(action, &v1alpha2.CanaryList{})
return err
}
// Patch applies the patch and returns the patched canary.
func (c *FakeCanaries) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Canary, err error) {
func (c *FakeCanaries) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha2.Canary, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(canariesResource, c.ns, name, data, subresources...), &v1alpha1.Canary{})
Invokes(testing.NewPatchSubresourceAction(canariesResource, c.ns, name, data, subresources...), &v1alpha2.Canary{})
if obj == nil {
return nil, err
}
return obj.(*v1alpha1.Canary), err
return obj.(*v1alpha2.Canary), err
}
@@ -19,22 +19,22 @@ limitations under the License.
package fake
import (
v1alpha1 "github.com/stefanprodan/flagger/pkg/client/clientset/versioned/typed/flagger/v1alpha1"
v1alpha2 "github.com/stefanprodan/flagger/pkg/client/clientset/versioned/typed/flagger/v1alpha2"
rest "k8s.io/client-go/rest"
testing "k8s.io/client-go/testing"
)
type FakeFlaggerV1alpha1 struct {
type FakeFlaggerV1alpha2 struct {
*testing.Fake
}
func (c *FakeFlaggerV1alpha1) Canaries(namespace string) v1alpha1.CanaryInterface {
func (c *FakeFlaggerV1alpha2) Canaries(namespace string) v1alpha2.CanaryInterface {
return &FakeCanaries{c, namespace}
}
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FakeFlaggerV1alpha1) RESTClient() rest.Interface {
func (c *FakeFlaggerV1alpha2) RESTClient() rest.Interface {
var ret *rest.RESTClient
return ret
}
@@ -16,31 +16,31 @@ limitations under the License.
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
package v1alpha2
import (
v1alpha1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha1"
v1alpha2 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
"github.com/stefanprodan/flagger/pkg/client/clientset/versioned/scheme"
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
rest "k8s.io/client-go/rest"
)
type FlaggerV1alpha1Interface interface {
type FlaggerV1alpha2Interface interface {
RESTClient() rest.Interface
CanariesGetter
}
// FlaggerV1alpha1Client is used to interact with features provided by the flagger.app group.
type FlaggerV1alpha1Client struct {
// FlaggerV1alpha2Client is used to interact with features provided by the flagger.app group.
type FlaggerV1alpha2Client struct {
restClient rest.Interface
}
func (c *FlaggerV1alpha1Client) Canaries(namespace string) CanaryInterface {
func (c *FlaggerV1alpha2Client) Canaries(namespace string) CanaryInterface {
return newCanaries(c, namespace)
}
// NewForConfig creates a new FlaggerV1alpha1Client for the given config.
func NewForConfig(c *rest.Config) (*FlaggerV1alpha1Client, error) {
// NewForConfig creates a new FlaggerV1alpha2Client for the given config.
func NewForConfig(c *rest.Config) (*FlaggerV1alpha2Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
@@ -49,12 +49,12 @@ func NewForConfig(c *rest.Config) (*FlaggerV1alpha1Client, error) {
if err != nil {
return nil, err
}
return &FlaggerV1alpha1Client{client}, nil
return &FlaggerV1alpha2Client{client}, nil
}
// NewForConfigOrDie creates a new FlaggerV1alpha1Client for the given config and
// NewForConfigOrDie creates a new FlaggerV1alpha2Client for the given config and
// panics if there is an error in the config.
func NewForConfigOrDie(c *rest.Config) *FlaggerV1alpha1Client {
func NewForConfigOrDie(c *rest.Config) *FlaggerV1alpha2Client {
client, err := NewForConfig(c)
if err != nil {
panic(err)
@@ -62,13 +62,13 @@ func NewForConfigOrDie(c *rest.Config) *FlaggerV1alpha1Client {
return client
}
// New creates a new FlaggerV1alpha1Client for the given RESTClient.
func New(c rest.Interface) *FlaggerV1alpha1Client {
return &FlaggerV1alpha1Client{c}
// New creates a new FlaggerV1alpha2Client for the given RESTClient.
func New(c rest.Interface) *FlaggerV1alpha2Client {
return &FlaggerV1alpha2Client{c}
}
func setConfigDefaults(config *rest.Config) error {
gv := v1alpha1.SchemeGroupVersion
gv := v1alpha2.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
@@ -82,7 +82,7 @@ func setConfigDefaults(config *rest.Config) error {
// RESTClient returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *FlaggerV1alpha1Client) RESTClient() rest.Interface {
func (c *FlaggerV1alpha2Client) RESTClient() rest.Interface {
if c == nil {
return nil
}
@@ -16,6 +16,6 @@ limitations under the License.
// Code generated by client-gen. DO NOT EDIT.
package v1alpha1
package v1alpha2
type CanaryExpansion interface{}
@@ -19,14 +19,14 @@ limitations under the License.
package flagger
import (
v1alpha1 "github.com/stefanprodan/flagger/pkg/client/informers/externalversions/flagger/v1alpha1"
v1alpha2 "github.com/stefanprodan/flagger/pkg/client/informers/externalversions/flagger/v1alpha2"
internalinterfaces "github.com/stefanprodan/flagger/pkg/client/informers/externalversions/internalinterfaces"
)
// 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
// V1alpha2 provides access to shared informers for resources in V1alpha2.
V1alpha2() v1alpha2.Interface
}
type group struct {
@@ -40,7 +40,7 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
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)
// V1alpha2 returns a new v1alpha2.Interface.
func (g *group) V1alpha2() v1alpha2.Interface {
return v1alpha2.New(g.factory, g.namespace, g.tweakListOptions)
}
@@ -16,15 +16,15 @@ limitations under the License.
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
package v1alpha2
import (
time "time"
flaggerv1alpha1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha1"
flaggerv1alpha2 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
versioned "github.com/stefanprodan/flagger/pkg/client/clientset/versioned"
internalinterfaces "github.com/stefanprodan/flagger/pkg/client/informers/externalversions/internalinterfaces"
v1alpha1 "github.com/stefanprodan/flagger/pkg/client/listers/flagger/v1alpha1"
v1alpha2 "github.com/stefanprodan/flagger/pkg/client/listers/flagger/v1alpha2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
@@ -35,7 +35,7 @@ import (
// Canaries.
type CanaryInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1alpha1.CanaryLister
Lister() v1alpha2.CanaryLister
}
type canaryInformer struct {
@@ -61,16 +61,16 @@ func NewFilteredCanaryInformer(client versioned.Interface, namespace string, res
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.FlaggerV1alpha1().Canaries(namespace).List(options)
return client.FlaggerV1alpha2().Canaries(namespace).List(options)
},
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.FlaggerV1alpha1().Canaries(namespace).Watch(options)
return client.FlaggerV1alpha2().Canaries(namespace).Watch(options)
},
},
&flaggerv1alpha1.Canary{},
&flaggerv1alpha2.Canary{},
resyncPeriod,
indexers,
)
@@ -81,9 +81,9 @@ func (f *canaryInformer) defaultInformer(client versioned.Interface, resyncPerio
}
func (f *canaryInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&flaggerv1alpha1.Canary{}, f.defaultInformer)
return f.factory.InformerFor(&flaggerv1alpha2.Canary{}, f.defaultInformer)
}
func (f *canaryInformer) Lister() v1alpha1.CanaryLister {
return v1alpha1.NewCanaryLister(f.Informer().GetIndexer())
func (f *canaryInformer) Lister() v1alpha2.CanaryLister {
return v1alpha2.NewCanaryLister(f.Informer().GetIndexer())
}
@@ -16,7 +16,7 @@ limitations under the License.
// Code generated by informer-gen. DO NOT EDIT.
package v1alpha1
package v1alpha2
import (
internalinterfaces "github.com/stefanprodan/flagger/pkg/client/informers/externalversions/internalinterfaces"
@@ -21,7 +21,7 @@ package externalversions
import (
"fmt"
v1alpha1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha1"
v1alpha2 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
schema "k8s.io/apimachinery/pkg/runtime/schema"
cache "k8s.io/client-go/tools/cache"
)
@@ -52,9 +52,9 @@ func (f *genericInformer) Lister() cache.GenericLister {
// TODO extend this to unknown resources with a client pool
func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
switch resource {
// Group=flagger.app, Version=v1alpha1
case v1alpha1.SchemeGroupVersion.WithResource("canaries"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Flagger().V1alpha1().Canaries().Informer()}, nil
// Group=flagger.app, Version=v1alpha2
case v1alpha2.SchemeGroupVersion.WithResource("canaries"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Flagger().V1alpha2().Canaries().Informer()}, nil
}
@@ -16,10 +16,10 @@ limitations under the License.
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha1
package v1alpha2
import (
v1alpha1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha1"
v1alpha2 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
@@ -28,7 +28,7 @@ import (
// CanaryLister helps list Canaries.
type CanaryLister interface {
// List lists all Canaries in the indexer.
List(selector labels.Selector) (ret []*v1alpha1.Canary, err error)
List(selector labels.Selector) (ret []*v1alpha2.Canary, err error)
// Canaries returns an object that can list and get Canaries.
Canaries(namespace string) CanaryNamespaceLister
CanaryListerExpansion
@@ -45,9 +45,9 @@ func NewCanaryLister(indexer cache.Indexer) CanaryLister {
}
// List lists all Canaries in the indexer.
func (s *canaryLister) List(selector labels.Selector) (ret []*v1alpha1.Canary, err error) {
func (s *canaryLister) List(selector labels.Selector) (ret []*v1alpha2.Canary, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.Canary))
ret = append(ret, m.(*v1alpha2.Canary))
})
return ret, err
}
@@ -60,9 +60,9 @@ func (s *canaryLister) Canaries(namespace string) CanaryNamespaceLister {
// CanaryNamespaceLister helps list and get Canaries.
type CanaryNamespaceLister interface {
// List lists all Canaries in the indexer for a given namespace.
List(selector labels.Selector) (ret []*v1alpha1.Canary, err error)
List(selector labels.Selector) (ret []*v1alpha2.Canary, err error)
// Get retrieves the Canary from the indexer for a given namespace and name.
Get(name string) (*v1alpha1.Canary, error)
Get(name string) (*v1alpha2.Canary, error)
CanaryNamespaceListerExpansion
}
@@ -74,21 +74,21 @@ type canaryNamespaceLister struct {
}
// List lists all Canaries in the indexer for a given namespace.
func (s canaryNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Canary, err error) {
func (s canaryNamespaceLister) List(selector labels.Selector) (ret []*v1alpha2.Canary, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1alpha1.Canary))
ret = append(ret, m.(*v1alpha2.Canary))
})
return ret, err
}
// Get retrieves the Canary from the indexer for a given namespace and name.
func (s canaryNamespaceLister) Get(name string) (*v1alpha1.Canary, error) {
func (s canaryNamespaceLister) Get(name string) (*v1alpha2.Canary, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1alpha1.Resource("canary"), name)
return nil, errors.NewNotFound(v1alpha2.Resource("canary"), name)
}
return obj.(*v1alpha1.Canary), nil
return obj.(*v1alpha2.Canary), nil
}
@@ -16,7 +16,7 @@ limitations under the License.
// Code generated by lister-gen. DO NOT EDIT.
package v1alpha1
package v1alpha2
// CanaryListerExpansion allows custom methods to be added to
// CanaryLister.
+3 -3
View File
@@ -7,11 +7,11 @@ import (
"github.com/google/go-cmp/cmp"
istioclientset "github.com/knative/pkg/client/clientset/versioned"
flaggerv1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha1"
flaggerv1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
clientset "github.com/stefanprodan/flagger/pkg/client/clientset/versioned"
flaggerscheme "github.com/stefanprodan/flagger/pkg/client/clientset/versioned/scheme"
flaggerinformers "github.com/stefanprodan/flagger/pkg/client/informers/externalversions/flagger/v1alpha1"
flaggerlisters "github.com/stefanprodan/flagger/pkg/client/listers/flagger/v1alpha1"
flaggerinformers "github.com/stefanprodan/flagger/pkg/client/informers/externalversions/flagger/v1alpha2"
flaggerlisters "github.com/stefanprodan/flagger/pkg/client/listers/flagger/v1alpha2"
"github.com/stefanprodan/flagger/pkg/notifier"
"go.uber.org/zap"
corev1 "k8s.io/api/core/v1"
+4 -4
View File
@@ -9,7 +9,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
istioclientset "github.com/knative/pkg/client/clientset/versioned"
flaggerv1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha1"
flaggerv1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
clientset "github.com/stefanprodan/flagger/pkg/client/clientset/versioned"
"go.uber.org/zap"
appsv1 "k8s.io/api/apps/v1"
@@ -154,7 +154,7 @@ func (c *CanaryDeployer) IsNewSpec(cd *flaggerv1.Canary) (bool, error) {
func (c *CanaryDeployer) SetFailedChecks(cd *flaggerv1.Canary, val int) error {
cd.Status.FailedChecks = val
cd.Status.LastTransitionTime = metav1.Now()
cd, err := c.flaggerClient.FlaggerV1alpha1().Canaries(cd.Namespace).Update(cd)
cd, err := c.flaggerClient.FlaggerV1alpha2().Canaries(cd.Namespace).Update(cd)
if err != nil {
return fmt.Errorf("deployment %s.%s update error %v", cd.Spec.TargetRef.Name, cd.Namespace, err)
}
@@ -165,7 +165,7 @@ func (c *CanaryDeployer) SetFailedChecks(cd *flaggerv1.Canary, val int) error {
func (c *CanaryDeployer) SetState(cd *flaggerv1.Canary, state flaggerv1.CanaryState) error {
cd.Status.State = state
cd.Status.LastTransitionTime = metav1.Now()
cd, err := c.flaggerClient.FlaggerV1alpha1().Canaries(cd.Namespace).Update(cd)
cd, err := c.flaggerClient.FlaggerV1alpha2().Canaries(cd.Namespace).Update(cd)
if err != nil {
return fmt.Errorf("deployment %s.%s update error %v", cd.Spec.TargetRef.Name, cd.Namespace, err)
}
@@ -192,7 +192,7 @@ func (c *CanaryDeployer) SyncStatus(cd *flaggerv1.Canary, status flaggerv1.Canar
cd.Status.FailedChecks = status.FailedChecks
cd.Status.CanaryRevision = specEnc
cd.Status.LastTransitionTime = metav1.Now()
cd, err = c.flaggerClient.FlaggerV1alpha1().Canaries(cd.Namespace).Update(cd)
cd, err = c.flaggerClient.FlaggerV1alpha2().Canaries(cd.Namespace).Update(cd)
if err != nil {
return fmt.Errorf("deployment %s.%s update error %v", cd.Spec.TargetRef.Name, cd.Namespace, err)
}
+16 -16
View File
@@ -3,7 +3,7 @@ package controller
import (
"testing"
"github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha1"
"github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
fakeFlagger "github.com/stefanprodan/flagger/pkg/client/clientset/versioned/fake"
"github.com/stefanprodan/flagger/pkg/logging"
appsv1 "k8s.io/api/apps/v1"
@@ -14,14 +14,14 @@ import (
"k8s.io/client-go/kubernetes/fake"
)
func newTestCanary() *v1alpha1.Canary {
cd := &v1alpha1.Canary{
TypeMeta: metav1.TypeMeta{APIVersion: v1alpha1.SchemeGroupVersion.String()},
func newTestCanary() *v1alpha2.Canary {
cd := &v1alpha2.Canary{
TypeMeta: metav1.TypeMeta{APIVersion: v1alpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "podinfo",
},
Spec: v1alpha1.CanarySpec{
Spec: v1alpha2.CanarySpec{
TargetRef: hpav1.CrossVersionObjectReference{
Name: "podinfo",
APIVersion: "apps/v1",
@@ -31,13 +31,13 @@ func newTestCanary() *v1alpha1.Canary {
Name: "podinfo",
APIVersion: "autoscaling/v2beta1",
Kind: "HorizontalPodAutoscaler",
}, Service: v1alpha1.CanaryService{
}, Service: v1alpha2.CanaryService{
Port: 9898,
}, CanaryAnalysis: v1alpha1.CanaryAnalysis{
}, CanaryAnalysis: v1alpha2.CanaryAnalysis{
Threshold: 10,
StepWeight: 10,
MaxWeight: 50,
Metrics: []v1alpha1.CanaryMetric{
Metrics: []v1alpha2.CanaryMetric{
{
Name: "istio_requests_total",
Threshold: 99,
@@ -356,7 +356,7 @@ func TestCanaryDeployer_SetFailedChecks(t *testing.T) {
t.Fatal(err.Error())
}
res, err := flaggerClient.FlaggerV1alpha1().Canaries("default").Get("podinfo", metav1.GetOptions{})
res, err := flaggerClient.FlaggerV1alpha2().Canaries("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
@@ -387,18 +387,18 @@ func TestCanaryDeployer_SetState(t *testing.T) {
t.Fatal(err.Error())
}
err = deployer.SetState(canary, v1alpha1.CanaryRunning)
err = deployer.SetState(canary, v1alpha2.CanaryRunning)
if err != nil {
t.Fatal(err.Error())
}
res, err := flaggerClient.FlaggerV1alpha1().Canaries("default").Get("podinfo", metav1.GetOptions{})
res, err := flaggerClient.FlaggerV1alpha2().Canaries("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if res.Status.State != v1alpha1.CanaryRunning {
t.Errorf("Got %v wanted %v", res.Status.State, v1alpha1.CanaryRunning)
if res.Status.State != v1alpha2.CanaryRunning {
t.Errorf("Got %v wanted %v", res.Status.State, v1alpha2.CanaryRunning)
}
}
@@ -423,8 +423,8 @@ func TestCanaryDeployer_SyncStatus(t *testing.T) {
t.Fatal(err.Error())
}
status := v1alpha1.CanaryStatus{
State: v1alpha1.CanaryRunning,
status := v1alpha2.CanaryStatus{
State: v1alpha2.CanaryRunning,
FailedChecks: 2,
}
err = deployer.SyncStatus(canary, status)
@@ -432,7 +432,7 @@ func TestCanaryDeployer_SyncStatus(t *testing.T) {
t.Fatal(err.Error())
}
res, err := flaggerClient.FlaggerV1alpha1().Canaries("default").Get("podinfo", metav1.GetOptions{})
res, err := flaggerClient.FlaggerV1alpha2().Canaries("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
+84
View File
@@ -0,0 +1,84 @@
package controller
import (
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestCanaryObserver_GetDeploymentCounter(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json := `{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1545905245.458,"100"]}]}}`
w.Write([]byte(json))
}))
defer ts.Close()
observer := CanaryObserver{
metricsServer: ts.URL,
}
val, err := observer.GetDeploymentCounter("podinfo", "default", "istio_requests_total", "1m")
if err != nil {
t.Fatal(err.Error())
}
if val != 100 {
t.Errorf("Got %v wanted %v", val, 100)
}
}
func TestCanaryObserver_GetDeploymentHistogram(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json := `{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1545905245.596,"0.2"]}]}}`
w.Write([]byte(json))
}))
defer ts.Close()
observer := CanaryObserver{
metricsServer: ts.URL,
}
val, err := observer.GetDeploymentHistogram("podinfo", "default", "istio_request_duration_seconds_bucket", "1m")
if err != nil {
t.Fatal(err.Error())
}
if val != 200*time.Millisecond {
t.Errorf("Got %v wanted %v", val, 200*time.Millisecond)
}
}
func TestCheckMetricsServer(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json := `{"status":"success","data":{"config.file":"/etc/prometheus/prometheus.yml"}}`
w.Write([]byte(json))
}))
defer ts.Close()
ok, err := CheckMetricsServer(ts.URL)
if err != nil {
t.Fatal(err.Error())
}
if !ok {
t.Errorf("Got %v wanted %v", ok, true)
}
}
func TestCheckMetricsServer_Offline(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadGateway)
}))
defer ts.Close()
ok, err := CheckMetricsServer(ts.URL)
if err == nil {
t.Errorf("Got no error wanted %v", http.StatusBadGateway)
}
if ok {
t.Errorf("Got %v wanted %v", ok, false)
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"time"
"github.com/prometheus/client_golang/prometheus"
flaggerv1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha1"
flaggerv1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
)
// CanaryRecorder records the canary analysis as Prometheus metrics
+1 -1
View File
@@ -5,7 +5,7 @@ import (
istiov1alpha3 "github.com/knative/pkg/apis/istio/v1alpha3"
istioclientset "github.com/knative/pkg/client/clientset/versioned"
flaggerv1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha1"
flaggerv1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
clientset "github.com/stefanprodan/flagger/pkg/client/clientset/versioned"
"go.uber.org/zap"
corev1 "k8s.io/api/core/v1"
+15 -4
View File
@@ -4,7 +4,7 @@ import (
"fmt"
"time"
flaggerv1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha1"
flaggerv1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
"k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -32,7 +32,7 @@ func (c *Controller) scheduleCanaries() {
func (c *Controller) advanceCanary(name string, namespace string) {
begin := time.Now()
// check if the canary exists
cd, err := c.flaggerClient.FlaggerV1alpha1().Canaries(namespace).Get(name, v1.GetOptions{})
cd, err := c.flaggerClient.FlaggerV1alpha2().Canaries(namespace).Get(name, v1.GetOptions{})
if err != nil {
c.logger.Errorf("Canary %s.%s not found", name, namespace)
return
@@ -139,7 +139,7 @@ func (c *Controller) advanceCanary(name string, namespace string) {
if canaryRoute.Weight == 0 {
c.recordEventInfof(cd, "Starting canary deployment for %s.%s", cd.Name, cd.Namespace)
} else {
if ok := c.checkCanaryMetrics(cd); !ok {
if ok := c.analyseCanary(cd); !ok {
if err := c.deployer.SetFailedChecks(cd, cd.Status.FailedChecks+1); err != nil {
c.recordEventWarningf(cd, "%v", err)
return
@@ -242,7 +242,8 @@ func (c *Controller) checkCanaryStatus(cd *flaggerv1.Canary, deployer CanaryDepl
return false
}
func (c *Controller) checkCanaryMetrics(r *flaggerv1.Canary) bool {
func (c *Controller) analyseCanary(r *flaggerv1.Canary) bool {
// run metrics checks
for _, metric := range r.Spec.CanaryAnalysis.Metrics {
if metric.Name == "istio_requests_total" {
val, err := c.observer.GetDeploymentCounter(r.Spec.TargetRef.Name, r.Namespace, metric.Name, metric.Interval)
@@ -272,5 +273,15 @@ func (c *Controller) checkCanaryMetrics(r *flaggerv1.Canary) bool {
}
}
// run external checks
for _, webhook := range r.Spec.CanaryAnalysis.Webhooks {
err := CallWebhook(r.Name, r.Namespace, webhook)
if err != nil {
c.recordEventWarningf(r, "Halt %s.%s advancement external check %s failed %v",
r.Name, r.Namespace, webhook.Name, err)
return false
}
}
return true
}
+8 -8
View File
@@ -6,7 +6,7 @@ import (
"time"
fakeIstio "github.com/knative/pkg/client/clientset/versioned/fake"
"github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha1"
"github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
fakeFlagger "github.com/stefanprodan/flagger/pkg/client/clientset/versioned/fake"
informers "github.com/stefanprodan/flagger/pkg/client/informers/externalversions"
"github.com/stefanprodan/flagger/pkg/logging"
@@ -47,7 +47,7 @@ func TestScheduler_Init(t *testing.T) {
}
flaggerInformerFactory := informers.NewSharedInformerFactory(flaggerClient, noResyncPeriodFunc())
flaggerInformer := flaggerInformerFactory.Flagger().V1alpha1().Canaries()
flaggerInformer := flaggerInformerFactory.Flagger().V1alpha2().Canaries()
ctrl := &Controller{
kubeClient: kubeClient,
@@ -101,7 +101,7 @@ func TestScheduler_NewRevision(t *testing.T) {
}
flaggerInformerFactory := informers.NewSharedInformerFactory(flaggerClient, noResyncPeriodFunc())
flaggerInformer := flaggerInformerFactory.Flagger().V1alpha1().Canaries()
flaggerInformer := flaggerInformerFactory.Flagger().V1alpha2().Canaries()
ctrl := &Controller{
kubeClient: kubeClient,
@@ -170,7 +170,7 @@ func TestScheduler_Rollback(t *testing.T) {
}
flaggerInformerFactory := informers.NewSharedInformerFactory(flaggerClient, noResyncPeriodFunc())
flaggerInformer := flaggerInformerFactory.Flagger().V1alpha1().Canaries()
flaggerInformer := flaggerInformerFactory.Flagger().V1alpha2().Canaries()
ctrl := &Controller{
kubeClient: kubeClient,
@@ -194,7 +194,7 @@ func TestScheduler_Rollback(t *testing.T) {
ctrl.advanceCanary("podinfo", "default")
// update failed checks to max
err := deployer.SyncStatus(canary, v1alpha1.CanaryStatus{State: v1alpha1.CanaryRunning, FailedChecks: 11})
err := deployer.SyncStatus(canary, v1alpha2.CanaryStatus{State: v1alpha2.CanaryRunning, FailedChecks: 11})
if err != nil {
t.Fatal(err.Error())
}
@@ -202,12 +202,12 @@ func TestScheduler_Rollback(t *testing.T) {
// detect changes
ctrl.advanceCanary("podinfo", "default")
c, err := flaggerClient.FlaggerV1alpha1().Canaries("default").Get("podinfo", metav1.GetOptions{})
c, err := flaggerClient.FlaggerV1alpha2().Canaries("default").Get("podinfo", metav1.GetOptions{})
if err != nil {
t.Fatal(err.Error())
}
if c.Status.State != v1alpha1.CanaryFailed {
t.Errorf("Got canary state %v wanted %v", c.Status.State, v1alpha1.CanaryFailed)
if c.Status.State != v1alpha2.CanaryFailed {
t.Errorf("Got canary state %v wanted %v", c.Status.State, v1alpha2.CanaryFailed)
}
}
+70
View File
@@ -0,0 +1,70 @@
package controller
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
flaggerv1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
"io/ioutil"
"net/http"
"net/url"
"time"
)
// CallWebhook does a HTTP POST to an external service and
// returns an error if the response status code is non-2xx
func CallWebhook(name string, namepace string, w flaggerv1.CanaryWebhook) error {
payload := flaggerv1.CanaryWebhookPayload{
Name: name,
Namespace: namepace,
Metadata: w.Metadata,
}
payloadBin, err := json.Marshal(payload)
if err != nil {
return err
}
hook, err := url.Parse(w.URL)
if err != nil {
return err
}
req, err := http.NewRequest("POST", hook.String(), bytes.NewBuffer(payloadBin))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
if len(w.Timeout) < 2 {
w.Timeout = "10s"
}
timeout, err := time.ParseDuration(w.Timeout)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(req.Context(), timeout)
defer cancel()
r, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
return err
}
defer r.Body.Close()
b, err := ioutil.ReadAll(r.Body)
if err != nil {
return fmt.Errorf("error reading body: %s", err.Error())
}
if r.StatusCode > 202 {
return errors.New(string(b))
}
return nil
}
+42
View File
@@ -0,0 +1,42 @@
package controller
import (
flaggerv1 "github.com/stefanprodan/flagger/pkg/apis/flagger/v1alpha2"
"net/http"
"net/http/httptest"
"testing"
)
func TestCallWebhook(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusAccepted)
}))
defer ts.Close()
hook := flaggerv1.CanaryWebhook{
Name: "validation",
URL: ts.URL,
Timeout: "10s",
Metadata: &map[string]string{"key1": "val1"},
}
err := CallWebhook("podinfo", "default", hook)
if err != nil {
t.Fatal(err.Error())
}
}
func TestCallWebhook_StatusCode(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer ts.Close()
hook := flaggerv1.CanaryWebhook{
Name: "validation",
URL: ts.URL,
}
err := CallWebhook("podinfo", "default", hook)
if err == nil {
t.Errorf("Got no error wanted %v", http.StatusInternalServerError)
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
package version
var VERSION = "0.1.2"
var VERSION = "0.2.0-alpha.1"
var REVISION = "unknown"