Skip to content

Commit

Permalink
feat: web metrics preserve data types, allow insecure tls, and make j…
Browse files Browse the repository at this point in the history
…sonPath optional (#731)
  • Loading branch information
jessesuen authored Sep 26, 2020
1 parent ed86374 commit b5aa6d7
Show file tree
Hide file tree
Showing 14 changed files with 490 additions and 91 deletions.
22 changes: 15 additions & 7 deletions docs/features/analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,7 @@ data:

## Web Metrics

A webhook can be used to call out to some external service to obtain the measurement. This example makes a HTTP GET request to some URL. The webhook response must return JSON content. The result of the `jsonPath` expression will be assigned to the `result` variable that can be referenced in the `successCondition` and `failureCondition` expressions.
A webhook can be used to call out to some external service to obtain the measurement. This example makes a HTTP GET request to some URL. The webhook response must return JSON content. The result of the optional `jsonPath` expression will be assigned to the `result` variable that can be referenced in the `successCondition` and `failureCondition` expressions. If omitted, will use the entire body of the as the result variable.

```yaml
metrics:
Expand All @@ -853,23 +853,31 @@ A webhook can be used to call out to some external service to obtain the measure
jsonPath: "{$.results.ok}"
```

In this example, the measurement is successful if the json response returns `"true"` for the nested `ok` field.
In the following example, given the payload, the measurement will be Successful if the `data.ok` field was `true`, and the `data.successPercent`
was greater than `0.90`

```json
{ "results": { "ok": "true", "successPercent": 0.95 } }
{
"data": {
"ok": true,
"successPercent": 0.95
}
}
```

For success conditions that need to evaluate a numeric return value the `asInt` or `asFloat` functions can be used to convert the result value.

```yaml
metrics:
- name: webmetric
successCondition: "asFloat(result) >= 0.90"
successCondition: "result.ok && result.successPercent >= 0.90"
provider:
web:
url: "http://my-server.com/api/v1/measurement?service={{ args.service-name }}"
headers:
- key: Authorization
value: "Bearer {{ args.api-token }}"
jsonPath: "{$.results.successPercent}"
jsonPath: "{$.data}"
```

NOTE: if the result is a string, two convenience functions `asInt` and `asFloat` are provided
to convert a result value to a numeric type so that mathematical comparison operators can be used
(e.g. >, <, >=, <=).
3 changes: 2 additions & 1 deletion manifests/crds/analysis-run-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2707,14 +2707,15 @@ spec:
- value
type: object
type: array
insecure:
type: boolean
jsonPath:
type: string
timeoutSeconds:
type: integer
url:
type: string
required:
- jsonPath
- url
type: object
type: object
Expand Down
3 changes: 2 additions & 1 deletion manifests/crds/analysis-template-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2701,14 +2701,15 @@ spec:
- value
type: object
type: array
insecure:
type: boolean
jsonPath:
type: string
timeoutSeconds:
type: integer
url:
type: string
required:
- jsonPath
- url
type: object
type: object
Expand Down
3 changes: 2 additions & 1 deletion manifests/crds/cluster-analysis-template-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2701,14 +2701,15 @@ spec:
- value
type: object
type: array
insecure:
type: boolean
jsonPath:
type: string
timeoutSeconds:
type: integer
url:
type: string
required:
- jsonPath
- url
type: object
type: object
Expand Down
13 changes: 5 additions & 8 deletions manifests/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
controller-gen.kubebuilder.io/version: v0.3.0
name: analysisruns.argoproj.io
spec:
additionalPrinterColumns:
Expand Down Expand Up @@ -2715,7 +2715,6 @@ spec:
url:
type: string
required:
- jsonPath
- url
type: object
type: object
Expand Down Expand Up @@ -2814,7 +2813,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
controller-gen.kubebuilder.io/version: v0.3.0
name: analysistemplates.argoproj.io
spec:
group: argoproj.io
Expand Down Expand Up @@ -5520,7 +5519,6 @@ spec:
url:
type: string
required:
- jsonPath
- url
type: object
type: object
Expand All @@ -5547,7 +5545,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
controller-gen.kubebuilder.io/version: v0.3.0
name: clusteranalysistemplates.argoproj.io
spec:
group: argoproj.io
Expand Down Expand Up @@ -8253,7 +8251,6 @@ spec:
url:
type: string
required:
- jsonPath
- url
type: object
type: object
Expand All @@ -8280,7 +8277,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
controller-gen.kubebuilder.io/version: v0.3.0
name: experiments.argoproj.io
spec:
additionalPrinterColumns:
Expand Down Expand Up @@ -10955,7 +10952,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
controller-gen.kubebuilder.io/version: v0.3.0
name: rollouts.argoproj.io
spec:
additionalPrinterColumns:
Expand Down
13 changes: 5 additions & 8 deletions manifests/namespace-install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
controller-gen.kubebuilder.io/version: v0.3.0
name: analysisruns.argoproj.io
spec:
additionalPrinterColumns:
Expand Down Expand Up @@ -2715,7 +2715,6 @@ spec:
url:
type: string
required:
- jsonPath
- url
type: object
type: object
Expand Down Expand Up @@ -2814,7 +2813,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
controller-gen.kubebuilder.io/version: v0.3.0
name: analysistemplates.argoproj.io
spec:
group: argoproj.io
Expand Down Expand Up @@ -5520,7 +5519,6 @@ spec:
url:
type: string
required:
- jsonPath
- url
type: object
type: object
Expand All @@ -5547,7 +5545,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
controller-gen.kubebuilder.io/version: v0.3.0
name: clusteranalysistemplates.argoproj.io
spec:
group: argoproj.io
Expand Down Expand Up @@ -8253,7 +8251,6 @@ spec:
url:
type: string
required:
- jsonPath
- url
type: object
type: object
Expand All @@ -8280,7 +8277,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
controller-gen.kubebuilder.io/version: v0.3.0
name: experiments.argoproj.io
spec:
additionalPrinterColumns:
Expand Down Expand Up @@ -10955,7 +10952,7 @@ apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.2.5
controller-gen.kubebuilder.io/version: v0.3.0
name: rollouts.argoproj.io
spec:
additionalPrinterColumns:
Expand Down
43 changes: 33 additions & 10 deletions metricproviders/webmetric/webmetric.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package webmetric

import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"time"

metricutil "github.com/argoproj/argo-rollouts/utils/metric"
Expand All @@ -19,7 +21,7 @@ import (
)

const (
//ProviderType indicates the provider is prometheus
// ProviderType indicates the provider is a web metric
ProviderType = "WebMetric"
)

Expand Down Expand Up @@ -96,15 +98,28 @@ func (p *Provider) parseResponse(metric v1alpha1.Metric, response *http.Response
return "", v1alpha1.AnalysisPhaseError, fmt.Errorf("Could not parse JSON body: %v", err)
}

buf := new(bytes.Buffer)
err = p.jsonParser.Execute(buf, data)
fullResults, err := p.jsonParser.FindResults(data)
if err != nil {
return "", v1alpha1.AnalysisPhaseError, fmt.Errorf("Could not find JSONPath in body: %s", err)
}
out := buf.String()
val, valString, err := getValue(fullResults)
if err != nil {
return "", v1alpha1.AnalysisPhaseError, err
}

status := evaluate.EvaluateResult(out, metric, p.logCtx)
return out, status, nil
status := evaluate.EvaluateResult(val, metric, p.logCtx)
return valString, status, nil
}

func getValue(fullResults [][]reflect.Value) (interface{}, string, error) {
for _, results := range fullResults {
for _, r := range results {
val := r.Interface()
valBytes, err := json.Marshal(val)
return val, string(valBytes), err
}
}
return nil, "", errors.New("result of web metric produced no value")
}

// Resume should not be used the WebMetric provider since all the work should occur in the Run method
Expand Down Expand Up @@ -137,14 +152,22 @@ func NewWebMetricHttpClient(metric v1alpha1.Metric) *http.Client {
c := &http.Client{
Timeout: timeout,
}
if metric.Provider.Web.Insecure {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
c.Transport = tr
}
return c
}

func NewWebMetricJsonParser(metric v1alpha1.Metric) (*jsonpath.JSONPath, error) {
jsonParser := jsonpath.New("metrics")

err := jsonParser.Parse(metric.Provider.Web.JSONPath)

jsonPath := metric.Provider.Web.JSONPath
if jsonPath == "" {
jsonPath = "{$}"
}
err := jsonParser.Parse(jsonPath)
return jsonParser, err
}

Expand Down
Loading

0 comments on commit b5aa6d7

Please sign in to comment.