From 296169f58ed7f15a589838184c904ac5e7631040 Mon Sep 17 00:00:00 2001 From: Thomas Santerre Date: Tue, 3 Dec 2019 14:58:04 +0900 Subject: [PATCH 01/28] Initial implementation for the WebMetric type of analysis. --- metricproviders/metricproviders.go | 8 + metricproviders/webmetric/webmetric.go | 191 +++++++++++++++++++ metricproviders/webmetric/webmetric_test.go | 1 + pkg/apis/rollouts/v1alpha1/analysis_types.go | 14 ++ 4 files changed, 214 insertions(+) create mode 100644 metricproviders/webmetric/webmetric.go create mode 100644 metricproviders/webmetric/webmetric_test.go diff --git a/metricproviders/metricproviders.go b/metricproviders/metricproviders.go index b716f26a22..d32e1c8378 100644 --- a/metricproviders/metricproviders.go +++ b/metricproviders/metricproviders.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/argoproj/argo-rollouts/metricproviders/kayenta" + "github.com/argoproj/argo-rollouts/metricproviders/webmetric" log "github.com/sirupsen/logrus" "k8s.io/client-go/kubernetes" @@ -50,6 +51,13 @@ func (f *ProviderFactory) NewProvider(logCtx log.Entry, metric v1alpha1.Metric) } else if metric.Provider.Kayenta != nil { c := kayenta.NewHttpClient() return kayenta.NewKayentaProvider(logCtx, c), nil + } else if metric.Provider.WebMetric != nil { + c := webmetric.NewWebMetricHttpClient(metric) + p, err := webmetric.NewWebMetricJsonParser(metric) + if err != nil { + return nil, err + } + return webmetric.NewWebMetricProvider(logCtx, c, p), nil } return nil, fmt.Errorf("no valid provider in metric '%s'", metric.Name) } diff --git a/metricproviders/webmetric/webmetric.go b/metricproviders/webmetric/webmetric.go new file mode 100644 index 0000000000..9b18551831 --- /dev/null +++ b/metricproviders/webmetric/webmetric.go @@ -0,0 +1,191 @@ +package webmetric + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "time" + + "k8s.io/client-go/util/jsonpath" + + "github.com/argoproj/argo-rollouts/utils/evaluate" + metricutil "github.com/argoproj/argo-rollouts/utils/metric" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + log "github.com/sirupsen/logrus" +) + +const ( + //ProviderType indicates the provider is prometheus + ProviderType = "WebMetric" +) + +// Provider contains all the required components to run a WebMetric query +// Implements the Provider Interface +type Provider struct { + logCtx log.Entry + client *http.Client + jsonParser *jsonpath.JSONPath +} + +// Type incidates provider is a WebMetric provider +func (p *Provider) Type() string { + return ProviderType +} + +func (p *Provider) Run(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric) v1alpha1.Measurement { + var ( + err error + ) + + startTime := metav1.Now() + + // Measurement to pass back + measurement := v1alpha1.Measurement{ + StartedAt: &startTime, + } + + // Create request + request := &http.Request{ + Method: "GET", + } + + request.URL, err = url.Parse(metric.Provider.WebMetric.Url) + if err != nil { + return metricutil.MarkMeasurementError(measurement, err) + } + + for _, header := range metric.Provider.WebMetric.Headers { + request.Header.Set(header.Key, header.Value) + } + + // Send Request + response, err := p.client.Do(request) + if err != nil || response.StatusCode != http.StatusOK { + return metricutil.MarkMeasurementError(measurement, err) + } + + // Parse Response + + return measurement +} + +func (p *Provider) parseResponse(metric v1alpha1.Metric, response http.Response) (string, v1alpha1.AnalysisPhase, error) { + var data interface{} + + bodyBytes, err := ioutil.ReadAll(response.Body) + if err != nil { + log.Fatal(err) + } + + err = json.Unmarshal(bodyBytes, &data) + if err != nil { + return "", v1alpha1.AnalysisPhaseError, fmt.Errorf("Could not parse JSON body: %v", err) + } + + buf := new(bytes.Buffer) + err = p.jsonParser.Execute(buf, data) + if err != nil { + return "", v1alpha1.AnalysisPhaseError, fmt.Errorf("Could not parse JSON body: %s", err) + } + + out := buf.String() + status := p.evaluateResponse(metric, out) + return out, status, nil +} + +func (p *Provider) evaluateResponse(metric v1alpha1.Metric, result string) v1alpha1.AnalysisPhase { + successCondition := false + failCondition := false + var err error + + if metric.SuccessCondition != "" { + successCondition, err = evaluate.EvalCondition(result, metric.SuccessCondition) + if err != nil { + p.logCtx.Warning(err.Error()) + return v1alpha1.AnalysisPhaseError + } + } + if metric.FailureCondition != "" { + failCondition, err = evaluate.EvalCondition(result, metric.FailureCondition) + if err != nil { + return v1alpha1.AnalysisPhaseError + } + } + + switch { + case metric.SuccessCondition == "" && metric.FailureCondition == "": + //Always return success unless there is an error + return v1alpha1.AnalysisPhaseSuccessful + case metric.SuccessCondition != "" && metric.FailureCondition == "": + // Without a failure condition, a measurement is considered a failure if the measurement's success condition is not true + failCondition = !successCondition + case metric.SuccessCondition == "" && metric.FailureCondition != "": + // Without a success condition, a measurement is considered a successful if the measurement's failure condition is not true + successCondition = !failCondition + } + + if failCondition { + return v1alpha1.AnalysisPhaseFailed + } + + if !failCondition && !successCondition { + return v1alpha1.AnalysisPhaseInconclusive + } + + // If we reach this code path, failCondition is false and successCondition is true + return v1alpha1.AnalysisPhaseSuccessful +} + +// Resume should not be used the WebMetric provider since all the work should occur in the Run method +func (p *Provider) Resume(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric, measurement v1alpha1.Measurement) v1alpha1.Measurement { + p.logCtx.Warn("WebMetric provider should not execute the Resume method") + return measurement +} + +// Terminate should not be used the WebMetric provider since all the work should occur in the Run method +func (p *Provider) Terminate(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric, measurement v1alpha1.Measurement) v1alpha1.Measurement { + p.logCtx.Warn("WebMetric provider should not execute the Terminate method") + return measurement +} + +// GarbageCollect is a no-op for the WebMetric provider +func (p *Provider) GarbageCollect(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric, limit int) error { + return nil +} + +func NewWebMetricHttpClient(metric v1alpha1.Metric) *http.Client { + var ( + timeout time.Duration + ) + + if metric.Provider.WebMetric.Timeout <= 0 { + timeout = time.Duration(10) * time.Second + } else { + timeout = time.Duration(metric.Provider.WebMetric.Timeout) * time.Second + } + c := &http.Client{ + Timeout: timeout, + } + return c +} + +func NewWebMetricJsonParser(metric v1alpha1.Metric) (*jsonpath.JSONPath, error) { + jsonParser := jsonpath.New("metrics") + + err := jsonParser.Parse(metric.Provider.WebMetric.JsonPath) + + return jsonParser, err +} + +func NewWebMetricProvider(logCtx log.Entry, client *http.Client, jsonParser *jsonpath.JSONPath) *Provider { + return &Provider{ + logCtx: logCtx, + client: client, + jsonParser: jsonParser, + } +} diff --git a/metricproviders/webmetric/webmetric_test.go b/metricproviders/webmetric/webmetric_test.go new file mode 100644 index 0000000000..b08691b29c --- /dev/null +++ b/metricproviders/webmetric/webmetric_test.go @@ -0,0 +1 @@ +package webmetric diff --git a/pkg/apis/rollouts/v1alpha1/analysis_types.go b/pkg/apis/rollouts/v1alpha1/analysis_types.go index a4cc9d92b7..8e4db4280c 100644 --- a/pkg/apis/rollouts/v1alpha1/analysis_types.go +++ b/pkg/apis/rollouts/v1alpha1/analysis_types.go @@ -97,6 +97,7 @@ type MetricProvider struct { // Prometheus specifies the prometheus metric to query Prometheus *PrometheusMetric `json:"prometheus,omitempty"` Kayenta *KayentaMetric `json:"kayenta,omitempty"` + WebMetric *WebMetricMetric `json:"webmetric,omitempty"` // Job specifies the job metric run Job *JobMetric `json:"job,omitempty"` } @@ -267,3 +268,16 @@ type ScopeDetail struct { Start string `json:"start"` End string `json:"end"` } + +// I don't like this repetition, but it fits the pattern... +type WebMetricMetric struct { + Url string `json:"url"` + Headers []WebMetricHeader `json:"headers,omitempty"` + Timeout int `json:"timeout,omitempty"` + JsonPath string `json:"jsonPath"` +} + +type WebMetricHeader struct { + Key string `json:"key"` + Value string `json:"value"` +} From eb42130a24abd1cb92ab991b212a98a48ba4087e Mon Sep 17 00:00:00 2001 From: Thomas Santerre Date: Tue, 3 Dec 2019 16:20:26 +0900 Subject: [PATCH 02/28] Wrote basic test, and fixed some functionality for an initial POC --- metricproviders/webmetric/webmetric.go | 30 ++++++++--- metricproviders/webmetric/webmetric_test.go | 60 +++++++++++++++++++++ 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/metricproviders/webmetric/webmetric.go b/metricproviders/webmetric/webmetric.go index 9b18551831..511a7547d8 100644 --- a/metricproviders/webmetric/webmetric.go +++ b/metricproviders/webmetric/webmetric.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "net/http" "net/url" + "strconv" "time" "k8s.io/client-go/util/jsonpath" @@ -58,6 +59,7 @@ func (p *Provider) Run(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric) v1alph if err != nil { return metricutil.MarkMeasurementError(measurement, err) } + log.Infof("URL: %v", request.URL) for _, header := range metric.Provider.WebMetric.Headers { request.Header.Set(header.Key, header.Value) @@ -69,17 +71,27 @@ func (p *Provider) Run(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric) v1alph return metricutil.MarkMeasurementError(measurement, err) } - // Parse Response + fmt.Printf("received response: %v", response) + + value, status, err := p.parseResponse(metric, response) + if err != nil || response.StatusCode != http.StatusOK { + return metricutil.MarkMeasurementError(measurement, err) + } + + measurement.Value = value + measurement.Phase = status + finishedTime := metav1.Now() + measurement.FinishedAt = &finishedTime return measurement } -func (p *Provider) parseResponse(metric v1alpha1.Metric, response http.Response) (string, v1alpha1.AnalysisPhase, error) { +func (p *Provider) parseResponse(metric v1alpha1.Metric, response *http.Response) (string, v1alpha1.AnalysisPhase, error) { var data interface{} bodyBytes, err := ioutil.ReadAll(response.Body) if err != nil { - log.Fatal(err) + return "", v1alpha1.AnalysisPhaseError, fmt.Errorf("Received no bytes in response: %v", err) } err = json.Unmarshal(bodyBytes, &data) @@ -90,15 +102,19 @@ func (p *Provider) parseResponse(metric v1alpha1.Metric, response http.Response) buf := new(bytes.Buffer) err = p.jsonParser.Execute(buf, data) if err != nil { - return "", v1alpha1.AnalysisPhaseError, fmt.Errorf("Could not parse JSON body: %s", err) + return "", v1alpha1.AnalysisPhaseError, fmt.Errorf("Could not find JSONPath in body: %s", err) } - out := buf.String() - status := p.evaluateResponse(metric, out) + outAsFloat, err := strconv.ParseFloat(buf.String(), 64) + if err != nil { + return "", v1alpha1.AnalysisPhaseError, fmt.Errorf("Could not convert response to a number: %s", err) + } + log.Infof("got value: %v", out) + status := p.evaluateResponse(metric, outAsFloat) return out, status, nil } -func (p *Provider) evaluateResponse(metric v1alpha1.Metric, result string) v1alpha1.AnalysisPhase { +func (p *Provider) evaluateResponse(metric v1alpha1.Metric, result float64) v1alpha1.AnalysisPhase { successCondition := false failCondition := false var err error diff --git a/metricproviders/webmetric/webmetric_test.go b/metricproviders/webmetric/webmetric_test.go index b08691b29c..159c191cff 100644 --- a/metricproviders/webmetric/webmetric_test.go +++ b/metricproviders/webmetric/webmetric_test.go @@ -1 +1,61 @@ package webmetric + +import ( + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +func TestRunSuccess(t *testing.T) { + input := ` + { + "key": [ + { + "key2": { + "value": 1 + } + } + ] + }` + + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("Content-Type", "application/json") + io.WriteString(rw, input) + })) + // Close the server when test finishes + defer server.Close() + + e := log.Entry{} + + metric := v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "result > 0", + FailureCondition: "result <= 0", + Provider: v1alpha1.MetricProvider{ + WebMetric: &v1alpha1.WebMetricMetric{ + Url: server.URL, + JsonPath: "{$.key[0].key2.value}", + }, + }, + } + + jp, err := NewWebMetricJsonParser(metric) + assert.NoError(t, err) + + p := NewWebMetricProvider(e, server.Client(), jp) + + measurement := p.Run(newAnalysisRun(), metric) + assert.NotNil(t, measurement.StartedAt) + assert.Equal(t, "1", measurement.Value) + assert.NotNil(t, measurement.FinishedAt) + assert.Equal(t, v1alpha1.AnalysisPhaseSuccessful, measurement.Phase) +} + +func newAnalysisRun() *v1alpha1.AnalysisRun { + return &v1alpha1.AnalysisRun{} +} From 56074a1eb78abe0ba25b8c00f3567b246e18194f Mon Sep 17 00:00:00 2001 From: Thomas Santerre Date: Tue, 3 Dec 2019 16:21:24 +0900 Subject: [PATCH 03/28] Clean up debug logging --- metricproviders/webmetric/webmetric.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/metricproviders/webmetric/webmetric.go b/metricproviders/webmetric/webmetric.go index 511a7547d8..4fb99860bc 100644 --- a/metricproviders/webmetric/webmetric.go +++ b/metricproviders/webmetric/webmetric.go @@ -59,7 +59,6 @@ func (p *Provider) Run(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric) v1alph if err != nil { return metricutil.MarkMeasurementError(measurement, err) } - log.Infof("URL: %v", request.URL) for _, header := range metric.Provider.WebMetric.Headers { request.Header.Set(header.Key, header.Value) @@ -71,8 +70,6 @@ func (p *Provider) Run(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric) v1alph return metricutil.MarkMeasurementError(measurement, err) } - fmt.Printf("received response: %v", response) - value, status, err := p.parseResponse(metric, response) if err != nil || response.StatusCode != http.StatusOK { return metricutil.MarkMeasurementError(measurement, err) @@ -109,7 +106,6 @@ func (p *Provider) parseResponse(metric v1alpha1.Metric, response *http.Response if err != nil { return "", v1alpha1.AnalysisPhaseError, fmt.Errorf("Could not convert response to a number: %s", err) } - log.Infof("got value: %v", out) status := p.evaluateResponse(metric, outAsFloat) return out, status, nil } From 94cd16b074b37e32efdc7e19f2c88db1028977e7 Mon Sep 17 00:00:00 2001 From: Thomas Santerre Date: Tue, 3 Dec 2019 16:32:56 +0900 Subject: [PATCH 04/28] Ran codegen --- pkg/apis/api-rules/violation_exceptions.list | 1 + .../rollouts/v1alpha1/openapi_generated.go | 79 ++++++++++++++++++- .../v1alpha1/zz_generated.deepcopy.go | 42 ++++++++++ 3 files changed, 121 insertions(+), 1 deletion(-) diff --git a/pkg/apis/api-rules/violation_exceptions.list b/pkg/apis/api-rules/violation_exceptions.list index c600d77805..dc408d79dc 100644 --- a/pkg/apis/api-rules/violation_exceptions.list +++ b/pkg/apis/api-rules/violation_exceptions.list @@ -1 +1,2 @@ +API rule violation: names_match,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,MetricProvider,WebMetric API rule violation: names_match,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutStatus,HPAReplicas diff --git a/pkg/apis/rollouts/v1alpha1/openapi_generated.go b/pkg/apis/rollouts/v1alpha1/openapi_generated.go index c13336ae37..2f7de4c234 100644 --- a/pkg/apis/rollouts/v1alpha1/openapi_generated.go +++ b/pkg/apis/rollouts/v1alpha1/openapi_generated.go @@ -76,6 +76,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.ScopeDetail": schema_pkg_apis_rollouts_v1alpha1_ScopeDetail(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.TemplateSpec": schema_pkg_apis_rollouts_v1alpha1_TemplateSpec(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.TemplateStatus": schema_pkg_apis_rollouts_v1alpha1_TemplateStatus(ref), + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WebMetricHeader": schema_pkg_apis_rollouts_v1alpha1_WebMetricHeader(ref), + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WebMetricMetric": schema_pkg_apis_rollouts_v1alpha1_WebMetricMetric(ref), } } @@ -1382,6 +1384,11 @@ func schema_pkg_apis_rollouts_v1alpha1_MetricProvider(ref common.ReferenceCallba Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.KayentaMetric"), }, }, + "webmetric": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WebMetricMetric"), + }, + }, "job": { SchemaProps: spec.SchemaProps{ Description: "Job specifies the job metric run", @@ -1392,7 +1399,7 @@ func schema_pkg_apis_rollouts_v1alpha1_MetricProvider(ref common.ReferenceCallba }, }, Dependencies: []string{ - "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.JobMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.KayentaMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.PrometheusMetric"}, + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.JobMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.KayentaMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.PrometheusMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WebMetricMetric"}, } } @@ -2325,3 +2332,73 @@ func schema_pkg_apis_rollouts_v1alpha1_TemplateStatus(ref common.ReferenceCallba "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } + +func schema_pkg_apis_rollouts_v1alpha1_WebMetricHeader(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"key", "value"}, + }, + }, + Dependencies: []string{}, + } +} + +func schema_pkg_apis_rollouts_v1alpha1_WebMetricMetric(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "I don't like this repetition, but it fits the pattern...", + Properties: map[string]spec.Schema{ + "url": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "headers": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WebMetricHeader"), + }, + }, + }, + }, + }, + "timeout": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int32", + }, + }, + "jsonPath": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"url", "jsonPath"}, + }, + }, + Dependencies: []string{ + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WebMetricHeader"}, + } +} diff --git a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go index d98ffbae07..0eb28dea95 100644 --- a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go @@ -780,6 +780,11 @@ func (in *MetricProvider) DeepCopyInto(out *MetricProvider) { *out = new(KayentaMetric) (*in).DeepCopyInto(*out) } + if in.WebMetric != nil { + in, out := &in.WebMetric, &out.WebMetric + *out = new(WebMetricMetric) + (*in).DeepCopyInto(*out) + } if in.Job != nil { in, out := &in.Job, &out.Job *out = new(JobMetric) @@ -1264,3 +1269,40 @@ func (in *TemplateStatus) DeepCopy() *TemplateStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WebMetricHeader) DeepCopyInto(out *WebMetricHeader) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebMetricHeader. +func (in *WebMetricHeader) DeepCopy() *WebMetricHeader { + if in == nil { + return nil + } + out := new(WebMetricHeader) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WebMetricMetric) DeepCopyInto(out *WebMetricMetric) { + *out = *in + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = make([]WebMetricHeader, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebMetricMetric. +func (in *WebMetricMetric) DeepCopy() *WebMetricMetric { + if in == nil { + return nil + } + out := new(WebMetricMetric) + in.DeepCopyInto(out) + return out +} From ff388e4425df0ec406ea82df44188386d978ea8e Mon Sep 17 00:00:00 2001 From: Thomas Santerre Date: Mon, 9 Dec 2019 11:23:38 +0900 Subject: [PATCH 05/28] Made minor fixes suggested in by Rollouts team: WebMetric naming updated, check for 2xx status code, minor naming changes for Go best practices --- manifests/crds/analysis-run-crd.yaml | 116 +++-- manifests/crds/analysis-template-crd.yaml | 116 +++-- manifests/crds/experiment-crd.yaml | 92 ++-- manifests/crds/rollout-crd.yaml | 98 ++-- manifests/install.yaml | 422 +++++++++++++----- manifests/namespace-install.yaml | 422 +++++++++++++----- metricproviders/metricproviders.go | 2 +- metricproviders/webmetric/webmetric.go | 24 +- metricproviders/webmetric/webmetric_test.go | 6 +- pkg/apis/api-rules/violation_exceptions.list | 1 - pkg/apis/rollouts/v1alpha1/analysis_types.go | 9 +- .../rollouts/v1alpha1/openapi_generated.go | 61 ++- .../v1alpha1/zz_generated.deepcopy.go | 32 +- 13 files changed, 966 insertions(+), 435 deletions(-) diff --git a/manifests/crds/analysis-run-crd.yaml b/manifests/crds/analysis-run-crd.yaml index 7748cc4514..a4d2761a01 100644 --- a/manifests/crds/analysis-run-crd.yaml +++ b/manifests/crds/analysis-run-crd.yaml @@ -1,6 +1,8 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: + annotations: + controller-gen.kubebuilder.io/version: (unknown) name: analysisruns.argoproj.io spec: additionalPrinterColumns: @@ -500,8 +502,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -513,8 +516,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -548,8 +552,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -561,8 +566,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -600,8 +606,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -622,8 +629,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -684,8 +692,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -706,8 +715,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -799,8 +809,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -821,8 +832,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1025,8 +1037,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1038,8 +1051,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1073,8 +1087,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1086,8 +1101,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1125,8 +1141,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1147,8 +1164,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1209,8 +1227,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1231,8 +1250,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1324,8 +1344,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1346,8 +1367,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1554,8 +1576,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1567,8 +1590,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1602,8 +1626,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1615,8 +1640,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1654,8 +1680,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1676,8 +1703,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1738,8 +1766,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1760,8 +1789,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1853,8 +1883,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1875,8 +1906,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -2598,6 +2630,30 @@ spec: query: type: string type: object + web: + properties: + headers: + items: + properties: + key: + type: string + value: + type: string + required: + - key + - value + type: object + type: array + jsonPath: + type: string + timeout: + type: integer + url: + type: string + required: + - jsonPath + - url + type: object type: object successCondition: type: string diff --git a/manifests/crds/analysis-template-crd.yaml b/manifests/crds/analysis-template-crd.yaml index 624288378f..4172c97b3d 100644 --- a/manifests/crds/analysis-template-crd.yaml +++ b/manifests/crds/analysis-template-crd.yaml @@ -1,6 +1,8 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: + annotations: + controller-gen.kubebuilder.io/version: (unknown) name: analysistemplates.argoproj.io spec: group: argoproj.io @@ -494,8 +496,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -507,8 +510,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -542,8 +546,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -555,8 +560,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -594,8 +600,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -616,8 +623,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -678,8 +686,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -700,8 +709,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -793,8 +803,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -815,8 +826,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1019,8 +1031,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1032,8 +1045,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1067,8 +1081,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1080,8 +1095,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1119,8 +1135,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1141,8 +1158,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1203,8 +1221,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1225,8 +1244,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1318,8 +1338,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1340,8 +1361,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1548,8 +1570,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1561,8 +1584,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1596,8 +1620,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1609,8 +1634,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1648,8 +1674,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1670,8 +1697,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1732,8 +1760,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1754,8 +1783,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1847,8 +1877,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1869,8 +1900,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -2592,6 +2624,30 @@ spec: query: type: string type: object + web: + properties: + headers: + items: + properties: + key: + type: string + value: + type: string + required: + - key + - value + type: object + type: array + jsonPath: + type: string + timeout: + type: integer + url: + type: string + required: + - jsonPath + - url + type: object type: object successCondition: type: string diff --git a/manifests/crds/experiment-crd.yaml b/manifests/crds/experiment-crd.yaml index 831ca57c3d..382d9e1c23 100644 --- a/manifests/crds/experiment-crd.yaml +++ b/manifests/crds/experiment-crd.yaml @@ -1,6 +1,8 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: + annotations: + controller-gen.kubebuilder.io/version: (unknown) name: experiments.argoproj.io spec: additionalPrinterColumns: @@ -487,8 +489,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -500,8 +503,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -535,8 +539,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -548,8 +553,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -587,8 +593,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -609,8 +616,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -671,8 +679,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -693,8 +702,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -786,8 +796,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -808,8 +819,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1012,8 +1024,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1025,8 +1038,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1060,8 +1074,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1073,8 +1088,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1112,8 +1128,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1134,8 +1151,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1196,8 +1214,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1218,8 +1237,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1311,8 +1331,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1333,8 +1354,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1541,8 +1563,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1554,8 +1577,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1589,8 +1613,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1602,8 +1627,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1641,8 +1667,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1663,8 +1690,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1725,8 +1753,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1747,8 +1776,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1840,8 +1870,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1862,8 +1893,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object diff --git a/manifests/crds/rollout-crd.yaml b/manifests/crds/rollout-crd.yaml index 4dfffe0193..995fd997ab 100644 --- a/manifests/crds/rollout-crd.yaml +++ b/manifests/crds/rollout-crd.yaml @@ -1,6 +1,8 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: + annotations: + controller-gen.kubebuilder.io/version: (unknown) name: rollouts.argoproj.io spec: additionalPrinterColumns: @@ -138,12 +140,14 @@ spec: type: string maxSurge: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true maxUnavailable: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true steps: items: properties: @@ -663,8 +667,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -676,8 +681,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -711,8 +717,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -724,8 +731,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -763,8 +771,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -785,8 +794,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -847,8 +857,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -869,8 +880,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -962,8 +974,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -984,8 +997,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1188,8 +1202,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1201,8 +1216,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1236,8 +1252,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1249,8 +1266,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1288,8 +1306,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1310,8 +1329,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1372,8 +1392,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1394,8 +1415,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1487,8 +1509,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1509,8 +1532,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1717,8 +1741,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1730,8 +1755,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1765,8 +1791,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1778,8 +1805,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1817,8 +1845,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1839,8 +1868,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1901,8 +1931,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1923,8 +1954,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -2016,8 +2048,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -2038,8 +2071,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object diff --git a/manifests/install.yaml b/manifests/install.yaml index ed9eab7d5c..eeb37e5683 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -2,6 +2,8 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: + annotations: + controller-gen.kubebuilder.io/version: (unknown) name: analysisruns.argoproj.io spec: additionalPrinterColumns: @@ -501,8 +503,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -514,8 +517,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -549,8 +553,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -562,8 +567,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -601,8 +607,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -623,8 +630,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -685,8 +693,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -707,8 +716,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -800,8 +810,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -822,8 +833,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1026,8 +1038,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1039,8 +1052,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1074,8 +1088,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1087,8 +1102,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1126,8 +1142,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1148,8 +1165,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1210,8 +1228,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1232,8 +1251,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1325,8 +1345,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1347,8 +1368,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1555,8 +1577,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1568,8 +1591,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1603,8 +1627,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1616,8 +1641,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1655,8 +1681,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1677,8 +1704,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1739,8 +1767,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1761,8 +1790,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1854,8 +1884,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1876,8 +1907,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -2599,6 +2631,30 @@ spec: query: type: string type: object + web: + properties: + headers: + items: + properties: + key: + type: string + value: + type: string + required: + - key + - value + type: object + type: array + jsonPath: + type: string + timeout: + type: integer + url: + type: string + required: + - jsonPath + - url + type: object type: object successCondition: type: string @@ -2691,6 +2747,8 @@ spec: apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: + annotations: + controller-gen.kubebuilder.io/version: (unknown) name: analysistemplates.argoproj.io spec: group: argoproj.io @@ -3184,8 +3242,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3197,8 +3256,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -3232,8 +3292,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3245,8 +3306,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -3284,8 +3346,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3306,8 +3369,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -3368,8 +3432,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3390,8 +3455,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -3483,8 +3549,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3505,8 +3572,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -3709,8 +3777,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3722,8 +3791,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -3757,8 +3827,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3770,8 +3841,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -3809,8 +3881,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3831,8 +3904,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -3893,8 +3967,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3915,8 +3990,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -4008,8 +4084,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -4030,8 +4107,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -4238,8 +4316,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -4251,8 +4330,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -4286,8 +4366,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -4299,8 +4380,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -4338,8 +4420,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -4360,8 +4443,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -4422,8 +4506,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -4444,8 +4529,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -4537,8 +4623,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -4559,8 +4646,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -5282,6 +5370,30 @@ spec: query: type: string type: object + web: + properties: + headers: + items: + properties: + key: + type: string + value: + type: string + required: + - key + - value + type: object + type: array + jsonPath: + type: string + timeout: + type: integer + url: + type: string + required: + - jsonPath + - url + type: object type: object successCondition: type: string @@ -5305,6 +5417,8 @@ spec: apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: + annotations: + controller-gen.kubebuilder.io/version: (unknown) name: experiments.argoproj.io spec: additionalPrinterColumns: @@ -5791,8 +5905,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -5804,8 +5919,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -5839,8 +5955,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -5852,8 +5969,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -5891,8 +6009,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -5913,8 +6032,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -5975,8 +6095,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -5997,8 +6118,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6090,8 +6212,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6112,8 +6235,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6316,8 +6440,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6329,8 +6454,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6364,8 +6490,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6377,8 +6504,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6416,8 +6544,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6438,8 +6567,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6500,8 +6630,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6522,8 +6653,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6615,8 +6747,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6637,8 +6770,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6845,8 +6979,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6858,8 +6993,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6893,8 +7029,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6906,8 +7043,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6945,8 +7083,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6967,8 +7106,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -7029,8 +7169,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -7051,8 +7192,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -7144,8 +7286,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -7166,8 +7309,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -7901,6 +8045,8 @@ spec: apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: + annotations: + controller-gen.kubebuilder.io/version: (unknown) name: rollouts.argoproj.io spec: additionalPrinterColumns: @@ -8038,12 +8184,14 @@ spec: type: string maxSurge: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true maxUnavailable: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true steps: items: properties: @@ -8563,8 +8711,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -8576,8 +8725,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -8611,8 +8761,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -8624,8 +8775,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -8663,8 +8815,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -8685,8 +8838,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -8747,8 +8901,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -8769,8 +8924,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -8862,8 +9018,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -8884,8 +9041,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9088,8 +9246,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9101,8 +9260,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9136,8 +9296,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9149,8 +9310,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9188,8 +9350,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9210,8 +9373,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9272,8 +9436,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9294,8 +9459,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9387,8 +9553,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9409,8 +9576,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9617,8 +9785,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9630,8 +9799,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9665,8 +9835,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9678,8 +9849,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9717,8 +9889,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9739,8 +9912,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9801,8 +9975,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9823,8 +9998,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9916,8 +10092,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9938,8 +10115,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index f03561c93c..28f28175e6 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -2,6 +2,8 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: + annotations: + controller-gen.kubebuilder.io/version: (unknown) name: analysisruns.argoproj.io spec: additionalPrinterColumns: @@ -501,8 +503,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -514,8 +517,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -549,8 +553,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -562,8 +567,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -601,8 +607,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -623,8 +630,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -685,8 +693,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -707,8 +716,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -800,8 +810,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -822,8 +833,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1026,8 +1038,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1039,8 +1052,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1074,8 +1088,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1087,8 +1102,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1126,8 +1142,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1148,8 +1165,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1210,8 +1228,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1232,8 +1251,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1325,8 +1345,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1347,8 +1368,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1555,8 +1577,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1568,8 +1591,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1603,8 +1627,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1616,8 +1641,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1655,8 +1681,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1677,8 +1704,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1739,8 +1767,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1761,8 +1790,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -1854,8 +1884,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -1876,8 +1907,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -2599,6 +2631,30 @@ spec: query: type: string type: object + web: + properties: + headers: + items: + properties: + key: + type: string + value: + type: string + required: + - key + - value + type: object + type: array + jsonPath: + type: string + timeout: + type: integer + url: + type: string + required: + - jsonPath + - url + type: object type: object successCondition: type: string @@ -2691,6 +2747,8 @@ spec: apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: + annotations: + controller-gen.kubebuilder.io/version: (unknown) name: analysistemplates.argoproj.io spec: group: argoproj.io @@ -3184,8 +3242,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3197,8 +3256,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -3232,8 +3292,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3245,8 +3306,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -3284,8 +3346,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3306,8 +3369,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -3368,8 +3432,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3390,8 +3455,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -3483,8 +3549,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3505,8 +3572,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -3709,8 +3777,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3722,8 +3791,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -3757,8 +3827,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3770,8 +3841,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -3809,8 +3881,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3831,8 +3904,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -3893,8 +3967,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -3915,8 +3990,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -4008,8 +4084,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -4030,8 +4107,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -4238,8 +4316,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -4251,8 +4330,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -4286,8 +4366,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -4299,8 +4380,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -4338,8 +4420,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -4360,8 +4443,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -4422,8 +4506,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -4444,8 +4529,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -4537,8 +4623,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -4559,8 +4646,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -5282,6 +5370,30 @@ spec: query: type: string type: object + web: + properties: + headers: + items: + properties: + key: + type: string + value: + type: string + required: + - key + - value + type: object + type: array + jsonPath: + type: string + timeout: + type: integer + url: + type: string + required: + - jsonPath + - url + type: object type: object successCondition: type: string @@ -5305,6 +5417,8 @@ spec: apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: + annotations: + controller-gen.kubebuilder.io/version: (unknown) name: experiments.argoproj.io spec: additionalPrinterColumns: @@ -5791,8 +5905,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -5804,8 +5919,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -5839,8 +5955,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -5852,8 +5969,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -5891,8 +6009,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -5913,8 +6032,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -5975,8 +6095,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -5997,8 +6118,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6090,8 +6212,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6112,8 +6235,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6316,8 +6440,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6329,8 +6454,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6364,8 +6490,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6377,8 +6504,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6416,8 +6544,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6438,8 +6567,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6500,8 +6630,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6522,8 +6653,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6615,8 +6747,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6637,8 +6770,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6845,8 +6979,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6858,8 +6993,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6893,8 +7029,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6906,8 +7043,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -6945,8 +7083,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -6967,8 +7106,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -7029,8 +7169,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -7051,8 +7192,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -7144,8 +7286,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -7166,8 +7309,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -7901,6 +8045,8 @@ spec: apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: + annotations: + controller-gen.kubebuilder.io/version: (unknown) name: rollouts.argoproj.io spec: additionalPrinterColumns: @@ -8038,12 +8184,14 @@ spec: type: string maxSurge: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true maxUnavailable: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true steps: items: properties: @@ -8563,8 +8711,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -8576,8 +8725,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -8611,8 +8761,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -8624,8 +8775,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -8663,8 +8815,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -8685,8 +8838,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -8747,8 +8901,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -8769,8 +8924,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -8862,8 +9018,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -8884,8 +9041,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9088,8 +9246,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9101,8 +9260,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9136,8 +9296,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9149,8 +9310,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9188,8 +9350,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9210,8 +9373,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9272,8 +9436,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9294,8 +9459,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9387,8 +9553,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9409,8 +9576,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9617,8 +9785,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9630,8 +9799,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9665,8 +9835,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9678,8 +9849,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9717,8 +9889,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9739,8 +9912,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9801,8 +9975,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9823,8 +9998,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object @@ -9916,8 +10092,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true scheme: type: string required: @@ -9938,8 +10115,9 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string + x-kubernetes-int-or-string: true required: - port type: object diff --git a/metricproviders/metricproviders.go b/metricproviders/metricproviders.go index d32e1c8378..e7795e226a 100644 --- a/metricproviders/metricproviders.go +++ b/metricproviders/metricproviders.go @@ -51,7 +51,7 @@ func (f *ProviderFactory) NewProvider(logCtx log.Entry, metric v1alpha1.Metric) } else if metric.Provider.Kayenta != nil { c := kayenta.NewHttpClient() return kayenta.NewKayentaProvider(logCtx, c), nil - } else if metric.Provider.WebMetric != nil { + } else if metric.Provider.Web != nil { c := webmetric.NewWebMetricHttpClient(metric) p, err := webmetric.NewWebMetricJsonParser(metric) if err != nil { diff --git a/metricproviders/webmetric/webmetric.go b/metricproviders/webmetric/webmetric.go index 4fb99860bc..9856ef1b9c 100644 --- a/metricproviders/webmetric/webmetric.go +++ b/metricproviders/webmetric/webmetric.go @@ -10,14 +10,12 @@ import ( "strconv" "time" - "k8s.io/client-go/util/jsonpath" - + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" "github.com/argoproj/argo-rollouts/utils/evaluate" metricutil "github.com/argoproj/argo-rollouts/utils/metric" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/util/jsonpath" ) const ( @@ -52,21 +50,21 @@ func (p *Provider) Run(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric) v1alph // Create request request := &http.Request{ - Method: "GET", + Method: "GET", // TODO maybe make this configurable....also implies we will need body templates } - request.URL, err = url.Parse(metric.Provider.WebMetric.Url) + request.URL, err = url.Parse(metric.Provider.Web.URL) if err != nil { return metricutil.MarkMeasurementError(measurement, err) } - for _, header := range metric.Provider.WebMetric.Headers { + for _, header := range metric.Provider.Web.Headers { request.Header.Set(header.Key, header.Value) } // Send Request response, err := p.client.Do(request) - if err != nil || response.StatusCode != http.StatusOK { + if err != nil || response.StatusCode < 200 || response.StatusCode >= 300 { return metricutil.MarkMeasurementError(measurement, err) } @@ -175,11 +173,13 @@ func NewWebMetricHttpClient(metric v1alpha1.Metric) *http.Client { timeout time.Duration ) - if metric.Provider.WebMetric.Timeout <= 0 { + // Using a default timeout of 10 seconds + if metric.Provider.Web.Timeout <= 0 { timeout = time.Duration(10) * time.Second } else { - timeout = time.Duration(metric.Provider.WebMetric.Timeout) * time.Second + timeout = time.Duration(metric.Provider.Web.Timeout) * time.Second } + c := &http.Client{ Timeout: timeout, } @@ -189,7 +189,7 @@ func NewWebMetricHttpClient(metric v1alpha1.Metric) *http.Client { func NewWebMetricJsonParser(metric v1alpha1.Metric) (*jsonpath.JSONPath, error) { jsonParser := jsonpath.New("metrics") - err := jsonParser.Parse(metric.Provider.WebMetric.JsonPath) + err := jsonParser.Parse(metric.Provider.Web.JSONPath) return jsonParser, err } diff --git a/metricproviders/webmetric/webmetric_test.go b/metricproviders/webmetric/webmetric_test.go index 159c191cff..3a8a38b05c 100644 --- a/metricproviders/webmetric/webmetric_test.go +++ b/metricproviders/webmetric/webmetric_test.go @@ -37,9 +37,9 @@ func TestRunSuccess(t *testing.T) { SuccessCondition: "result > 0", FailureCondition: "result <= 0", Provider: v1alpha1.MetricProvider{ - WebMetric: &v1alpha1.WebMetricMetric{ - Url: server.URL, - JsonPath: "{$.key[0].key2.value}", + Web: &v1alpha1.WebMetric{ + URL: server.URL, + JSONPath: "{$.key[0].key2.value}", }, }, } diff --git a/pkg/apis/api-rules/violation_exceptions.list b/pkg/apis/api-rules/violation_exceptions.list index dc408d79dc..c600d77805 100644 --- a/pkg/apis/api-rules/violation_exceptions.list +++ b/pkg/apis/api-rules/violation_exceptions.list @@ -1,2 +1 @@ -API rule violation: names_match,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,MetricProvider,WebMetric API rule violation: names_match,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutStatus,HPAReplicas diff --git a/pkg/apis/rollouts/v1alpha1/analysis_types.go b/pkg/apis/rollouts/v1alpha1/analysis_types.go index 8e4db4280c..fb6f5df02a 100644 --- a/pkg/apis/rollouts/v1alpha1/analysis_types.go +++ b/pkg/apis/rollouts/v1alpha1/analysis_types.go @@ -97,7 +97,7 @@ type MetricProvider struct { // Prometheus specifies the prometheus metric to query Prometheus *PrometheusMetric `json:"prometheus,omitempty"` Kayenta *KayentaMetric `json:"kayenta,omitempty"` - WebMetric *WebMetricMetric `json:"webmetric,omitempty"` + Web *WebMetric `json:"web,omitempty"` // Job specifies the job metric run Job *JobMetric `json:"job,omitempty"` } @@ -269,12 +269,11 @@ type ScopeDetail struct { End string `json:"end"` } -// I don't like this repetition, but it fits the pattern... -type WebMetricMetric struct { - Url string `json:"url"` +type WebMetric struct { + URL string `json:"url"` Headers []WebMetricHeader `json:"headers,omitempty"` Timeout int `json:"timeout,omitempty"` - JsonPath string `json:"jsonPath"` + JSONPath string `json:"jsonPath"` } type WebMetricHeader struct { diff --git a/pkg/apis/rollouts/v1alpha1/openapi_generated.go b/pkg/apis/rollouts/v1alpha1/openapi_generated.go index 2f7de4c234..77ea037b56 100644 --- a/pkg/apis/rollouts/v1alpha1/openapi_generated.go +++ b/pkg/apis/rollouts/v1alpha1/openapi_generated.go @@ -76,8 +76,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.ScopeDetail": schema_pkg_apis_rollouts_v1alpha1_ScopeDetail(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.TemplateSpec": schema_pkg_apis_rollouts_v1alpha1_TemplateSpec(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.TemplateStatus": schema_pkg_apis_rollouts_v1alpha1_TemplateStatus(ref), + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WebMetric": schema_pkg_apis_rollouts_v1alpha1_WebMetric(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WebMetricHeader": schema_pkg_apis_rollouts_v1alpha1_WebMetricHeader(ref), - "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WebMetricMetric": schema_pkg_apis_rollouts_v1alpha1_WebMetricMetric(ref), } } @@ -1384,9 +1384,9 @@ func schema_pkg_apis_rollouts_v1alpha1_MetricProvider(ref common.ReferenceCallba Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.KayentaMetric"), }, }, - "webmetric": { + "web": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WebMetricMetric"), + Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WebMetric"), }, }, "job": { @@ -1399,7 +1399,7 @@ func schema_pkg_apis_rollouts_v1alpha1_MetricProvider(ref common.ReferenceCallba }, }, Dependencies: []string{ - "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.JobMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.KayentaMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.PrometheusMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WebMetricMetric"}, + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.JobMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.KayentaMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.PrometheusMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WebMetric"}, } } @@ -2333,36 +2333,10 @@ func schema_pkg_apis_rollouts_v1alpha1_TemplateStatus(ref common.ReferenceCallba } } -func schema_pkg_apis_rollouts_v1alpha1_WebMetricHeader(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Properties: map[string]spec.Schema{ - "key": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "value": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"key", "value"}, - }, - }, - Dependencies: []string{}, - } -} - -func schema_pkg_apis_rollouts_v1alpha1_WebMetricMetric(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_rollouts_v1alpha1_WebMetric(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "I don't like this repetition, but it fits the pattern...", Properties: map[string]spec.Schema{ "url": { SchemaProps: spec.SchemaProps{ @@ -2402,3 +2376,28 @@ func schema_pkg_apis_rollouts_v1alpha1_WebMetricMetric(ref common.ReferenceCallb "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WebMetricHeader"}, } } + +func schema_pkg_apis_rollouts_v1alpha1_WebMetricHeader(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"key", "value"}, + }, + }, + Dependencies: []string{}, + } +} diff --git a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go index 0eb28dea95..43b342f3b0 100644 --- a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go @@ -780,9 +780,9 @@ func (in *MetricProvider) DeepCopyInto(out *MetricProvider) { *out = new(KayentaMetric) (*in).DeepCopyInto(*out) } - if in.WebMetric != nil { - in, out := &in.WebMetric, &out.WebMetric - *out = new(WebMetricMetric) + if in.Web != nil { + in, out := &in.Web, &out.Web + *out = new(WebMetric) (*in).DeepCopyInto(*out) } if in.Job != nil { @@ -1271,38 +1271,38 @@ func (in *TemplateStatus) DeepCopy() *TemplateStatus { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WebMetricHeader) DeepCopyInto(out *WebMetricHeader) { +func (in *WebMetric) DeepCopyInto(out *WebMetric) { *out = *in + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = make([]WebMetricHeader, len(*in)) + copy(*out, *in) + } return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebMetricHeader. -func (in *WebMetricHeader) DeepCopy() *WebMetricHeader { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebMetric. +func (in *WebMetric) DeepCopy() *WebMetric { if in == nil { return nil } - out := new(WebMetricHeader) + out := new(WebMetric) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WebMetricMetric) DeepCopyInto(out *WebMetricMetric) { +func (in *WebMetricHeader) DeepCopyInto(out *WebMetricHeader) { *out = *in - if in.Headers != nil { - in, out := &in.Headers, &out.Headers - *out = make([]WebMetricHeader, len(*in)) - copy(*out, *in) - } return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebMetricMetric. -func (in *WebMetricMetric) DeepCopy() *WebMetricMetric { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebMetricHeader. +func (in *WebMetricHeader) DeepCopy() *WebMetricHeader { if in == nil { return nil } - out := new(WebMetricMetric) + out := new(WebMetricHeader) in.DeepCopyInto(out) return out } From 9eac5a3a5bf4c4942f8c5a64b308b991b04dbbb7 Mon Sep 17 00:00:00 2001 From: Thomas Santerre Date: Mon, 9 Dec 2019 18:40:21 +0900 Subject: [PATCH 06/28] Smarter logic for detecting type in the JSON response. Also parameterized/extended test suite. --- metricproviders/webmetric/webmetric.go | 38 +++- metricproviders/webmetric/webmetric_test.go | 200 ++++++++++++++++---- 2 files changed, 195 insertions(+), 43 deletions(-) diff --git a/metricproviders/webmetric/webmetric.go b/metricproviders/webmetric/webmetric.go index 9856ef1b9c..167890a6c5 100644 --- a/metricproviders/webmetric/webmetric.go +++ b/metricproviders/webmetric/webmetric.go @@ -100,15 +100,15 @@ func (p *Provider) parseResponse(metric v1alpha1.Metric, response *http.Response return "", v1alpha1.AnalysisPhaseError, fmt.Errorf("Could not find JSONPath in body: %s", err) } out := buf.String() - outAsFloat, err := strconv.ParseFloat(buf.String(), 64) - if err != nil { - return "", v1alpha1.AnalysisPhaseError, fmt.Errorf("Could not convert response to a number: %s", err) - } - status := p.evaluateResponse(metric, outAsFloat) + + // Try to get the right primitive + outInterface := parsePrimitiveFromString(out) + + status := p.evaluateResponse(metric, outInterface) return out, status, nil } -func (p *Provider) evaluateResponse(metric v1alpha1.Metric, result float64) v1alpha1.AnalysisPhase { +func (p *Provider) evaluateResponse(metric v1alpha1.Metric, result interface{}) v1alpha1.AnalysisPhase { successCondition := false failCondition := false var err error @@ -201,3 +201,29 @@ func NewWebMetricProvider(logCtx log.Entry, client *http.Client, jsonParser *jso jsonParser: jsonParser, } } + +func parsePrimitiveFromString(in string) interface{} { + // Chain ordering as follows: + // int -> float -> bool -> string + + // 64 bit Int conversion + inAsInt, err := strconv.ParseInt(in, 10, 64) + if err == nil { + return inAsInt + } + + // Float conversion + inAsFloat, err := strconv.ParseFloat(in, 64) + if err == nil { + return inAsFloat + } + + // Bool conversion + inAsBool, err := strconv.ParseBool(in) + if err == nil { + return inAsBool + } + + // Else + return in +} diff --git a/metricproviders/webmetric/webmetric_test.go b/metricproviders/webmetric/webmetric_test.go index 3a8a38b05c..91faa6d727 100644 --- a/metricproviders/webmetric/webmetric_test.go +++ b/metricproviders/webmetric/webmetric_test.go @@ -11,51 +11,177 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRunSuccess(t *testing.T) { - input := ` - { - "key": [ - { - "key2": { - "value": 1 - } - } - ] - }` - - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - rw.Header().Set("Content-Type", "application/json") - io.WriteString(rw, input) - })) - // Close the server when test finishes - defer server.Close() - - e := log.Entry{} - - metric := v1alpha1.Metric{ - Name: "foo", - SuccessCondition: "result > 0", - FailureCondition: "result <= 0", - Provider: v1alpha1.MetricProvider{ - Web: &v1alpha1.WebMetric{ - URL: server.URL, - JSONPath: "{$.key[0].key2.value}", +func TestRunSuite(t *testing.T) { + // Test Cases + var tests = []struct { + webServerResponse string + metric v1alpha1.Metric + expectedValue string + expectedPhase v1alpha1.AnalysisPhase + }{ + // When_numberReturnedInJson_And_MatchesConditions_Then_Succeed + { + webServerResponse: `{"key": [{"key2": {"value": 1}}]}`, + metric: v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "result > 0", + FailureCondition: "result <= 0", + Provider: v1alpha1.MetricProvider{ + Web: &v1alpha1.WebMetric{ + // URL: server.URL, + JSONPath: "{$.key[0].key2.value}", + }, + }, }, + expectedValue: "1", + expectedPhase: v1alpha1.AnalysisPhaseSuccessful, + }, + // When_numberReturnedInJson_And_DoesNotMatcheConditions_Then_Failure + { + + webServerResponse: `{"key": [{"key2": {"value": 0}}]}`, + metric: v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "result > 0", + FailureCondition: "result <= 0", + Provider: v1alpha1.MetricProvider{ + Web: &v1alpha1.WebMetric{ + // URL: server.URL, + JSONPath: "{$.key[0].key2.value}", + }, + }, + }, + expectedValue: "0", + expectedPhase: v1alpha1.AnalysisPhaseFailed, + }, + // When_floatReturnedInJson_And_MatchesConditions_Then_Success + { + webServerResponse: `{"key": [{"key2": {"value": 1.1}}]}`, + metric: v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "result > 0", + FailureCondition: "result <= 0", + Provider: v1alpha1.MetricProvider{ + Web: &v1alpha1.WebMetric{ + // URL: server.URL, + JSONPath: "{$.key[0].key2.value}", + }, + }, + }, + expectedValue: "1.1", + expectedPhase: v1alpha1.AnalysisPhaseSuccessful, + }, + // When_floatReturnedInJson_And_DoesNotMatchConditions_Then_Failure + { + webServerResponse: `{"key": [{"key2": {"value": -1.1}}]}`, + metric: v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "result > 0", + FailureCondition: "result <= 0", + Provider: v1alpha1.MetricProvider{ + Web: &v1alpha1.WebMetric{ + // URL: server.URL, + JSONPath: "{$.key[0].key2.value}", + }, + }, + }, + expectedValue: "-1.1", + expectedPhase: v1alpha1.AnalysisPhaseFailed, + }, + // When_stringReturnedInJson_And_MatchesConditions_Then_Succeed + { + webServerResponse: `{"key": [{"key2": {"value": "true"}}]}`, + metric: v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "true", + FailureCondition: "false", + Provider: v1alpha1.MetricProvider{ + Web: &v1alpha1.WebMetric{ + // URL: server.URL, + JSONPath: "{$.key[0].key2.value}", + }, + }, + }, + expectedValue: "true", + expectedPhase: v1alpha1.AnalysisPhaseSuccessful, + }, + // When_stringReturnedInJson_And_DoesNotMatchConditions_Then_Fail + { + webServerResponse: `{"key": [{"key2": {"value": "true"}}]}`, + metric: v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "true", + FailureCondition: "true", + Provider: v1alpha1.MetricProvider{ + Web: &v1alpha1.WebMetric{ + // URL: server.URL, + JSONPath: "{$.key[0].key2.value}", + }, + }, + }, + expectedValue: "true", + expectedPhase: v1alpha1.AnalysisPhaseFailed, }, } - jp, err := NewWebMetricJsonParser(metric) - assert.NoError(t, err) + // Run + + for _, test := range tests { + // Server setup with response + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("Content-Type", "application/json") + io.WriteString(rw, test.webServerResponse) + })) + defer server.Close() - p := NewWebMetricProvider(e, server.Client(), jp) + // Need to set this dynamically... + test.metric.Provider.Web.URL = server.URL - measurement := p.Run(newAnalysisRun(), metric) - assert.NotNil(t, measurement.StartedAt) - assert.Equal(t, "1", measurement.Value) - assert.NotNil(t, measurement.FinishedAt) - assert.Equal(t, v1alpha1.AnalysisPhaseSuccessful, measurement.Phase) + logCtx := log.WithField("test", "test") + + jsonparser, err := NewWebMetricJsonParser(test.metric) + assert.NoError(t, err) + provider := NewWebMetricProvider(*logCtx, server.Client(), jsonparser) + + // Get our result + measurement := provider.Run(newAnalysisRun(), test.metric) + + // Common Asserts + assert.NotNil(t, measurement) + assert.Equal(t, string(test.expectedPhase), string(measurement.Phase)) + + // Phase specific cases + switch test.expectedPhase { + case v1alpha1.AnalysisPhaseSuccessful: + assert.NotNil(t, measurement.StartedAt) + assert.Equal(t, test.expectedValue, measurement.Value) + assert.NotNil(t, measurement.FinishedAt) + case v1alpha1.AnalysisPhaseFailed: + assert.NotNil(t, measurement.StartedAt) + assert.Equal(t, test.expectedValue, measurement.Value) + assert.NotNil(t, measurement.FinishedAt) + } + + } } func newAnalysisRun() *v1alpha1.AnalysisRun { return &v1alpha1.AnalysisRun{} } + +func TestParsePrimitiveSuite(t *testing.T) { + var tests = []struct { + in string + out interface{} + }{ + {"1", int64(1)}, + {"true", true}, + {"1.1", float64(1.1)}, + {"String", "String"}, + } + + for _, test := range tests { + result := parsePrimitiveFromString(test.in) + assert.Equal(t, test.out, result) + } +} From 3fa6f7117d7a8596250726dd92f9d94188717ca6 Mon Sep 17 00:00:00 2001 From: dthomson25 Date: Mon, 2 Dec 2019 20:42:46 -0800 Subject: [PATCH 07/28] Fix Infinite loop with PreviewReplicaCount set (#308) * Fix Infinite loop with PreviewReplicaCount set * Use xlarge instead of large --- .circleci/config.yml | 2 +- rollout/bluegreen.go | 32 ++++++------- rollout/bluegreen_test.go | 99 +++++++++++++++++++++++++++++++++++++++ rollout/replicaset.go | 12 ----- 4 files changed, 114 insertions(+), 31 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b5d387aa3e..83768d0d48 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ jobs: docker: # CircleCI Go images available at: https://hub.docker.com/r/circleci/golang/ - image: circleci/golang:1.13.1 - resource_class: large + resource_class: xlarge environment: TEST_RESULTS: /tmp/test-results steps: diff --git a/rollout/bluegreen.go b/rollout/bluegreen.go index 65a1f7e43d..e9fd8358d1 100644 --- a/rollout/bluegreen.go +++ b/rollout/bluegreen.go @@ -262,15 +262,23 @@ func (c *RolloutController) syncRolloutStatusBlueGreen(previewSvc *corev1.Servic func calculateScaleUpPreviewCheckPoint(roCtx *blueGreenContext, activeRS *appsv1.ReplicaSet) bool { r := roCtx.Rollout() newRS := roCtx.NewRS() - newRSAvailableCount := replicasetutil.GetAvailableReplicaCountForReplicaSets([]*appsv1.ReplicaSet{newRS}) - if r.Spec.Strategy.BlueGreen.PreviewReplicaCount != nil && newRSAvailableCount == *r.Spec.Strategy.BlueGreen.PreviewReplicaCount { - return true - } else if reconcileBlueGreenTemplateChange(roCtx) { + + if reconcileBlueGreenTemplateChange(roCtx) || r.Spec.Strategy.BlueGreen.PreviewReplicaCount == nil { return false - } else if newRS != nil && activeRS != nil && activeRS.Name == newRS.Name { + } + + if newRS == nil || activeRS == nil || activeRS.Name == newRS.Name { return false } - return r.Status.BlueGreen.ScaleUpPreviewCheckPoint + + // Once the ScaleUpPreviewCheckPoint is set to true, the rollout should keep that value until + // the newRS becomes the new activeRS or there is a template change. + if r.Status.BlueGreen.ScaleUpPreviewCheckPoint { + return r.Status.BlueGreen.ScaleUpPreviewCheckPoint + } + + newRSAvailableCount := replicasetutil.GetAvailableReplicaCountForReplicaSets([]*appsv1.ReplicaSet{newRS}) + return newRSAvailableCount == *r.Spec.Strategy.BlueGreen.PreviewReplicaCount } // Should run only on scaling events and not during the normal rollout process. @@ -303,18 +311,6 @@ func (c *RolloutController) scaleBlueGreen(rollout *v1alpha1.Rollout, newRS *app } } - previewRS, _ := replicasetutil.GetReplicaSetByTemplateHash(allRS, rollout.Status.BlueGreen.PreviewSelector) - if previewRS != nil { - previewReplicas := rolloutReplicas - if rollout.Spec.Strategy.BlueGreen.PreviewReplicaCount != nil && !rollout.Status.BlueGreen.ScaleUpPreviewCheckPoint { - previewReplicas = *rollout.Spec.Strategy.BlueGreen.PreviewReplicaCount - } - if *(previewRS.Spec.Replicas) != previewReplicas { - _, _, err := c.scaleReplicaSetAndRecordEvent(previewRS, previewReplicas, rollout) - return err - } - } - if newRS != nil { newRSReplicaCount, err := replicasetutil.NewRSNewReplicas(rollout, allRS, newRS) if err != nil { diff --git a/rollout/bluegreen_test.go b/rollout/bluegreen_test.go index f181b202d6..829b52dcff 100644 --- a/rollout/bluegreen_test.go +++ b/rollout/bluegreen_test.go @@ -742,6 +742,105 @@ func TestBlueGreenRolloutScaleUpdateActiveRS(t *testing.T) { f.run(getKey(r2, t)) } +func TestPreviewReplicaCountHandleScaleUpPreviewCheckPoint(t *testing.T) { + t.Run("TrueAfterMeetingMinAvailable", func(t *testing.T) { + f := newFixture(t) + defer f.Close() + + r1 := newBlueGreenRollout("foo", 5, nil, "active", "") + r1.Spec.Strategy.BlueGreen.PreviewReplicaCount = pointer.Int32Ptr(3) + r1.Spec.Strategy.BlueGreen.AutoPromotionEnabled = pointer.BoolPtr(false) + rs1 := newReplicaSetWithStatus(r1, 5, 5) + r2 := bumpVersion(r1) + + rs2 := newReplicaSetWithStatus(r2, 3, 3) + f.kubeobjects = append(f.kubeobjects, rs1, rs2) + f.replicaSetLister = append(f.replicaSetLister, rs1, rs2) + + rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + + activeSvc := newService("active", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash}) + + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, 3, 3, 8, 5, false, true) + f.rolloutLister = append(f.rolloutLister, r2) + f.objects = append(f.objects, r2) + f.kubeobjects = append(f.kubeobjects, activeSvc) + f.serviceLister = append(f.serviceLister, activeSvc) + + patchIndex := f.expectPatchRolloutAction(r1) + f.run(getKey(r2, t)) + patch := f.getPatchedRollout(patchIndex) + + assert.Contains(t, patch, `"scaleUpPreviewCheckPoint":true`) + + }) + t.Run("FalseAfterActiveServiceSwitch", func(t *testing.T) { + f := newFixture(t) + defer f.Close() + + r1 := newBlueGreenRollout("foo", 5, nil, "active", "") + r1.Spec.Strategy.BlueGreen.PreviewReplicaCount = pointer.Int32Ptr(3) + r1.Spec.Strategy.BlueGreen.AutoPromotionEnabled = pointer.BoolPtr(false) + rs1 := newReplicaSetWithStatus(r1, 5, 5) + now := metav1.Now().Add(10 * time.Second) + rs1.Annotations[v1alpha1.DefaultReplicaSetScaleDownDeadlineAnnotationKey] = now.UTC().Format(time.RFC3339) + r2 := bumpVersion(r1) + + rs2 := newReplicaSetWithStatus(r2, 5, 5) + f.kubeobjects = append(f.kubeobjects, rs1, rs2) + f.replicaSetLister = append(f.replicaSetLister, rs1, rs2) + + rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + + activeSvc := newService("active", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash}) + + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, 5, 5, 8, 5, false, true) + r2.Status.BlueGreen.ScaleUpPreviewCheckPoint = true + f.rolloutLister = append(f.rolloutLister, r2) + f.objects = append(f.objects, r2) + f.kubeobjects = append(f.kubeobjects, activeSvc) + f.serviceLister = append(f.serviceLister, activeSvc) + + f.expectPatchReplicaSetAction(rs1) + patchIndex := f.expectPatchRolloutAction(r1) + + f.run(getKey(r2, t)) + patch := f.getPatchedRollout(patchIndex) + assert.Contains(t, patch, `"scaleUpPreviewCheckPoint":null`) + }) + t.Run("TrueWhenScalingUpPreview", func(t *testing.T) { + f := newFixture(t) + defer f.Close() + + r1 := newBlueGreenRollout("foo", 5, nil, "active", "") + r1.Spec.Strategy.BlueGreen.PreviewReplicaCount = pointer.Int32Ptr(3) + rs1 := newReplicaSetWithStatus(r1, 5, 5) + r2 := bumpVersion(r1) + + rs2 := newReplicaSetWithStatus(r2, 3, 3) + f.kubeobjects = append(f.kubeobjects, rs1, rs2) + f.replicaSetLister = append(f.replicaSetLister, rs1, rs2) + + rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + + activeSvc := newService("active", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash}) + + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, 5, 5, 8, 5, false, true) + r2.Status.BlueGreen.ScaleUpPreviewCheckPoint = true + f.rolloutLister = append(f.rolloutLister, r2) + f.objects = append(f.objects, r2) + f.kubeobjects = append(f.kubeobjects, activeSvc) + f.serviceLister = append(f.serviceLister, activeSvc) + + f.expectUpdateReplicaSetAction(rs1) + f.expectPatchRolloutAction(r2) + f.run(getKey(r2, t)) + }) +} + func TestBlueGreenRolloutIgnoringScalingUsePreviewRSCount(t *testing.T) { f := newFixture(t) defer f.Close() diff --git a/rollout/replicaset.go b/rollout/replicaset.go index 9eb632268d..2d54058546 100644 --- a/rollout/replicaset.go +++ b/rollout/replicaset.go @@ -77,18 +77,6 @@ func (c *RolloutController) reconcileNewReplicaSet(roCtx rolloutContext) (bool, } roCtx.Log().Infof("Reconciling new ReplicaSet '%s'", newRS.Name) allRSs := roCtx.AllRSs() - if rollout.Spec.Strategy.BlueGreen != nil { - rolloutReplicas := defaults.GetReplicasOrDefault(rollout.Spec.Replicas) - if *(newRS.Spec.Replicas) == rolloutReplicas { - // Scaling not required. - return false, nil - } - if *(newRS.Spec.Replicas) > rolloutReplicas { - // Scale down. - scaled, _, err := c.scaleReplicaSetAndRecordEvent(newRS, rolloutReplicas, rollout) - return scaled, err - } - } newReplicasCount, err := replicasetutil.NewRSNewReplicas(rollout, allRSs, newRS) if err != nil { return false, err From 084b1d7ab1e264ff5c6fa78f3b50d756c7813e74 Mon Sep 17 00:00:00 2001 From: dthomson25 Date: Mon, 2 Dec 2019 20:44:04 -0800 Subject: [PATCH 08/28] Fix incorrect patch on getting started guide (#315) --- docs/getting-started.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index f8187b4614..1b0fad5f9d 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -22,6 +22,8 @@ On GKE, you will need grant your account the ability to create new cluster roles kubectl create clusterrolebinding YOURNAME-cluster-admin-binding --clusterrole=cluster-admin --user=YOUREMAIL@gmail.com ``` +Note: The cluster-level installation assumes that Argo Rollouts is deployed into the `argo-rollouts` namespace. If you would like to install Argo Rollouts in another namespace, you will need to modify the `ClusterRoleBinding` resource that binds the ClusterRole to the ServiceAcccount created. The namespace for the ServiceAccount referenced in the ClusterRoleBinding needs to be modified to match your desired namespace. + ### Namespace-Level Installation ```bash kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-rollouts/stable/manifests/namespace-install.yaml @@ -79,15 +81,21 @@ Once the patch is applied, you can watch the new replicaset came up as healthy b ```bash $ kubectl get replicaset -w -o wide ``` -Once that replicaset is healthy, the rollout will enter a paused state by setting the `spec.paused` field to true, and setting the `.status.pauseStartedTime` to the current time. +Once that replicaset is healthy, the rollout will enter a paused state by adding a pause condition to `.status.pauseConditions`. The pause condition contains a reason and a pause start time. + +## Promoting the rollout +The rollout does not continue progessing to the new version until the pause conditon is removed from the status. Since the rollout YAML submitted does not have a duration within the pause step, the Rollout is paused indefinitely until a external process (i.e. a user or automiated tool) removes the pause conditon. + +Argo Rollouts has a [kubectl plugin](features/kubectl-plugin.md) to help automate operations like promoting a rollout through a step. The installation instructions are [here](features/kubectl-plugin.md#installation). + +Once the plugin is installed, the user can run the following command to promote the rollout through the pause step: -## Unpausing the rollout -The rollout can be unpaused by running `kubectl edit rollout example-rollout` and setting the `spec.paused` field to `false` or the following ```bash -$ kubectl patch rollout example-rollout --type merge -p '{"spec": {"paused": false}}' +kubectl argo rollouts promote example-rollout + ``` -At this point, the Rollout has executed all the steps to transition to a new version. As a result, the new ReplicaSet is considered the new stable ReplicaSet, and the previous ReplicaSet will be scaled down. The Rollout will repeat this behavior if the Pod Spec Template is changed again. +At this point, the Rollout has executed all the steps to transition to a new version. As a result, the new ReplicaSet is considered the new stable ReplicaSet, and the previous ReplicaSet will be scaled down. The Rollout will repeat these steps when the Pod Spec Template is changed again. ## Going forward Check out the [features page](features/index.md) for more configuration options for a rollout. From 104611553dd82374fae6b621f76706c96cc57c9a Mon Sep 17 00:00:00 2001 From: dthomson25 Date: Mon, 2 Dec 2019 20:48:39 -0800 Subject: [PATCH 09/28] Create one background analysis per revision (#309) --- rollout/context.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rollout/context.go b/rollout/context.go index 0a6d9350ee..c819a92d11 100644 --- a/rollout/context.go +++ b/rollout/context.go @@ -179,7 +179,8 @@ func (cCtx *canaryContext) SetCurrentAnalysisRuns(ars []*v1alpha1.AnalysisRun) { cCtx.currentArs = ars currBackgroundAr := analysisutil.GetCurrentBackgroundAnalysisRun(ars) if currBackgroundAr != nil && !cCtx.PauseContext().IsAborted() { - if !currBackgroundAr.Status.Phase.Completed() { + switch currBackgroundAr.Status.Phase { + case v1alpha1.AnalysisPhasePending, v1alpha1.AnalysisPhaseRunning, v1alpha1.AnalysisPhaseSuccessful, "": cCtx.newStatus.Canary.CurrentBackgroundAnalysisRun = currBackgroundAr.Name } } From a24a7330783663525676aa972579800cbfaa3ac7 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Wed, 4 Dec 2019 13:38:57 +0900 Subject: [PATCH 10/28] Bluegreen: allow preview service/replica sets to be replaced and fix sg fault in syncReplicasOnly (#314) --- rollout/bluegreen.go | 11 +++++++++++ utils/replicaset/replicaset.go | 4 ++-- utils/replicaset/replicaset_test.go | 6 +++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/rollout/bluegreen.go b/rollout/bluegreen.go index e9fd8358d1..b4e0f224e3 100644 --- a/rollout/bluegreen.go +++ b/rollout/bluegreen.go @@ -144,6 +144,10 @@ func (c *RolloutController) reconcileBlueGreenPause(activeSvc, previewSvc *corev } newRS := roCtx.NewRS() + if newRS == nil { + return false + } + newRSPodHash := newRS.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] if _, ok := activeSvc.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]; !ok { @@ -223,6 +227,12 @@ func (c *RolloutController) syncRolloutStatusBlueGreen(previewSvc *corev1.Servic oldRSs := roCtx.OlderRSs() allRSs := roCtx.AllRSs() newStatus := c.calculateBaseStatus(roCtx) + + if replicasetutil.CheckPodSpecChange(r, newRS) { + roCtx.PauseContext().ClearPauseConditions() + roCtx.PauseContext().RemoveAbort() + } + newStatus.AvailableReplicas = replicasetutil.GetAvailableReplicaCountForReplicaSets([]*appsv1.ReplicaSet{newRS}) previewSelector, ok := serviceutil.GetRolloutSelectorLabel(previewSvc) if !ok { @@ -233,6 +243,7 @@ func (c *RolloutController) syncRolloutStatusBlueGreen(previewSvc *corev1.Servic if !ok { activeSelector = "" } + newStatus.BlueGreen.ActiveSelector = activeSelector if newStatus.BlueGreen.ActiveSelector != r.Status.BlueGreen.ActiveSelector { previousActiveRS, _ := replicasetutil.GetReplicaSetByTemplateHash(oldRSs, r.Status.BlueGreen.ActiveSelector) diff --git a/utils/replicaset/replicaset.go b/utils/replicaset/replicaset.go index 83b1a459b7..019a42dd63 100644 --- a/utils/replicaset/replicaset.go +++ b/utils/replicaset/replicaset.go @@ -271,7 +271,7 @@ func checkStepHashChange(rollout *v1alpha1.Rollout) bool { // checkPodSpecChange indicates if the rollout spec has changed indicating that the rollout needs to reset the // currentStepIndex to zero. If there is no previous pod spec to compare to the function defaults to false -func checkPodSpecChange(rollout *v1alpha1.Rollout, newRS *appsv1.ReplicaSet) bool { +func CheckPodSpecChange(rollout *v1alpha1.Rollout, newRS *appsv1.ReplicaSet) bool { if rollout.Status.CurrentPodHash == "" { return false } @@ -292,7 +292,7 @@ func PodTemplateOrStepsChanged(rollout *v1alpha1.Rollout, newRS *appsv1.ReplicaS if checkStepHashChange(rollout) { return true } - if checkPodSpecChange(rollout, newRS) { + if CheckPodSpecChange(rollout, newRS) { return true } return false diff --git a/utils/replicaset/replicaset_test.go b/utils/replicaset/replicaset_test.go index 9a7b081fa0..0429d490e6 100644 --- a/utils/replicaset/replicaset_test.go +++ b/utils/replicaset/replicaset_test.go @@ -622,12 +622,12 @@ func TestMaxUnavailable(t *testing.T) { func TestCheckPodSpecChange(t *testing.T) { ro := generateRollout("ngnix") rs := generateRS(ro) - assert.False(t, checkPodSpecChange(&ro, &rs)) + assert.False(t, CheckPodSpecChange(&ro, &rs)) ro.Status.CurrentPodHash = controller.ComputeHash(&ro.Spec.Template, ro.Status.CollisionCount) - assert.False(t, checkPodSpecChange(&ro, &rs)) + assert.False(t, CheckPodSpecChange(&ro, &rs)) ro.Status.CurrentPodHash = "different-hash" - assert.True(t, checkPodSpecChange(&ro, &rs)) + assert.True(t, CheckPodSpecChange(&ro, &rs)) } func TestCheckStepHashChange(t *testing.T) { From 5feb38224715e5c8ceb3b8f20a8d5658d6d80ae5 Mon Sep 17 00:00:00 2001 From: dthomson25 Date: Thu, 5 Dec 2019 13:22:14 -0800 Subject: [PATCH 11/28] Set StableRS hash to current if replicaset does not actually exist (#320) --- rollout/canary.go | 14 +++++++------- rollout/canary_test.go | 6 +++--- rollout/context.go | 11 +++++------ rollout/sync.go | 3 +-- utils/replicaset/canary.go | 3 +++ utils/replicaset/canary_test.go | 30 ++++++++++++++++++++++++++++++ 6 files changed, 49 insertions(+), 18 deletions(-) diff --git a/rollout/canary.go b/rollout/canary.go index 2e5897903a..1018764151 100644 --- a/rollout/canary.go +++ b/rollout/canary.go @@ -1,6 +1,7 @@ package rollout import ( + "fmt" "sort" appsv1 "k8s.io/api/apps/v1" @@ -34,8 +35,7 @@ func (c *RolloutController) rolloutCanary(rollout *v1alpha1.Rollout, rsList []*a if err != nil { return err } - stableRS, oldRSs := replicasetutil.GetStableRS(rollout, newRS, previousRSs) - roCtx := newCanaryCtx(rollout, newRS, stableRS, oldRSs, exList, arList) + roCtx := newCanaryCtx(rollout, newRS, previousRSs, exList, arList) return c.syncRolloutStatusCanary(roCtx) } @@ -43,13 +43,12 @@ func (c *RolloutController) rolloutCanary(rollout *v1alpha1.Rollout, rsList []*a if err != nil { return err } - stableRS, oldRSs := replicasetutil.GetStableRS(rollout, newRS, previousRSs) - roCtx := newCanaryCtx(rollout, newRS, stableRS, oldRSs, exList, arList) + roCtx := newCanaryCtx(rollout, newRS, previousRSs, exList, arList) logCtx := roCtx.Log() logCtx.Info("Cleaning up old replicasets, experiments, and analysis runs") - if err := c.cleanupRollouts(oldRSs, roCtx); err != nil { + if err := c.cleanupRollouts(roCtx.OlderRSs(), roCtx); err != nil { return err } @@ -251,6 +250,7 @@ func (c *RolloutController) syncRolloutStatusCanary(roCtx *canaryContext) error r := roCtx.Rollout() logCtx := roCtx.Log() newRS := roCtx.NewRS() + stableRS := roCtx.StableRS() allRSs := roCtx.AllRSs() newStatus := c.calculateBaseStatus(roCtx) @@ -279,8 +279,8 @@ func (c *RolloutController) syncRolloutStatusCanary(roCtx *canaryContext) error return c.persistRolloutStatus(roCtx, &newStatus) } - if r.Status.Canary.StableRS == "" { - msg := "Setting StableRS to CurrentPodHash as it is empty beforehand" + if stableRS == nil { + msg := fmt.Sprintf("Setting StableRS to CurrentPodHash: StableRS hash: %s", newStatus.CurrentPodHash) logCtx.Info(msg) newStatus.Canary.StableRS = newStatus.CurrentPodHash if stepCount > 0 { diff --git a/rollout/canary_test.go b/rollout/canary_test.go index 9a188f3296..24cc2aca25 100644 --- a/rollout/canary_test.go +++ b/rollout/canary_test.go @@ -62,14 +62,14 @@ func TestReconcileCanaryStepsHandleBaseCases(t *testing.T) { // Handle case with no steps r := newCanaryRollout("test", 1, nil, nil, nil, intstr.FromInt(0), intstr.FromInt(1)) - roCtx := newCanaryCtx(r, nil, nil, nil, nil, nil) + roCtx := newCanaryCtx(r, nil, nil, nil, nil) stepResult := controller.reconcileCanaryPause(roCtx) assert.False(t, stepResult) assert.Len(t, fake.Actions(), 0) r2 := newCanaryRollout("test", 1, nil, []v1alpha1.CanaryStep{{SetWeight: int32Ptr(10)}}, nil, intstr.FromInt(0), intstr.FromInt(1)) r2.Status.CurrentStepIndex = int32Ptr(1) - roCtx2 := newCanaryCtx(r2, nil, nil, nil, nil, nil) + roCtx2 := newCanaryCtx(r2, nil, nil, nil, nil) stepResult = controller.reconcileCanaryPause(roCtx2) assert.False(t, stepResult) assert.Len(t, fake.Actions(), 0) @@ -1118,7 +1118,7 @@ func TestNoResumeAfterPauseDurationIfUserPaused(t *testing.T) { r1 := newCanaryRollout("foo", 1, nil, steps, pointer.Int32Ptr(1), intstr.FromInt(1), intstr.FromInt(1)) rs1 := newReplicaSetWithStatus(r1, 1, 1) rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r1 = updateCanaryRolloutStatus(r1, rs1PodHash, 2, 1, 2, true) + r1 = updateCanaryRolloutStatus(r1, rs1PodHash, 1, 1, 1, true) overAMinuteAgo := metav1.Time{Time: time.Now().Add(-61 * time.Second)} r1.Status.ObservedGeneration = conditions.ComputeGenerationHash(r1.Spec) r1.Status.PauseConditions = []v1alpha1.PauseCondition{{ diff --git a/rollout/context.go b/rollout/context.go index c819a92d11..ec3b214a23 100644 --- a/rollout/context.go +++ b/rollout/context.go @@ -9,6 +9,7 @@ import ( analysisutil "github.com/argoproj/argo-rollouts/utils/analysis" experimentutil "github.com/argoproj/argo-rollouts/utils/experiment" logutil "github.com/argoproj/argo-rollouts/utils/log" + replicasetutil "github.com/argoproj/argo-rollouts/utils/replicaset" ) type rolloutContext interface { @@ -119,11 +120,9 @@ func (bgCtx *blueGreenContext) NewStatus() v1alpha1.RolloutStatus { return bgCtx.newStatus } -func newCanaryCtx(r *v1alpha1.Rollout, newRS *appsv1.ReplicaSet, stableRS *appsv1.ReplicaSet, olderRSs []*appsv1.ReplicaSet, exList []*v1alpha1.Experiment, arList []*v1alpha1.AnalysisRun) *canaryContext { - allRSs := append(olderRSs, newRS) - if stableRS != nil { - allRSs = append(allRSs, stableRS) - } +func newCanaryCtx(r *v1alpha1.Rollout, newRS *appsv1.ReplicaSet, otherRSs []*appsv1.ReplicaSet, exList []*v1alpha1.Experiment, arList []*v1alpha1.AnalysisRun) *canaryContext { + allRSs := append(otherRSs, newRS) + stableRS, oldRSs := replicasetutil.GetStableRS(r, newRS, otherRSs) currentArs, otherArs := analysisutil.FilterCurrentRolloutAnalysisRuns(arList, r) currentEx := experimentutil.GetCurrentExperiment(r, exList) @@ -134,7 +133,7 @@ func newCanaryCtx(r *v1alpha1.Rollout, newRS *appsv1.ReplicaSet, stableRS *appsv log: logCtx, newRS: newRS, stableRS: stableRS, - olderRSs: olderRSs, + olderRSs: oldRSs, allRSs: allRSs, currentArs: currentArs, diff --git a/rollout/sync.go b/rollout/sync.go index 3310162672..e20e1b9f0d 100644 --- a/rollout/sync.go +++ b/rollout/sync.go @@ -241,8 +241,7 @@ func (c *RolloutController) syncReplicasOnly(r *v1alpha1.Rollout, rsList []*apps return err } - stableRS, oldRSs := replicasetutil.GetStableRS(r, newRS, rsList) - roCtx := newCanaryCtx(r, newRS, stableRS, oldRSs, exList, arList) + roCtx := newCanaryCtx(r, newRS, oldRSs, exList, arList) if isScaling { if _, err := c.reconcileCanaryReplicaSets(roCtx); err != nil { diff --git a/utils/replicaset/canary.go b/utils/replicaset/canary.go index 1fb7e8f779..05d0a02ec4 100644 --- a/utils/replicaset/canary.go +++ b/utils/replicaset/canary.go @@ -261,6 +261,9 @@ func GetStableRS(rollout *v1alpha1.Rollout, newRS *appsv1.ReplicaSet, rslist []* if rollout.Status.Canary.StableRS == "" { return nil, rslist } + if newRS != nil && newRS.Labels != nil && newRS.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] == rollout.Status.Canary.StableRS { + return newRS, rslist + } olderRSs := []*appsv1.ReplicaSet{} var stableRS *appsv1.ReplicaSet for i := range rslist { diff --git a/utils/replicaset/canary_test.go b/utils/replicaset/canary_test.go index b112507ed6..c72fed0d50 100644 --- a/utils/replicaset/canary_test.go +++ b/utils/replicaset/canary_test.go @@ -388,6 +388,36 @@ func TestCalculateReplicaCountsForCanaryStableRSdEdgeCases(t *testing.T) { assert.Equal(t, int32(0), stableRSReplicaCount) } +func TestGetStableRS(t *testing.T) { + rs := func(podHash string) appsv1.ReplicaSet { + return appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: podHash, + Labels: map[string]string{ + v1alpha1.DefaultRolloutUniqueLabelKey: podHash, + }, + }, + } + } + + rollout := &v1alpha1.Rollout{} + rs1 := rs("1") + rs2 := rs("2") + rs3 := rs("3") + noStable, rsList := GetStableRS(rollout, &rs1, []*appsv1.ReplicaSet{&rs2, &rs3}) + assert.Nil(t, noStable) + assert.Len(t, rsList, 2) + + rollout.Status.Canary.StableRS = "1" + sameAsNewRS, rsList := GetStableRS(rollout, &rs1, []*appsv1.ReplicaSet{&rs2, &rs3}) + assert.Equal(t, *sameAsNewRS, rs1) + assert.Len(t, rsList, 2) + + stableInOtherRSs, rsList := GetStableRS(rollout, &rs2, []*appsv1.ReplicaSet{&rs1, &rs2, &rs3}) + assert.Equal(t, *stableInOtherRSs, rs1) + assert.Len(t, rsList, 1) + +} func TestGetCurrentCanaryStep(t *testing.T) { rollout := newRollout(10, 10, intstr.FromInt(0), intstr.FromInt(1), "", "") rollout.Spec.Strategy.Canary.Steps = nil From 53ee15bf485a25ea58f9ba4a729d5e4016007132 Mon Sep 17 00:00:00 2001 From: Danny Thomson Date: Fri, 6 Dec 2019 10:36:12 -0800 Subject: [PATCH 12/28] Update version to v0.6.1 --- CHANGELOG.md | 16 +++++++++++++++- VERSION | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c21317c64..265e8867bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,18 @@ # Changelog +# v0.6.1 +## Quick Start +kubectl create namespace argo-rollouts +kubectl apply -n argo-rollouts -f https://raw.githubusercontent.com/argoproj/argo-rollouts/v0.6.1/manifests/install.yaml + +# Changes since v0.6.0 +## Bug Fixes + +- Create one background analysis per revision (#309) +- Fix Infinite loop with PreviewReplicaCount set (#308) +- Fix a delete by zero in get command (#310) +- Set StableRS hash to current if replicaset does not actually exist (#320) +- Bluegreen: allow preview service/replica sets to be replaced and fix sg fault in syncReplicasOnly (#314) + # v0.6.0 ## Quick Start ``` @@ -263,4 +277,4 @@ Changes the following clusterroles to prevent name collision with Argo Workflows # v0.1.0 * Creates a controller that manages a rollout object that mimics a deployment object -* Declaratively offers a Blue Green Strategy by creating the replicaset from the spec and managing an active and preview service to point to the new replicaset \ No newline at end of file +* Declaratively offers a Blue Green Strategy by creating the replicaset from the spec and managing an active and preview service to point to the new replicaset diff --git a/VERSION b/VERSION index a918a2aa18..ee6cdce3c2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.0 +0.6.1 From 81f59fe11b38ffba373547660dc0ee85db9d1c9c Mon Sep 17 00:00:00 2001 From: Saradhi Sreegiriraju Date: Tue, 10 Dec 2019 12:05:34 -0800 Subject: [PATCH 13/28] Add Community Blogs and Presentations section (#322) --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7eceedc386..76958f1a63 100644 --- a/README.md +++ b/README.md @@ -15,4 +15,8 @@ To learn more about Argo Rollouts go to the [complete documentation](https://arg ## Who uses Argo Rollouts? Organizations below are **officially** using Argo Rollouts. Please send a PR with your organization name if you are using Argo Rollouts. -1. Intuit (https://www.intuit.com/) +1. [Intuit](https://www.intuit.com/) + +## Community Blogs and Presentations +* [How Intuit Does Canary and Blue Green Deployments](https://www.youtube.com/watch?v=yeVkTTO9nOA) +* [Leveling Up Your CD: Unlocking Progressive Delivery on Kubernetes](https://www.youtube.com/watch?v=Nv0PPwbIEkY) From bdc1ec860a9c611996d0b391e1dabdb21d6bcf57 Mon Sep 17 00:00:00 2001 From: Bertrand Paquet Date: Tue, 10 Dec 2019 21:17:01 +0100 Subject: [PATCH 14/28] Fix a typo (#321) --- examples/rollout-analysis-step.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rollout-analysis-step.yaml b/examples/rollout-analysis-step.yaml index 0a395dd36a..269f939a4f 100644 --- a/examples/rollout-analysis-step.yaml +++ b/examples/rollout-analysis-step.yaml @@ -29,7 +29,7 @@ spec: steps: - setWeight: 25 # An AnalysisTemplate is referenced at the second step, which starts an AnalysisRun after - # the setWeight step. The rolllout will not progress to the following step until the + # the setWeight step. The rollout will not progress to the following step until the # AnalysisRun is complete. A failure/error of the analysis will cause the rollout's update to # abort, and set the canary weight to zero. - analysis: From 867a0f6fcaced089bb55cecd11e7729e7aa40baa Mon Sep 17 00:00:00 2001 From: Petteri Vaarala Date: Thu, 12 Dec 2019 21:14:57 +0200 Subject: [PATCH 15/28] Fix link to deployment concepts in docs (#325) --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 3f04119a2d..83f096ff77 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,7 +1,7 @@ # Argo Rollouts - Advanced Kubernetes Deployment Controller ## What is Argo Rollouts? -Argo Rollouts introduces a new custom resource called a Rollout to provide additional deployment strategies such as Blue Green and Canary to Kubernetes. The Rollout custom resource provides feature parity with the deployment resource with additional deployment strategies. Check out the [Deployment Concepts](#deployment-concepts) for more information on the various deployment strategies. +Argo Rollouts introduces a new custom resource called a Rollout to provide additional deployment strategies such as Blue Green and Canary to Kubernetes. The Rollout custom resource provides feature parity with the deployment resource with additional deployment strategies. Check out the [Deployment Concepts](deployment-concepts.md) for more information on the various deployment strategies. ## Why Argo Rollouts? Deployments resources offer two strategies to deploy changes: `RollingUpdate` and `Recreate`. While these strategies can solve a wide number of use cases, large scale production deployments use additional strategies, such as blue-green or canary, that are missing from the Deployment controller. In order to use these strategies in Kubernetes, users are forced to build scripts on top of their deployments. The Argo Rollouts controller provides these strategies as simple declarative, configurable, GitOps-friendly options. From c61a43be07f375147e7721612f16c691ca9f0f8f Mon Sep 17 00:00:00 2001 From: Jesse Suen Date: Mon, 16 Dec 2019 08:26:52 -0800 Subject: [PATCH 16/28] fix: omitted revisionHistoryLimit was not defaulting to 10 (#330) --- rollout/sync.go | 6 ++--- rollout/sync_test.go | 64 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 59 insertions(+), 11 deletions(-) diff --git a/rollout/sync.go b/rollout/sync.go index e20e1b9f0d..ce2f41a0d4 100644 --- a/rollout/sync.go +++ b/rollout/sync.go @@ -369,9 +369,7 @@ func (c *RolloutController) calculateBaseStatus(roCtx rolloutContext) v1alpha1.R func (c *RolloutController) cleanupRollouts(oldRSs []*appsv1.ReplicaSet, roCtx rolloutContext) error { rollout := roCtx.Rollout() logCtx := roCtx.Log() - if !conditions.HasRevisionHistoryLimit(rollout) { - return nil - } + revHistoryLimit := defaults.GetRevisionHistoryLimitOrDefault(rollout) // Avoid deleting replica set with deletion timestamp set aliveFilter := func(rs *appsv1.ReplicaSet) bool { @@ -379,7 +377,7 @@ func (c *RolloutController) cleanupRollouts(oldRSs []*appsv1.ReplicaSet, roCtx r } cleanableRSes := controller.FilterReplicaSets(oldRSs, aliveFilter) - diff := int32(len(cleanableRSes)) - *rollout.Spec.RevisionHistoryLimit + diff := int32(len(cleanableRSes)) - revHistoryLimit if diff <= 0 { return nil } diff --git a/rollout/sync_test.go b/rollout/sync_test.go index 137a6f31cf..bf1e31d70f 100644 --- a/rollout/sync_test.go +++ b/rollout/sync_test.go @@ -239,6 +239,17 @@ func TestCleanupRollouts(t *testing.T) { now := metav1.Now() before := metav1.Time{Time: now.Add(-time.Minute)} + newRS := func(name string) *appsv1.ReplicaSet { + return &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + CreationTimestamp: before, + }, + Spec: appsv1.ReplicaSetSpec{Replicas: int32Ptr(0)}, + Status: appsv1.ReplicaSetStatus{Replicas: int32(0)}, + } + } + tests := []struct { name string revisionHistoryLimit *int32 @@ -248,12 +259,20 @@ func TestCleanupRollouts(t *testing.T) { { name: "No Revision History Limit", revisionHistoryLimit: nil, - replicaSets: []*appsv1.ReplicaSet{{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - CreationTimestamp: now, - }, - }}, + replicaSets: []*appsv1.ReplicaSet{ + newRS("foo1"), + newRS("foo2"), + newRS("foo3"), + newRS("foo4"), + newRS("foo5"), + newRS("foo6"), + newRS("foo7"), + newRS("foo8"), + newRS("foo9"), + newRS("foo10"), + newRS("foo11"), + }, + expectedDeleted: map[string]bool{"foo1": true}, }, { name: "Avoid deleting RS with deletion timestamp", @@ -272,6 +291,36 @@ func TestCleanupRollouts(t *testing.T) { { name: "Delete extra replicasets", revisionHistoryLimit: int32Ptr(1), + replicaSets: []*appsv1.ReplicaSet{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + CreationTimestamp: before, + }, + Spec: appsv1.ReplicaSetSpec{ + Replicas: int32Ptr(0), + }, + Status: appsv1.ReplicaSetStatus{ + Replicas: int32(0), + }, + }, { + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + CreationTimestamp: now, + }, + Spec: appsv1.ReplicaSetSpec{ + Replicas: int32Ptr(1), + }, + Status: appsv1.ReplicaSetStatus{ + Replicas: int32(1), + }, + }, + }, + expectedDeleted: map[string]bool{"foo": true}, + }, + { + name: "Dont delete scaled replicasets", + revisionHistoryLimit: int32Ptr(1), replicaSets: []*appsv1.ReplicaSet{ { ObjectMeta: metav1.ObjectMeta{ @@ -297,7 +346,7 @@ func TestCleanupRollouts(t *testing.T) { }, }, }, - expectedDeleted: map[string]bool{"foo": true}, + expectedDeleted: map[string]bool{}, }, { name: "Do not delete any replicasets", @@ -344,6 +393,7 @@ func TestCleanupRollouts(t *testing.T) { err := c.cleanupRollouts(test.replicaSets, roCtx) assert.Nil(t, err) + assert.Equal(t, len(test.expectedDeleted), len(k8sfake.Actions())) for _, action := range k8sfake.Actions() { rsName := action.(testclient.DeleteAction).GetName() assert.True(t, test.expectedDeleted[rsName]) From f870efe3e2890112385a08b3223512ebf40998b4 Mon Sep 17 00:00:00 2001 From: dthomson25 Date: Mon, 16 Dec 2019 08:27:20 -0800 Subject: [PATCH 17/28] Fix panics with incorrectly configured rollouts (#328) * Fix panic if rollout cannot create a new RS * Enable controller to handle panics with crashing --- rollout/sync.go | 5 ++++- utils/controller/controller.go | 16 +++++++++++++--- utils/controller/controller_test.go | 10 ++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/rollout/sync.go b/rollout/sync.go index ce2f41a0d4..abaa4482bd 100644 --- a/rollout/sync.go +++ b/rollout/sync.go @@ -182,7 +182,10 @@ func (c *RolloutController) getNewReplicaSet(rollout *v1alpha1.Rollout, rsList, c.recorder.Event(rollout, corev1.EventTypeWarning, conditions.FailedRSCreateReason, msg) newStatus := rollout.Status.DeepCopy() cond := conditions.NewRolloutCondition(v1alpha1.RolloutProgressing, corev1.ConditionFalse, conditions.FailedRSCreateReason, msg) - err := c.patchCondition(rollout, newStatus, cond) + patchErr := c.patchCondition(rollout, newStatus, cond) + if patchErr != nil { + logCtx.Warnf("Error Patching Rollout: %s", patchErr.Error()) + } return nil, err } diff --git a/utils/controller/controller.go b/utils/controller/controller.go index 4463c7f45b..84ac471bf2 100644 --- a/utils/controller/controller.go +++ b/utils/controller/controller.go @@ -2,6 +2,7 @@ package controller import ( "fmt" + "runtime/debug" "time" log "github.com/sirupsen/logrus" @@ -56,13 +57,22 @@ func processNextWorkItem(workqueue workqueue.RateLimitingInterface, objType stri return nil } namespace, name, err := cache.SplitMetaNamespaceKey(key) + logCtx := log.WithField(objType, name).WithField(logutil.NamespaceKey, namespace) if err != nil { return err } - + runSyncHandler := func() (err error) { + defer func() { + if r := recover(); r != nil { + logCtx.Errorf("Recovered from panic: %+v\n%s", r, debug.Stack()) + err = fmt.Errorf("Recovered from Panic") + } + }() + return syncHandler(key) + } // Run the syncHandler, passing it the namespace/name string of the // Rollout resource to be synced. - if err := syncHandler(key); err != nil { + if err := runSyncHandler(); err != nil { metricsServer.IncError(namespace, name) // Put the item back on the workqueue to handle any transient errors. workqueue.AddRateLimited(key) @@ -71,7 +81,7 @@ func processNextWorkItem(workqueue workqueue.RateLimitingInterface, objType stri // Finally, if no error occurs we Forget this item so it does not // get queued again until another change happens. workqueue.Forget(obj) - log.WithField(objType, name).WithField(logutil.NamespaceKey, namespace).Info("Successfully synced") + logCtx.Info("Successfully synced") return nil }(obj) diff --git a/utils/controller/controller_test.go b/utils/controller/controller_test.go index 6208440e53..c6a4bdc434 100644 --- a/utils/controller/controller_test.go +++ b/utils/controller/controller_test.go @@ -20,6 +20,16 @@ import ( "k8s.io/client-go/tools/cache" ) +func TestProcessNextWorkItemHandlePanic(t *testing.T) { + q := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Rollouts") + q.Add("valid/key") + metricServer := metrics.NewMetricsServer("localhost:8080", nil) + syncHandler := func(key string) error { + panic("Bad big panic :(") + } + assert.True(t, processNextWorkItem(q, log.RolloutKey, syncHandler, metricServer)) +} + func TestProcessNextWorkItemShutDownQueue(t *testing.T) { q := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Rollouts") syncHandler := func(key string) error { From 88e59d61747bedec6a19fbb2cc8e64a541ae33a3 Mon Sep 17 00:00:00 2001 From: dthomson25 Date: Mon, 16 Dec 2019 09:06:00 -0800 Subject: [PATCH 18/28] Add Nginx docs for traffic management (#326) --- docs/features/traffic-management/index.md | 47 +++++++++++++++++++++++ docs/features/traffic-management/istio.md | 3 ++ docs/features/traffic-management/nginx.md | 35 +++++++++++++++++ mkdocs.yml | 1 + 4 files changed, 86 insertions(+) create mode 100644 docs/features/traffic-management/index.md create mode 100644 docs/features/traffic-management/istio.md create mode 100644 docs/features/traffic-management/nginx.md diff --git a/docs/features/traffic-management/index.md b/docs/features/traffic-management/index.md new file mode 100644 index 0000000000..d5100f41dc --- /dev/null +++ b/docs/features/traffic-management/index.md @@ -0,0 +1,47 @@ +# Traffic management + +NOTE: This is being implemented for the v0.7 version and everything described below is subject to change. + +Traffic management is controlling the data plane to have intelligent routing rules for an application. These routing rules can manipulate the flow of traffic to different versions of an application enabling Progressive Delivery. These controls limit the blast radius of a new release by ensuring a small percentage of users receive a new version while it is verified. + +There are various techniques to achieve traffic management: + +- Raw percentages (i.e., 5% of traffic should go to the new version while the rest goes to the stable version) +- Header-based routing (i.e., send requests with a specific header to the new version) +- Mirrored traffic where all the traffic is copied and send to the new version in parallel (but the response is ignored) + +## Traffic Management tools in Kubernetes + +The core Kubernetes objects do not have fine-grained tools needed to fulfill all the requirements of traffic management. At most, Kubernetes offers naïve load balancing capabilities through the Service object by offering an endpoint that routes traffic to a grouping of pods based on that Service's selector. Functionality like traffic mirroring or routing by headers is not possible with the default core Service object, and the only way to control the percentage of traffic to different versions of an application is by manipulating replica counts of those versions. + +Service Meshes fill this missing functionality in Kubernetes. They introduce new concepts and functionality to control the data plane through the use of CRDs and other core Kubernetes resources. + +## How does Argo Rollouts enable traffic management? + +Argo Rollouts enables traffic management by manipulating the Service Mesh resources to match the intent of the Rollout. Argo Rollouts currently supports the following service meshes: + +- [Istio](istio.md) +- [Nginx Ingress Controller](nginx.md) +- File a ticket [here](https://github.com/argoproj/argo-rollouts/issues) if you would like another implementation (or thumbs up it if that issue already exists) + +Regardless of the Service Mesh used, the Rollout object has to set a canary Service and a stable Service in its spec. Here is an example with those fields set: +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +spec: + ... + strategy: + canary: + canaryService: canary-service + stableService: stable-service + networking: + ... +``` + +The controller modifies these Services to route traffic to the appropriate canary and stable ReplicaSets as the Rollout progresses. These Services are used by the Service Mesh to define what group of pods should receive the canary and stable traffic. + +Additionally, the Argo Rollouts controller needs to treat the Rollout object differently when using traffic management. In particular, the Stable ReplicaSet owned by the Rollout remains fully scaled up as the Rollout progresses through the Canary steps. + +Since the traffic is controlled independently by the Service Mesh resources, the controller needs to make a best effort to ensure that the Stable and New ReplicaSets are not overwhelmed by the traffic sent to them. By leaving the Stable ReplicaSet scaled up, the controller is ensuring that the Stable ReplicaSet can handle 100% of the traffic at any time*. The New ReplicaSet follows the same behavior as without traffic management. The new ReplicaSet's replica count is equal to the latest SetWeight step percentage multiple by the total replica count of the Rollout. This calculation ensures that the canary version does not receive more traffic than it can handle. + +*The Rollout has to assume that the application can handle 100% of traffic if it is fully scaled up. It should outsource to the HPA to detect if the Rollout needs to more replicas if 100% isn't enough. \ No newline at end of file diff --git a/docs/features/traffic-management/istio.md b/docs/features/traffic-management/istio.md new file mode 100644 index 0000000000..0ac10c17ac --- /dev/null +++ b/docs/features/traffic-management/istio.md @@ -0,0 +1,3 @@ +# Istio + +TBD \ No newline at end of file diff --git a/docs/features/traffic-management/nginx.md b/docs/features/traffic-management/nginx.md new file mode 100644 index 0000000000..f4ef73a305 --- /dev/null +++ b/docs/features/traffic-management/nginx.md @@ -0,0 +1,35 @@ +# Nginx + +NOTE: This is being implemented for the v0.7 version and everything described below is subject to change. + +The [Nginx Ingress Controller](https://kubernetes.github.io/ingress-nginx/) enables traffic management through one or more Ingress objects to configure an Nginx deployment that routes traffic directly to pods. Each Nginx Ingress contains multiple annotations that modify the behavior of the Nginx Deployment. For traffic management between different versions of an application, the Nginx Ingress controller provides the capability to split traffic by introducing a second Ingress object (referred to as the canary Ingress) with some special annotations. Here are the canary specific annotations: + +- `nginx.ingress.kubernetes.io/canary` indicates that this Ingress is serving canary traffic +- `nginx.ingress.kubernetes.io/canary-weight` indicates what percentage of traffic to send to the canary. +- Other canary-specific annotations deal with routing traffic via headers or cookies. A future version of Argo Rollouts will contain this functionality. + + You can read more about these canary annotations on the official [documenentation page](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#canary). The canary Ingress ignores any other non-canary nginx annotations. Instead, it leverages the annotation settings from the primary Ingress. + +## Integration with Argo Rollouts +There are a couple of required fields in a Rollout to send split traffic between versions using Nginx. Below is an example of a Rollout with those fields: + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +spec: + ... + strategy: + canary: + canaryService: canary-service # required + stableService: stable-service # required + networking: + nginx: + primaryIngress: primary-ingress # required + annotationPrefix: example.nginx.com/ # optional +``` + +The primary Ingress field is a reference to an Ingress in the same namespace of the Rollout. The Rollout requires the primary Ingress routes traffic to the stable ReplicaSet. The Rollout checks that condition by confirming the Ingress has a backend that matches the Rollout's stableService. + +The controller routes traffic to the canary ReplicaSet by creating a second Ingress with the canary annotations. As the Rollout progresses through the Canary steps, the controller updates the canary Ingress's canary annotations to reflect the desired state of the Rollout enabling traffic splitting between two different versions. + +Since the Nginx Ingress controller allows users to configure the annotation prefix used by the Ingress controller, Rollouts can specify the optional `annonationPrefix` field. The canary Ingress uses that prefix instead of the default `nginx.ingress.kubernetes.io` if the field set. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 5c2775d958..f2a2f15ebe 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -24,6 +24,7 @@ nav: - features/index.md - BlueGreen: features/bluegreen.md - Canary: features/canary.md + - Traffic Management: features/traffic-management/index.md - Kubectl Plugin: features/kubectl-plugin.md - HPA Support: features/hpa-support.md - Kustomize Support: features/kustomize.md From 53b806e244108202292154133f50e52d46ff8bf4 Mon Sep 17 00:00:00 2001 From: Danny Thomson Date: Mon, 16 Dec 2019 09:25:50 -0800 Subject: [PATCH 19/28] Update version to v0.6.2 --- CHANGELOG.md | 12 ++++++++++++ VERSION | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 265e8867bd..76676e35df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,16 @@ # Changelog + +# v0.6.2 +## Quick Start +kubectl create namespace argo-rollouts +kubectl apply -n argo-rollouts -f https://raw.githubusercontent.com/argoproj/argo-rollouts/v0.6.2/manifests/install.yaml + +## Bug Fixes + +* omitted revisionHistoryLimit was not defaulting to 10 (#330) +* Fix panic if rollout cannot create a new RS (#328) +* Enable controller to handle panics with crashing (#328) + # v0.6.1 ## Quick Start kubectl create namespace argo-rollouts diff --git a/VERSION b/VERSION index ee6cdce3c2..b616048743 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.1 +0.6.2 From 4496241b6ed0fec4da067f723f997756c41558f5 Mon Sep 17 00:00:00 2001 From: Thomas Santerre Date: Tue, 17 Dec 2019 17:39:41 +0900 Subject: [PATCH 20/28] Fix error handling with HTTP client, change timeout field to timeoutSeconds, and minor style changes. --- manifests/crds/analysis-run-crd.yaml | 2 +- manifests/crds/analysis-template-crd.yaml | 2 +- manifests/install.yaml | 4 ++-- manifests/namespace-install.yaml | 4 ++-- metricproviders/webmetric/webmetric.go | 21 ++++++++----------- pkg/apis/rollouts/v1alpha1/analysis_types.go | 8 +++---- .../rollouts/v1alpha1/openapi_generated.go | 2 +- 7 files changed, 20 insertions(+), 23 deletions(-) diff --git a/manifests/crds/analysis-run-crd.yaml b/manifests/crds/analysis-run-crd.yaml index a4d2761a01..777dbc2ad1 100644 --- a/manifests/crds/analysis-run-crd.yaml +++ b/manifests/crds/analysis-run-crd.yaml @@ -2646,7 +2646,7 @@ spec: type: array jsonPath: type: string - timeout: + timeoutSeconds: type: integer url: type: string diff --git a/manifests/crds/analysis-template-crd.yaml b/manifests/crds/analysis-template-crd.yaml index 4172c97b3d..f7076c6189 100644 --- a/manifests/crds/analysis-template-crd.yaml +++ b/manifests/crds/analysis-template-crd.yaml @@ -2640,7 +2640,7 @@ spec: type: array jsonPath: type: string - timeout: + timeoutSeconds: type: integer url: type: string diff --git a/manifests/install.yaml b/manifests/install.yaml index eeb37e5683..2baec27c98 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -2647,7 +2647,7 @@ spec: type: array jsonPath: type: string - timeout: + timeoutSeconds: type: integer url: type: string @@ -5386,7 +5386,7 @@ spec: type: array jsonPath: type: string - timeout: + timeoutSeconds: type: integer url: type: string diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index 28f28175e6..8d9ed9242a 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -2647,7 +2647,7 @@ spec: type: array jsonPath: type: string - timeout: + timeoutSeconds: type: integer url: type: string @@ -5386,7 +5386,7 @@ spec: type: array jsonPath: type: string - timeout: + timeoutSeconds: type: integer url: type: string diff --git a/metricproviders/webmetric/webmetric.go b/metricproviders/webmetric/webmetric.go index 167890a6c5..2f57f00a33 100644 --- a/metricproviders/webmetric/webmetric.go +++ b/metricproviders/webmetric/webmetric.go @@ -37,10 +37,6 @@ func (p *Provider) Type() string { } func (p *Provider) Run(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric) v1alpha1.Measurement { - var ( - err error - ) - startTime := metav1.Now() // Measurement to pass back @@ -53,10 +49,11 @@ func (p *Provider) Run(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric) v1alph Method: "GET", // TODO maybe make this configurable....also implies we will need body templates } - request.URL, err = url.Parse(metric.Provider.Web.URL) + url, err := url.Parse(metric.Provider.Web.URL) if err != nil { return metricutil.MarkMeasurementError(measurement, err) } + request.URL = url for _, header := range metric.Provider.Web.Headers { request.Header.Set(header.Key, header.Value) @@ -64,12 +61,14 @@ func (p *Provider) Run(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric) v1alph // Send Request response, err := p.client.Do(request) - if err != nil || response.StatusCode < 200 || response.StatusCode >= 300 { + if err != nil { return metricutil.MarkMeasurementError(measurement, err) + } else if response.StatusCode < 200 || response.StatusCode >= 300 { + return metricutil.MarkMeasurementError(measurement, fmt.Errorf("received non 2xx response code: %v", response.StatusCode)) } value, status, err := p.parseResponse(metric, response) - if err != nil || response.StatusCode != http.StatusOK { + if err != nil { return metricutil.MarkMeasurementError(measurement, err) } @@ -169,15 +168,13 @@ func (p *Provider) GarbageCollect(run *v1alpha1.AnalysisRun, metric v1alpha1.Met } func NewWebMetricHttpClient(metric v1alpha1.Metric) *http.Client { - var ( - timeout time.Duration - ) + var timeout time.Duration // Using a default timeout of 10 seconds - if metric.Provider.Web.Timeout <= 0 { + if metric.Provider.Web.TimeoutSeconds <= 0 { timeout = time.Duration(10) * time.Second } else { - timeout = time.Duration(metric.Provider.Web.Timeout) * time.Second + timeout = time.Duration(metric.Provider.Web.TimeoutSeconds) * time.Second } c := &http.Client{ diff --git a/pkg/apis/rollouts/v1alpha1/analysis_types.go b/pkg/apis/rollouts/v1alpha1/analysis_types.go index fb6f5df02a..a490a90ce1 100644 --- a/pkg/apis/rollouts/v1alpha1/analysis_types.go +++ b/pkg/apis/rollouts/v1alpha1/analysis_types.go @@ -270,10 +270,10 @@ type ScopeDetail struct { } type WebMetric struct { - URL string `json:"url"` - Headers []WebMetricHeader `json:"headers,omitempty"` - Timeout int `json:"timeout,omitempty"` - JSONPath string `json:"jsonPath"` + URL string `json:"url"` + Headers []WebMetricHeader `json:"headers,omitempty"` + TimeoutSeconds int `json:"timeoutSeconds,omitempty"` + JSONPath string `json:"jsonPath"` } type WebMetricHeader struct { diff --git a/pkg/apis/rollouts/v1alpha1/openapi_generated.go b/pkg/apis/rollouts/v1alpha1/openapi_generated.go index 77ea037b56..85e8de95d0 100644 --- a/pkg/apis/rollouts/v1alpha1/openapi_generated.go +++ b/pkg/apis/rollouts/v1alpha1/openapi_generated.go @@ -2356,7 +2356,7 @@ func schema_pkg_apis_rollouts_v1alpha1_WebMetric(ref common.ReferenceCallback) c }, }, }, - "timeout": { + "timeoutSeconds": { SchemaProps: spec.SchemaProps{ Type: []string{"integer"}, Format: "int32", From 5dd2fce7aeca4702cb4c2c3b01c55fd6a2dc0b36 Mon Sep 17 00:00:00 2001 From: Thomas Santerre Date: Tue, 17 Dec 2019 17:55:51 +0900 Subject: [PATCH 21/28] Add simple non 2xx test. --- metricproviders/webmetric/webmetric_test.go | 34 +++++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/metricproviders/webmetric/webmetric_test.go b/metricproviders/webmetric/webmetric_test.go index 91faa6d727..3734315e40 100644 --- a/metricproviders/webmetric/webmetric_test.go +++ b/metricproviders/webmetric/webmetric_test.go @@ -14,6 +14,7 @@ import ( func TestRunSuite(t *testing.T) { // Test Cases var tests = []struct { + webServerStatus int webServerResponse string metric v1alpha1.Metric expectedValue string @@ -21,6 +22,7 @@ func TestRunSuite(t *testing.T) { }{ // When_numberReturnedInJson_And_MatchesConditions_Then_Succeed { + webServerStatus: 200, webServerResponse: `{"key": [{"key2": {"value": 1}}]}`, metric: v1alpha1.Metric{ Name: "foo", @@ -38,7 +40,7 @@ func TestRunSuite(t *testing.T) { }, // When_numberReturnedInJson_And_DoesNotMatcheConditions_Then_Failure { - + webServerStatus: 200, webServerResponse: `{"key": [{"key2": {"value": 0}}]}`, metric: v1alpha1.Metric{ Name: "foo", @@ -56,6 +58,7 @@ func TestRunSuite(t *testing.T) { }, // When_floatReturnedInJson_And_MatchesConditions_Then_Success { + webServerStatus: 200, webServerResponse: `{"key": [{"key2": {"value": 1.1}}]}`, metric: v1alpha1.Metric{ Name: "foo", @@ -73,6 +76,7 @@ func TestRunSuite(t *testing.T) { }, // When_floatReturnedInJson_And_DoesNotMatchConditions_Then_Failure { + webServerStatus: 200, webServerResponse: `{"key": [{"key2": {"value": -1.1}}]}`, metric: v1alpha1.Metric{ Name: "foo", @@ -90,6 +94,7 @@ func TestRunSuite(t *testing.T) { }, // When_stringReturnedInJson_And_MatchesConditions_Then_Succeed { + webServerStatus: 200, webServerResponse: `{"key": [{"key2": {"value": "true"}}]}`, metric: v1alpha1.Metric{ Name: "foo", @@ -107,6 +112,7 @@ func TestRunSuite(t *testing.T) { }, // When_stringReturnedInJson_And_DoesNotMatchConditions_Then_Fail { + webServerStatus: 200, webServerResponse: `{"key": [{"key2": {"value": "true"}}]}`, metric: v1alpha1.Metric{ Name: "foo", @@ -122,6 +128,24 @@ func TestRunSuite(t *testing.T) { expectedValue: "true", expectedPhase: v1alpha1.AnalysisPhaseFailed, }, + // When_non200_Then_Fail + { + webServerStatus: 300, + webServerResponse: `{"key": [{"key2": {"value": "true"}}]}`, + metric: v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "true", + FailureCondition: "true", + Provider: v1alpha1.MetricProvider{ + Web: &v1alpha1.WebMetric{ + // URL: server.URL, + JSONPath: "{$.key[0].key2.value}", + }, + }, + }, + expectedValue: "true", + expectedPhase: v1alpha1.AnalysisPhaseError, + }, } // Run @@ -129,8 +153,12 @@ func TestRunSuite(t *testing.T) { for _, test := range tests { // Server setup with response server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - rw.Header().Set("Content-Type", "application/json") - io.WriteString(rw, test.webServerResponse) + if test.webServerStatus < 200 || test.webServerStatus >= 300 { + http.Error(rw, http.StatusText(test.webServerStatus), test.webServerStatus) + } else { + rw.Header().Set("Content-Type", "application/json") + io.WriteString(rw, test.webServerResponse) + } })) defer server.Close() From e3321fe02ba842ccac87a9501000be524e7cabb9 Mon Sep 17 00:00:00 2001 From: Thomas Santerre Date: Wed, 18 Dec 2019 11:31:33 +0900 Subject: [PATCH 22/28] Add test for headers, fix bug where header is unitialized, causing NPE. --- metricproviders/webmetric/webmetric.go | 1 + metricproviders/webmetric/webmetric_test.go | 25 +++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/metricproviders/webmetric/webmetric.go b/metricproviders/webmetric/webmetric.go index 2f57f00a33..4424a8458f 100644 --- a/metricproviders/webmetric/webmetric.go +++ b/metricproviders/webmetric/webmetric.go @@ -55,6 +55,7 @@ func (p *Provider) Run(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric) v1alph } request.URL = url + request.Header = make(http.Header) for _, header := range metric.Provider.Web.Headers { request.Header.Set(header.Key, header.Value) } diff --git a/metricproviders/webmetric/webmetric_test.go b/metricproviders/webmetric/webmetric_test.go index 3734315e40..89ce1ff291 100644 --- a/metricproviders/webmetric/webmetric_test.go +++ b/metricproviders/webmetric/webmetric_test.go @@ -32,6 +32,7 @@ func TestRunSuite(t *testing.T) { Web: &v1alpha1.WebMetric{ // URL: server.URL, JSONPath: "{$.key[0].key2.value}", + Headers: []v1alpha1.WebMetricHeader{v1alpha1.WebMetricHeader{Key: "key", Value: "value"}}, }, }, }, @@ -146,6 +147,24 @@ func TestRunSuite(t *testing.T) { expectedValue: "true", expectedPhase: v1alpha1.AnalysisPhaseError, }, + // When_non200_Then_Fail + { + webServerStatus: 300, + webServerResponse: `{"key": [{"key2": {"value": "true"}}]}`, + metric: v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "true", + FailureCondition: "true", + Provider: v1alpha1.MetricProvider{ + Web: &v1alpha1.WebMetric{ + URL: "bad://url.com", + JSONPath: "{$.key[0].key2.value}", + }, + }, + }, + expectedValue: "true", + expectedPhase: v1alpha1.AnalysisPhaseError, + }, } // Run @@ -162,8 +181,10 @@ func TestRunSuite(t *testing.T) { })) defer server.Close() - // Need to set this dynamically... - test.metric.Provider.Web.URL = server.URL + // Need to set this dynamically if not present... + if test.metric.Provider.Web.URL == "" { + test.metric.Provider.Web.URL = server.URL + } logCtx := log.WithField("test", "test") From 0feb20bbd76b81cfdd07b775d4de0b6f7a418bcb Mon Sep 17 00:00:00 2001 From: Thomas Santerre Date: Wed, 18 Dec 2019 11:49:10 +0900 Subject: [PATCH 23/28] Add a few more corner cases for HTTP responses. --- metricproviders/webmetric/webmetric_test.go | 82 ++++++++++++++++++--- 1 file changed, 71 insertions(+), 11 deletions(-) diff --git a/metricproviders/webmetric/webmetric_test.go b/metricproviders/webmetric/webmetric_test.go index 89ce1ff291..85eb4825be 100644 --- a/metricproviders/webmetric/webmetric_test.go +++ b/metricproviders/webmetric/webmetric_test.go @@ -14,11 +14,12 @@ import ( func TestRunSuite(t *testing.T) { // Test Cases var tests = []struct { - webServerStatus int - webServerResponse string - metric v1alpha1.Metric - expectedValue string - expectedPhase v1alpha1.AnalysisPhase + webServerStatus int + webServerResponse string + metric v1alpha1.Metric + expectedValue string + expectedPhase v1alpha1.AnalysisPhase + expectedErrorMessage string }{ // When_numberReturnedInJson_And_MatchesConditions_Then_Succeed { @@ -129,7 +130,7 @@ func TestRunSuite(t *testing.T) { expectedValue: "true", expectedPhase: v1alpha1.AnalysisPhaseFailed, }, - // When_non200_Then_Fail + // When_non200_Then_Error { webServerStatus: 300, webServerResponse: `{"key": [{"key2": {"value": "true"}}]}`, @@ -147,9 +148,9 @@ func TestRunSuite(t *testing.T) { expectedValue: "true", expectedPhase: v1alpha1.AnalysisPhaseError, }, - // When_non200_Then_Fail + // When_BadURL_Then_Fail { - webServerStatus: 300, + webServerStatus: 200, webServerResponse: `{"key": [{"key2": {"value": "true"}}]}`, metric: v1alpha1.Metric{ Name: "foo", @@ -162,8 +163,63 @@ func TestRunSuite(t *testing.T) { }, }, }, - expectedValue: "true", - expectedPhase: v1alpha1.AnalysisPhaseError, + expectedValue: "true", + expectedPhase: v1alpha1.AnalysisPhaseError, + expectedErrorMessage: "unsupported protocol scheme", + }, + // When_200Response_And_EmptyBody_Then_Error + { + webServerStatus: 200, + webServerResponse: ``, + metric: v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "true", + FailureCondition: "true", + Provider: v1alpha1.MetricProvider{ + Web: &v1alpha1.WebMetric{ + JSONPath: "{$.key[0].key2.value}", + }, + }, + }, + expectedValue: "true", + expectedPhase: v1alpha1.AnalysisPhaseError, + expectedErrorMessage: "Could not parse JSON body", + }, + // When_200Response_And_InvalidBody_Then_Error + { + webServerStatus: 200, + webServerResponse: `test: notJson`, + metric: v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "true", + FailureCondition: "true", + Provider: v1alpha1.MetricProvider{ + Web: &v1alpha1.WebMetric{ + JSONPath: "{$.key[0].key2.value}", + }, + }, + }, + expectedValue: "true", + expectedPhase: v1alpha1.AnalysisPhaseError, + expectedErrorMessage: "Could not parse JSON body", + }, + // When_200Response_And_JsonPathHasNoMatch_Then_Error + { + webServerStatus: 200, + webServerResponse: `{"key": [{"key2": {"value": "true"}}]}`, + metric: v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "true", + FailureCondition: "true", + Provider: v1alpha1.MetricProvider{ + Web: &v1alpha1.WebMetric{ + JSONPath: "{$.key[0].key2.novalue}", + }, + }, + }, + expectedValue: "true", + expectedPhase: v1alpha1.AnalysisPhaseError, + expectedErrorMessage: "Could not find JSONPath in body", }, } @@ -176,7 +232,9 @@ func TestRunSuite(t *testing.T) { http.Error(rw, http.StatusText(test.webServerStatus), test.webServerStatus) } else { rw.Header().Set("Content-Type", "application/json") - io.WriteString(rw, test.webServerResponse) + if test.webServerResponse != "" { + io.WriteString(rw, test.webServerResponse) + } } })) defer server.Close() @@ -209,6 +267,8 @@ func TestRunSuite(t *testing.T) { assert.NotNil(t, measurement.StartedAt) assert.Equal(t, test.expectedValue, measurement.Value) assert.NotNil(t, measurement.FinishedAt) + case v1alpha1.AnalysisPhaseError: + assert.Contains(t, measurement.Message, test.expectedErrorMessage) } } From 81b91e15a2bf197d68ee4d19fc24c2c0a6a426d2 Mon Sep 17 00:00:00 2001 From: Thomas Santerre Date: Wed, 18 Dec 2019 11:59:16 +0900 Subject: [PATCH 24/28] golang-ci fix --- metricproviders/webmetric/webmetric_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metricproviders/webmetric/webmetric_test.go b/metricproviders/webmetric/webmetric_test.go index 85eb4825be..195e415861 100644 --- a/metricproviders/webmetric/webmetric_test.go +++ b/metricproviders/webmetric/webmetric_test.go @@ -33,7 +33,7 @@ func TestRunSuite(t *testing.T) { Web: &v1alpha1.WebMetric{ // URL: server.URL, JSONPath: "{$.key[0].key2.value}", - Headers: []v1alpha1.WebMetricHeader{v1alpha1.WebMetricHeader{Key: "key", Value: "value"}}, + Headers: []v1alpha1.WebMetricHeader{{Key: "key", Value: "value"}}, }, }, }, From eb30b3652eca342e8cc95ca3d5163b2dc977eb9e Mon Sep 17 00:00:00 2001 From: Thomas Santerre Date: Wed, 8 Jan 2020 00:01:17 +0900 Subject: [PATCH 25/28] First pass at a using the evaluation engine to enforce typing. --- Gopkg.lock | 1 + manifests/crds/analysis-run-crd.yaml | 12 +++++++ manifests/crds/analysis-template-crd.yaml | 12 +++++++ manifests/crds/experiment-crd.yaml | 12 +++++++ manifests/crds/rollout-crd.yaml | 12 +++++++ metricproviders/webmetric/webmetric.go | 32 +----------------- metricproviders/webmetric/webmetric_test.go | 33 +++++-------------- .../rollouts/v1alpha1/openapi_generated.go | 2 +- utils/evaluate/evaluate.go | 16 +++++++++ 9 files changed, 75 insertions(+), 57 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index faacb10aa7..4783709ea4 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1377,6 +1377,7 @@ "k8s.io/client-go/tools/clientcmd", "k8s.io/client-go/tools/record", "k8s.io/client-go/util/flowcontrol", + "k8s.io/client-go/util/jsonpath", "k8s.io/client-go/util/workqueue", "k8s.io/code-generator/cmd/client-gen", "k8s.io/klog", diff --git a/manifests/crds/analysis-run-crd.yaml b/manifests/crds/analysis-run-crd.yaml index 777dbc2ad1..18018f2a20 100644 --- a/manifests/crds/analysis-run-crd.yaml +++ b/manifests/crds/analysis-run-crd.yaml @@ -660,6 +660,10 @@ spec: - containerPort type: object type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map readinessProbe: properties: exec: @@ -1734,6 +1738,10 @@ spec: - containerPort type: object type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map readinessProbe: properties: exec: @@ -2113,6 +2121,10 @@ spec: - whenUnsatisfiable type: object type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map volumes: items: properties: diff --git a/manifests/crds/analysis-template-crd.yaml b/manifests/crds/analysis-template-crd.yaml index f7076c6189..9d4e359592 100644 --- a/manifests/crds/analysis-template-crd.yaml +++ b/manifests/crds/analysis-template-crd.yaml @@ -654,6 +654,10 @@ spec: - containerPort type: object type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map readinessProbe: properties: exec: @@ -1728,6 +1732,10 @@ spec: - containerPort type: object type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map readinessProbe: properties: exec: @@ -2107,6 +2115,10 @@ spec: - whenUnsatisfiable type: object type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map volumes: items: properties: diff --git a/manifests/crds/experiment-crd.yaml b/manifests/crds/experiment-crd.yaml index 382d9e1c23..61d3178d97 100644 --- a/manifests/crds/experiment-crd.yaml +++ b/manifests/crds/experiment-crd.yaml @@ -647,6 +647,10 @@ spec: - containerPort type: object type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map readinessProbe: properties: exec: @@ -1721,6 +1725,10 @@ spec: - containerPort type: object type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map readinessProbe: properties: exec: @@ -2100,6 +2108,10 @@ spec: - whenUnsatisfiable type: object type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map volumes: items: properties: diff --git a/manifests/crds/rollout-crd.yaml b/manifests/crds/rollout-crd.yaml index 995fd997ab..a6d3ad71a2 100644 --- a/manifests/crds/rollout-crd.yaml +++ b/manifests/crds/rollout-crd.yaml @@ -825,6 +825,10 @@ spec: - containerPort type: object type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map readinessProbe: properties: exec: @@ -1899,6 +1903,10 @@ spec: - containerPort type: object type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map readinessProbe: properties: exec: @@ -2278,6 +2286,10 @@ spec: - whenUnsatisfiable type: object type: array + x-kubernetes-list-map-keys: + - topologyKey + - whenUnsatisfiable + x-kubernetes-list-type: map volumes: items: properties: diff --git a/metricproviders/webmetric/webmetric.go b/metricproviders/webmetric/webmetric.go index 4424a8458f..f3cc63635b 100644 --- a/metricproviders/webmetric/webmetric.go +++ b/metricproviders/webmetric/webmetric.go @@ -7,7 +7,6 @@ import ( "io/ioutil" "net/http" "net/url" - "strconv" "time" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" @@ -101,10 +100,7 @@ func (p *Provider) parseResponse(metric v1alpha1.Metric, response *http.Response } out := buf.String() - // Try to get the right primitive - outInterface := parsePrimitiveFromString(out) - - status := p.evaluateResponse(metric, outInterface) + status := p.evaluateResponse(metric, out) return out, status, nil } @@ -199,29 +195,3 @@ func NewWebMetricProvider(logCtx log.Entry, client *http.Client, jsonParser *jso jsonParser: jsonParser, } } - -func parsePrimitiveFromString(in string) interface{} { - // Chain ordering as follows: - // int -> float -> bool -> string - - // 64 bit Int conversion - inAsInt, err := strconv.ParseInt(in, 10, 64) - if err == nil { - return inAsInt - } - - // Float conversion - inAsFloat, err := strconv.ParseFloat(in, 64) - if err == nil { - return inAsFloat - } - - // Bool conversion - inAsBool, err := strconv.ParseBool(in) - if err == nil { - return inAsBool - } - - // Else - return in -} diff --git a/metricproviders/webmetric/webmetric_test.go b/metricproviders/webmetric/webmetric_test.go index 195e415861..d4a2a77019 100644 --- a/metricproviders/webmetric/webmetric_test.go +++ b/metricproviders/webmetric/webmetric_test.go @@ -27,8 +27,8 @@ func TestRunSuite(t *testing.T) { webServerResponse: `{"key": [{"key2": {"value": 1}}]}`, metric: v1alpha1.Metric{ Name: "foo", - SuccessCondition: "result > 0", - FailureCondition: "result <= 0", + SuccessCondition: "asInt(result) > 0", + FailureCondition: "asInt(result) <= 0", Provider: v1alpha1.MetricProvider{ Web: &v1alpha1.WebMetric{ // URL: server.URL, @@ -46,8 +46,8 @@ func TestRunSuite(t *testing.T) { webServerResponse: `{"key": [{"key2": {"value": 0}}]}`, metric: v1alpha1.Metric{ Name: "foo", - SuccessCondition: "result > 0", - FailureCondition: "result <= 0", + SuccessCondition: "asInt(result) > 0", + FailureCondition: "asInt(result) <= 0", Provider: v1alpha1.MetricProvider{ Web: &v1alpha1.WebMetric{ // URL: server.URL, @@ -64,8 +64,8 @@ func TestRunSuite(t *testing.T) { webServerResponse: `{"key": [{"key2": {"value": 1.1}}]}`, metric: v1alpha1.Metric{ Name: "foo", - SuccessCondition: "result > 0", - FailureCondition: "result <= 0", + SuccessCondition: "asFloat(result) > 0", + FailureCondition: "asFloat(result) <= 0", Provider: v1alpha1.MetricProvider{ Web: &v1alpha1.WebMetric{ // URL: server.URL, @@ -82,8 +82,8 @@ func TestRunSuite(t *testing.T) { webServerResponse: `{"key": [{"key2": {"value": -1.1}}]}`, metric: v1alpha1.Metric{ Name: "foo", - SuccessCondition: "result > 0", - FailureCondition: "result <= 0", + SuccessCondition: "asFloat(result) > 0", + FailureCondition: "asFloat(result) <= 0", Provider: v1alpha1.MetricProvider{ Web: &v1alpha1.WebMetric{ // URL: server.URL, @@ -277,20 +277,3 @@ func TestRunSuite(t *testing.T) { func newAnalysisRun() *v1alpha1.AnalysisRun { return &v1alpha1.AnalysisRun{} } - -func TestParsePrimitiveSuite(t *testing.T) { - var tests = []struct { - in string - out interface{} - }{ - {"1", int64(1)}, - {"true", true}, - {"1.1", float64(1.1)}, - {"String", "String"}, - } - - for _, test := range tests { - result := parsePrimitiveFromString(test.in) - assert.Equal(t, test.out, result) - } -} diff --git a/pkg/apis/rollouts/v1alpha1/openapi_generated.go b/pkg/apis/rollouts/v1alpha1/openapi_generated.go index 85e8de95d0..252894d365 100644 --- a/pkg/apis/rollouts/v1alpha1/openapi_generated.go +++ b/pkg/apis/rollouts/v1alpha1/openapi_generated.go @@ -1,7 +1,7 @@ // +build !ignore_autogenerated /* -Copyright 2019 The Kubernetes sample-controller Authors. +Copyright 2020 The Kubernetes sample-controller Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/utils/evaluate/evaluate.go b/utils/evaluate/evaluate.go index f2a5d752f2..7a382d6700 100644 --- a/utils/evaluate/evaluate.go +++ b/utils/evaluate/evaluate.go @@ -1,6 +1,8 @@ package evaluate import ( + "strconv" + "github.com/antonmedv/expr" ) @@ -8,6 +10,20 @@ import ( func EvalCondition(resultValue interface{}, condition string) (bool, error) { env := map[string]interface{}{ "result": resultValue, + "asInt": func(in string) int64 { + inAsInt, err := strconv.ParseInt(in, 10, 64) + if err == nil { + return inAsInt + } + panic(err) + }, + "asFloat": func(in string) float64 { + inAsFloat, err := strconv.ParseFloat(in, 64) + if err == nil { + return inAsFloat + } + panic(err) + }, } program, err := expr.Compile(condition, expr.Env(env), expr.AsBool()) From 6bd55771aca47b79ec2f1d52b2906f628b7510d4 Mon Sep 17 00:00:00 2001 From: Thomas Santerre Date: Sun, 12 Jan 2020 15:30:01 +0900 Subject: [PATCH 26/28] Increment numProviders when Web is present. --- utils/analysis/factory.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utils/analysis/factory.go b/utils/analysis/factory.go index 6c0479633b..5332edb340 100644 --- a/utils/analysis/factory.go +++ b/utils/analysis/factory.go @@ -104,6 +104,9 @@ func ValidateMetric(metric v1alpha1.Metric) error { if metric.Provider.Job != nil { numProviders++ } + if metric.Provider.Web != nil { + numProviders++ + } if numProviders == 0 { return fmt.Errorf("no provider specified") } From 79d8718441ff7ef341e7999fdd04d49b3bdde729 Mon Sep 17 00:00:00 2001 From: Thomas Santerre Date: Wed, 15 Jan 2020 09:41:05 +0900 Subject: [PATCH 27/28] Fix leftover merge conflict. --- mkdocs.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 62d13bdc83..9bd4ac1d0f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -24,14 +24,10 @@ nav: - features/index.md - BlueGreen: features/bluegreen.md - Canary: features/canary.md -<<<<<<< HEAD - - Traffic Management: features/traffic-management/index.md -======= - Traffic Management: - Overview: features/traffic-management/index.md - Istio: features/traffic-management/istio.md - NGINX: features/traffic-management/nginx.md ->>>>>>> c79494b99e33e54ab9734f885c196d1d81e8f1f6 - Kubectl Plugin: features/kubectl-plugin.md - HPA Support: features/hpa-support.md - Kustomize Support: features/kustomize.md From e476ea7e512fd1019e8e800cc7ee0da380b2373c Mon Sep 17 00:00:00 2001 From: Thomas Santerre Date: Wed, 15 Jan 2020 10:18:17 +0900 Subject: [PATCH 28/28] Add tests to cover panic cases for the type conversions, and also add a recover clause to the eval logic. NOTE: this clause might not be necessary. --- utils/evaluate/evaluate.go | 45 +++++++++++++++-------- utils/evaluate/evaluate_test.go | 64 +++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 15 deletions(-) diff --git a/utils/evaluate/evaluate.go b/utils/evaluate/evaluate.go index 7a382d6700..0c3f4d5de8 100644 --- a/utils/evaluate/evaluate.go +++ b/utils/evaluate/evaluate.go @@ -1,6 +1,7 @@ package evaluate import ( + "fmt" "strconv" "github.com/antonmedv/expr" @@ -8,24 +9,22 @@ import ( // EvalCondition evaluates the condition with the resultValue as an input func EvalCondition(resultValue interface{}, condition string) (bool, error) { + var err error + env := map[string]interface{}{ - "result": resultValue, - "asInt": func(in string) int64 { - inAsInt, err := strconv.ParseInt(in, 10, 64) - if err == nil { - return inAsInt - } - panic(err) - }, - "asFloat": func(in string) float64 { - inAsFloat, err := strconv.ParseFloat(in, 64) - if err == nil { - return inAsFloat - } - panic(err) - }, + "result": resultValue, + "asInt": asInt, + "asFloat": asFloat, } + // Setup a clean recovery in case the eval code panics. + // TODO: this actually might not be nessary since it seems evaluation lib handles panics from functions internally + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("evaluation logic panicked: %v", r) + } + }() + program, err := expr.Compile(condition, expr.Env(env), expr.AsBool()) if err != nil { return false, err @@ -38,3 +37,19 @@ func EvalCondition(resultValue interface{}, condition string) (bool, error) { return output.(bool), err } + +func asInt(in string) int64 { + inAsInt, err := strconv.ParseInt(in, 10, 64) + if err == nil { + return inAsInt + } + panic(err) +} + +func asFloat(in string) float64 { + inAsFloat, err := strconv.ParseFloat(in, 64) + if err == nil { + return inAsFloat + } + panic(err) +} diff --git a/utils/evaluate/evaluate_test.go b/utils/evaluate/evaluate_test.go index 796a1718e2..9daa2c32c4 100644 --- a/utils/evaluate/evaluate_test.go +++ b/utils/evaluate/evaluate_test.go @@ -56,3 +56,67 @@ func TestEvaluateInvalidStruct(t *testing.T) { assert.Errorf(t, err, "") assert.False(t, b) } + +func TestEvaluateAsIntPanic(t *testing.T) { + b, err := EvalCondition("1.1", "asInt(result) == 1.1") + assert.Errorf(t, err, "got expected error: %v", err) + assert.False(t, b) +} + +func TestEvaluateAsInt(t *testing.T) { + b, err := EvalCondition("1", "asInt(result) == 1") + assert.NoError(t, err) + assert.True(t, b) +} + +func TestEvaluateAsFloatPanic(t *testing.T) { + b, err := EvalCondition("NotANum", "asFloat(result) == 1.1") + assert.Errorf(t, err, "got expected error: %v", err) + assert.False(t, b) +} + +func TestEvaluateAsFloat(t *testing.T) { + b, err := EvalCondition("1.1", "asFloat(result) == 1.1") + assert.NoError(t, err) + assert.True(t, b) +} + +func TestAsInt(t *testing.T) { + tests := []struct { + input string + output int64 + shouldPanic bool + }{ + {"1", 1, false}, + {"notint", 1, true}, + {"1.1", 1, true}, + } + + for _, test := range tests { + if test.shouldPanic { + assert.Panics(t, func() { asInt(test.input) }) + } else { + assert.Equal(t, test.output, asInt(test.input)) + } + } +} + +func TestAsFloat(t *testing.T) { + tests := []struct { + input string + output float64 + shouldPanic bool + }{ + {"1", 1, false}, + {"notfloat", 1, true}, + {"1.1", 1.1, false}, + } + + for _, test := range tests { + if test.shouldPanic { + assert.Panics(t, func() { asFloat(test.input) }) + } else { + assert.Equal(t, test.output, asFloat(test.input)) + } + } +}