Skip to content

Commit

Permalink
feat: support namespaced Datadog credentials (#1)
Browse files Browse the repository at this point in the history
Adding support for multi datadog accounts.
Creating a CI file with the minimum.
Documentation updated and fork CI updated.
Update docs/analysis/datadog.md

Co-authored-by: Thibault Jamet <tjamet@users.noreply.github.com>
  • Loading branch information
ariadnarouco and tjamet authored Aug 13, 2024
1 parent 708db68 commit ff807af
Show file tree
Hide file tree
Showing 23 changed files with 1,004 additions and 423 deletions.
4 changes: 2 additions & 2 deletions analysis/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ func (c *Controller) runMeasurements(run *v1alpha1.AnalysisRun, tasks []metricTa
logger := logutil.WithRedactor(*logutil.WithAnalysisRun(run).WithField("metric", t.metric.Name), secrets)

var newMeasurement v1alpha1.Measurement
provider, providerErr := c.newProvider(*logger, t.metric)
provider, providerErr := c.newProvider(*logger, run.Namespace, t.metric)
if providerErr != nil {
log.Errorf("Error in getting metric provider :%v", providerErr)
if t.incompleteMeasurement != nil {
Expand Down Expand Up @@ -744,7 +744,7 @@ func (c *Controller) garbageCollectMeasurements(run *v1alpha1.AnalysisRun, measu
continue
}
logger := logutil.WithAnalysisRun(run).WithField("metric", metric.Name)
provider, err := c.newProvider(*logger, metric)
provider, err := c.newProvider(*logger, run.Namespace, metric)
if err != nil {
errors = append(errors, err)
continue
Expand Down
2 changes: 1 addition & 1 deletion analysis/analysis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1113,7 +1113,7 @@ func TestGarbageCollectArgResolution(t *testing.T) {
defer f.Close()
c, _, _ := f.newController(noResyncPeriodFunc)

c.newProvider = func(logCtx log.Entry, metric v1alpha1.Metric) (metric.Provider, error) {
c.newProvider = func(logCtx log.Entry, namespace string, metric v1alpha1.Metric) (metric.Provider, error) {
assert.Equal(t, "https://prometheus.kubeaddons:8080", metric.Provider.Prometheus.Address)
return f.provider, nil
}
Expand Down
2 changes: 1 addition & 1 deletion analysis/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type Controller struct {

metricsServer *metrics.MetricsServer

newProvider func(logCtx log.Entry, metric v1alpha1.Metric) (metric.Provider, error)
newProvider func(logCtx log.Entry, namespace string, metric v1alpha1.Metric) (metric.Provider, error)

// used for unit testing
enqueueAnalysis func(obj any)
Expand Down
4 changes: 2 additions & 2 deletions analysis/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (f *fixture) newController(resync resyncFunc) (*Controller, informers.Share
c.enqueueAnalysis(obj)
}
f.provider = &mocks.Provider{}
c.newProvider = func(logCtx log.Entry, metric v1alpha1.Metric) (metric.Provider, error) {
c.newProvider = func(logCtx log.Entry, namespace string, metric v1alpha1.Metric) (metric.Provider, error) {
return f.provider, nil
}

Expand Down Expand Up @@ -370,7 +370,7 @@ func TestFailedToCreateProviderError(t *testing.T) {
f.objects = append(f.objects, ar)

c, i, k8sI := f.newController(noResyncPeriodFunc)
c.newProvider = func(logCtx log.Entry, metric v1alpha1.Metric) (metric.Provider, error) {
c.newProvider = func(logCtx log.Entry, namespace string, metric v1alpha1.Metric) (metric.Provider, error) {
return nil, fmt.Errorf("failed to create provider")
}

Expand Down
42 changes: 42 additions & 0 deletions docs/analysis/datadog.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,48 @@ stringData:

`apiVersion` here is different from the `apiVersion` from the Datadog configuration above.

!!! important
###### Namespaced secret
Datadog integration supports referring to secrets inside the same namespace as argo-rollouts (by default)
or referring to a secret in the same namespace as the `AnalysisTemplate`.

To use a secret from the `AnalysisTemplate` namespace, include a `secretRef` section in the template, specifying the `name` of the secret and setting the `namespaced` property to `true`.

The process for retrieving Datadog credentials is as follows:
1. **If a `secretRef` is defined in the `AnalysisTemplate`:** Argo Rollouts will search for the secret with the specified name in the namespace where the template resides.
2. **If the secret is not found in the specified namespace:** Argo Rollouts will then check the environment variables.
3. **If the credentials are not found in environment variables:** Argo Rollouts will look for a secret named "Datadog" in the namespace where Argo Rollouts itself is deployed.

---

Let me know if there's anything else you'd like to adjust!

```yaml
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: loq-error-rate
spec:
args:
- name: service-name
metrics:
- name: error-rate
interval: 5m
successCondition: result <= 0.01
failureLimit: 3
provider:
datadog:
apiVersion: v2
interval: 5m
secretRef:
name: "mysecret"
namespaced: true
query: |
sum:requests.error.rate{service:{{args.service-name}}}
```



### Working with Datadog API v2

!!! important
Expand Down
33 changes: 33 additions & 0 deletions docs/features/kustomize/rollout_cr_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,17 @@
},
"query": {
"type": "string"
},
"secretRef": {
"properties": {
"name": {
"type": "string"
},
"namespaced": {
"type": "boolean"
}
},
"type": "object"
}
},
"type": "object"
Expand Down Expand Up @@ -5131,6 +5142,17 @@
},
"query": {
"type": "string"
},
"secretRef": {
"properties": {
"name": {
"type": "string"
},
"namespaced": {
"type": "boolean"
}
},
"type": "object"
}
},
"type": "object"
Expand Down Expand Up @@ -9995,6 +10017,17 @@
},
"query": {
"type": "string"
},
"secretRef": {
"properties": {
"name": {
"type": "string"
},
"namespaced": {
"type": "boolean"
}
},
"type": "object"
}
},
"type": "object"
Expand Down
7 changes: 7 additions & 0 deletions manifests/crds/analysis-run-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,13 @@ spec:
type: object
query:
type: string
secretRef:
properties:
name:
type: string
namespaced:
type: boolean
type: object
type: object
graphite:
properties:
Expand Down
7 changes: 7 additions & 0 deletions manifests/crds/analysis-template-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,13 @@ spec:
type: object
query:
type: string
secretRef:
properties:
name:
type: string
namespaced:
type: boolean
type: object
type: object
graphite:
properties:
Expand Down
7 changes: 7 additions & 0 deletions manifests/crds/cluster-analysis-template-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,13 @@ spec:
type: object
query:
type: string
secretRef:
properties:
name:
type: string
namespaced:
type: boolean
type: object
type: object
graphite:
properties:
Expand Down
21 changes: 21 additions & 0 deletions manifests/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,13 @@ spec:
type: object
query:
type: string
secretRef:
properties:
name:
type: string
namespaced:
type: boolean
type: object
type: object
graphite:
properties:
Expand Down Expand Up @@ -3506,6 +3513,13 @@ spec:
type: object
query:
type: string
secretRef:
properties:
name:
type: string
namespaced:
type: boolean
type: object
type: object
graphite:
properties:
Expand Down Expand Up @@ -6682,6 +6696,13 @@ spec:
type: object
query:
type: string
secretRef:
properties:
name:
type: string
namespaced:
type: boolean
type: object
type: object
graphite:
properties:
Expand Down
67 changes: 28 additions & 39 deletions metricproviders/datadog/datadog.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@ package datadog

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"time"

"github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
Expand All @@ -22,7 +19,6 @@ import (
timeutil "github.com/argoproj/argo-rollouts/utils/time"

log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)

Expand Down Expand Up @@ -369,18 +365,6 @@ func (p *Provider) GarbageCollect(run *v1alpha1.AnalysisRun, metric v1alpha1.Met
return nil
}

func lookupKeysInEnv(keys []string) map[string]string {
valuesByKey := make(map[string]string)
for i := range keys {
key := keys[i]
formattedKey := strings.ToUpper(strings.ReplaceAll(key, "-", "_"))
if value, ok := os.LookupEnv(fmt.Sprintf("DD_%s", formattedKey)); ok {
valuesByKey[key] = value
}
}
return valuesByKey
}

// The current gen tooling we are using can't generate CRD with all the validations we need.
// This is unfortunate, user has more ways to deliver an invalid Analysis Template vs
// being rejected on delivery by k8s (and allowing for a validation step if desired in CI/CD).
Expand Down Expand Up @@ -422,32 +406,13 @@ func validateIncomingProps(dd *v1alpha1.DatadogMetric) error {
return nil
}

func NewDatadogProvider(logCtx log.Entry, kubeclientset kubernetes.Interface, metric v1alpha1.Metric) (*Provider, error) {
ns := defaults.Namespace()

apiKey := ""
appKey := ""
address := ""
secretKeys := []string{DatadogApiKey, DatadogAppKey, DatadogAddress}
envValuesByKey := lookupKeysInEnv(secretKeys)
if len(envValuesByKey) == len(secretKeys) {
apiKey = envValuesByKey[DatadogApiKey]
appKey = envValuesByKey[DatadogAppKey]
address = envValuesByKey[DatadogAddress]
} else {
secret, err := kubeclientset.CoreV1().Secrets(ns).Get(context.TODO(), DatadogTokensSecretName, metav1.GetOptions{})
if err != nil {
return nil, err
}
apiKey = string(secret.Data[DatadogApiKey])
appKey = string(secret.Data[DatadogAppKey])
if _, hasAddress := secret.Data[DatadogAddress]; hasAddress {
address = string(secret.Data[DatadogAddress])
}
func NewDatadogProvider(logCtx log.Entry, kubeclientset kubernetes.Interface, namespace string, metric v1alpha1.Metric) (*Provider, error) {
address, apiKey, appKey, err := findCredentials(logCtx, kubeclientset, namespace, metric)
if err != nil {
return nil, err
}

if apiKey != "" && appKey != "" {

err := validateIncomingProps(metric.Provider.Datadog)
if err != nil {
return nil, err
Expand All @@ -465,3 +430,27 @@ func NewDatadogProvider(logCtx log.Entry, kubeclientset kubernetes.Interface, me
return nil, errors.New("API or App token not found")
}
}

func findCredentials(logCtx log.Entry, kubeclientset kubernetes.Interface, namespace string, metric v1alpha1.Metric) (string, string, string, error) {
finders := []CredentialsFinder{}
secretName := metric.Provider.Datadog.SecretRef.Name
namespaced := metric.Provider.Datadog.SecretRef.Namespaced

if secretName != "" {
if namespaced {
finders = append(finders, NewSecretFinder(kubeclientset, secretName, namespace))
} else {
finders = append(finders, NewSecretFinder(kubeclientset, secretName, defaults.Namespace()))
}
}
finders = append(finders, NewEnvVariablesFinder(), NewSecretFinder(kubeclientset, DatadogTokensSecretName, defaults.Namespace()))

for _, finder := range finders {
address, apiKey, appKey := finder.FindCredentials(logCtx)
if address != "" && apiKey != "" && appKey != "" {
return address, apiKey, appKey, nil
}
}

return "", "", "", errors.New("failed to find the credentials for datadog provider")
}
Loading

0 comments on commit ff807af

Please sign in to comment.