Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation webhook shows multiple errors #382

Merged
merged 5 commits into from
Nov 29, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions mapper/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
const (
InstanceLabel = dtwebhook.LabelInstance
UpdatedViaDynakubeAnnotation = "dynatrace.com/updated-via-operator"
ConflictErrorMessage = "namespace matches two or more DynaKubes which is unsupported. " +
0sewa0 marked this conversation as resolved.
Show resolved Hide resolved
"refine the labels on your namespace metadata or DynaKube/CodeModules specification"
)

type ConflictChecker struct {
Expand All @@ -28,8 +30,7 @@ func (c *ConflictChecker) check(dk *dynatracev1beta1.DynaKube) error {
return nil
}
if c.alreadyUsed {
return errors.New("namespace matches two or more DynaKubes which is unsupported. " +
"refine the labels on your namespace metadata or DynaKube/CodeModules specification")
return errors.New(ConflictErrorMessage)
}
c.alreadyUsed = true
return nil
Expand Down
56 changes: 56 additions & 0 deletions webhook/validation/activegate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package validation

import (
"fmt"

dynatracev1beta1 "github.com/Dynatrace/dynatrace-operator/api/v1beta1"
)

const (
errorConflictingActiveGateSections = `The DynaKube's specification tries to use the deprecated ActiveGate section(s) alongside the new ActiveGate section, which is not supported.
`

errorInvalidActiveGateCapability = `The DynaKube's specification tries to use an invalid capability in ActiveGate section, invalid capability=%s.
Make sure you correctly specify the ActiveGate capabilities in your custom resource.
`

errorDuplicateActiveGateCapability = `The DynaKube's specification tries to specify duplicate capabilities in the ActiveGate section, duplicate capability=%s.
Make sure you don't duplicate an Activegate capability in your custom resource.
`
)

func conflictingActiveGateConfiguration(dv *dynakubeValidator, dynakube *dynatracev1beta1.DynaKube) string {
if dynakube.DeprecatedActiveGateMode() && dynakube.ActiveGateMode() {
log.Info("requested dynakube has conflicting active gate configuration", "name", dynakube.Name, "namespace", dynakube.Namespace)
return errorConflictingActiveGateSections
}
return ""
}

func duplicateActiveGateCapabilities(dv *dynakubeValidator, dynakube *dynatracev1beta1.DynaKube) string {
if dynakube.ActiveGateMode() {
capabilities := dynakube.Spec.ActiveGate.Capabilities
duplicateChecker := map[dynatracev1beta1.CapabilityDisplayName]bool{}
for _, capability := range capabilities {
if duplicateChecker[capability] {
log.Info("requested dynakube has duplicates in the active gate capabilities section", "name", dynakube.Name, "namespace", dynakube.Namespace)
return fmt.Sprintf(errorDuplicateActiveGateCapability, capability)
}
duplicateChecker[capability] = true
}
}
return ""
}

func invalidActiveGateCapabilities(dv *dynakubeValidator, dynakube *dynatracev1beta1.DynaKube) string {
if dynakube.ActiveGateMode() {
capabilities := dynakube.Spec.ActiveGate.Capabilities
for _, capability := range capabilities {
if _, ok := dynatracev1beta1.ActiveGateDisplayNames[capability]; !ok {
log.Info("requested dynakube has invalid active gate capability", "name", dynakube.Name, "namespace", dynakube.Namespace)
return fmt.Sprintf(errorInvalidActiveGateCapability, capability)
}
}
}
return ""
}
97 changes: 97 additions & 0 deletions webhook/validation/activegate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package validation

import (
"fmt"
"testing"

dynatracev1beta1 "github.com/Dynatrace/dynatrace-operator/api/v1beta1"
)

func TestConflictingActiveGateConfiguration(t *testing.T) {
t.Run(`valid dynakube specs`, func(t *testing.T) {

assertAllowedResponseWithoutWarnings(t, &dynatracev1beta1.DynaKube{
ObjectMeta: defaultDynakubeObjectMeta,
Spec: dynatracev1beta1.DynaKubeSpec{
APIURL: testApiUrl,
Routing: dynatracev1beta1.RoutingSpec{
Enabled: true,
},
KubernetesMonitoring: dynatracev1beta1.KubernetesMonitoringSpec{
Enabled: true,
},
},
})

assertAllowedResponseWithoutWarnings(t, &dynatracev1beta1.DynaKube{
ObjectMeta: defaultDynakubeObjectMeta,
Spec: dynatracev1beta1.DynaKubeSpec{
APIURL: testApiUrl,
ActiveGate: dynatracev1beta1.ActiveGateSpec{
Capabilities: []dynatracev1beta1.CapabilityDisplayName{
dynatracev1beta1.RoutingCapability.DisplayName,
dynatracev1beta1.KubeMonCapability.DisplayName,
dynatracev1beta1.DataIngestCapability.DisplayName,
},
},
},
})
})
t.Run(`conflicting dynakube specs`, func(t *testing.T) {
assertDeniedResponse(t,
[]string{errorConflictingActiveGateSections},
&dynatracev1beta1.DynaKube{
ObjectMeta: defaultDynakubeObjectMeta,
Spec: dynatracev1beta1.DynaKubeSpec{
APIURL: testApiUrl,
Routing: dynatracev1beta1.RoutingSpec{
Enabled: true,
},
ActiveGate: dynatracev1beta1.ActiveGateSpec{
Capabilities: []dynatracev1beta1.CapabilityDisplayName{
dynatracev1beta1.RoutingCapability.DisplayName,
},
},
},
})
})
}

func TestDuplicateActiveGateCapabilities(t *testing.T) {

t.Run(`conflicting dynakube specs`, func(t *testing.T) {
assertDeniedResponse(t,
[]string{fmt.Sprintf(errorDuplicateActiveGateCapability, dynatracev1beta1.RoutingCapability.DisplayName)},
&dynatracev1beta1.DynaKube{
ObjectMeta: defaultDynakubeObjectMeta,
Spec: dynatracev1beta1.DynaKubeSpec{
APIURL: testApiUrl,
ActiveGate: dynatracev1beta1.ActiveGateSpec{
Capabilities: []dynatracev1beta1.CapabilityDisplayName{
dynatracev1beta1.RoutingCapability.DisplayName,
dynatracev1beta1.RoutingCapability.DisplayName,
},
},
},
})
})
}

func TestInvalidActiveGateCapabilities(t *testing.T) {

t.Run(`conflicting dynakube specs`, func(t *testing.T) {
assertDeniedResponse(t,
[]string{fmt.Sprintf(errorInvalidActiveGateCapability, "invalid-capability")},
&dynatracev1beta1.DynaKube{
ObjectMeta: defaultDynakubeObjectMeta,
Spec: dynatracev1beta1.DynaKubeSpec{
APIURL: testApiUrl,
ActiveGate: dynatracev1beta1.ActiveGateSpec{
Capabilities: []dynatracev1beta1.CapabilityDisplayName{
"invalid-capability",
},
},
},
})
})
}
20 changes: 20 additions & 0 deletions webhook/validation/api_url.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package validation

import (
dynatracev1beta1 "github.com/Dynatrace/dynatrace-operator/api/v1beta1"
)

const (
exampleApiUrl = "https://ENVIRONMENTID.live.dynatrace.com/api"
errorNoApiUrl = `The DynaKube's specification is missing the API URL or still has the example value set.
Make sure you correctly specify the URL in your custom resource.
`
)

func noApiUrl(dv *dynakubeValidator, dynakube *dynatracev1beta1.DynaKube) string {
if dynakube.Spec.APIURL != "" && dynakube.Spec.APIURL != exampleApiUrl {
return ""
}
log.Info("requested dynakube has no api url", "name", dynakube.Name, "namespace", dynakube.Namespace)
return errorNoApiUrl
}
31 changes: 31 additions & 0 deletions webhook/validation/api_url_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package validation

import (
"testing"

dynatracev1beta1 "github.com/Dynatrace/dynatrace-operator/api/v1beta1"
"github.com/stretchr/testify/assert"
)

func TestHasApiUrl(t *testing.T) {
instance := &dynatracev1beta1.DynaKube{}
assert.Equal(t, errorNoApiUrl, noApiUrl(nil, instance))

instance.Spec.APIURL = testApiUrl
assert.Empty(t, noApiUrl(nil, instance))

t.Run(`missing API URL`, func(t *testing.T) {
assertDeniedResponse(t, []string{errorNoApiUrl}, &dynatracev1beta1.DynaKube{
Spec: dynatracev1beta1.DynaKubeSpec{
APIURL: "",
},
})
})
t.Run(`invalid API URL`, func(t *testing.T) {
assertDeniedResponse(t, []string{errorNoApiUrl}, &dynatracev1beta1.DynaKube{
Spec: dynatracev1beta1.DynaKubeSpec{
APIURL: exampleApiUrl,
},
})
})
}
25 changes: 25 additions & 0 deletions webhook/validation/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package validation

import (
dynatracev1beta1 "github.com/Dynatrace/dynatrace-operator/api/v1beta1"
"github.com/Dynatrace/dynatrace-operator/logger"
)

var log = logger.NewDTLogger().WithName("validation-webhook")

type validator func(dv *dynakubeValidator, dynakube *dynatracev1beta1.DynaKube) string

var validators = []validator{
noApiUrl,
missingCSIDaemonSet,
conflictingActiveGateConfiguration,
invalidActiveGateCapabilities,
duplicateActiveGateCapabilities,
conflictingOneAgentConfiguration,
conflictingNodeSelector,
conflictingNamespaceSelector,
}

var warnings = []validator{
previewWarning,
}
31 changes: 31 additions & 0 deletions webhook/validation/csi_daemonset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package validation

import (
"context"

dynatracev1beta1 "github.com/Dynatrace/dynatrace-operator/api/v1beta1"
dtcsi "github.com/Dynatrace/dynatrace-operator/controllers/csi"
appsv1 "k8s.io/api/apps/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
)

const (
errorCSIRequired = `The Dynakube's specification requires the CSI driver to work. Make sure you deployed the correct manifests.
`
)

func missingCSIDaemonSet(dv *dynakubeValidator, dynakube *dynatracev1beta1.DynaKube) string {
if !dynakube.NeedsCSIDriver() {
return ""
}
csiDaemonSet := appsv1.DaemonSet{}
err := dv.clt.Get(context.TODO(), types.NamespacedName{Name: dtcsi.DaemonSetName, Namespace: dynakube.Namespace}, &csiDaemonSet)
if k8serrors.IsNotFound(err) {
log.Info("requested dynakube uses csi driver, but csi driver is missing in the cluster", "name", dynakube.Name, "namespace", dynakube.Namespace)
return errorCSIRequired
} else if err != nil {
log.Info("error occurred while listing dynakubes", "err", err.Error())
}
return ""
}
35 changes: 35 additions & 0 deletions webhook/validation/csi_daemonset_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package validation

import (
"testing"

dynatracev1beta1 "github.com/Dynatrace/dynatrace-operator/api/v1beta1"
)

func TestMissingCSIDaemonSet(t *testing.T) {
t.Run(`valid dynakube specs`, func(t *testing.T) {
assertAllowedResponseWithWarnings(t, &dynatracev1beta1.DynaKube{
ObjectMeta: defaultDynakubeObjectMeta,
Spec: dynatracev1beta1.DynaKubeSpec{
APIURL: testApiUrl,
OneAgent: dynatracev1beta1.OneAgentSpec{
CloudNativeFullStack: &dynatracev1beta1.CloudNativeFullStackSpec{},
},
},
}, &defaultCSIDaemonSet)
})

t.Run(`invalid dynakube specs`, func(t *testing.T) {
assertDeniedResponse(t,
[]string{errorCSIRequired},
&dynatracev1beta1.DynaKube{
ObjectMeta: defaultDynakubeObjectMeta,
Spec: dynatracev1beta1.DynaKubeSpec{
APIURL: testApiUrl,
OneAgent: dynatracev1beta1.OneAgentSpec{
CloudNativeFullStack: &dynatracev1beta1.CloudNativeFullStackSpec{},
},
},
})
})
}
36 changes: 36 additions & 0 deletions webhook/validation/namespace_selector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package validation

import (
"context"

dynatracev1beta1 "github.com/Dynatrace/dynatrace-operator/api/v1beta1"
"github.com/Dynatrace/dynatrace-operator/mapper"
)

const (
errorConflictingNamespaceSelector = `The DynaKube's specification tries to inject into namespaces where another Dynakube already injects into, which is not supported.
Make sure the namespaceSelector doesn't conflict with other Dynakubes namespaceSelector
`
errorConflictingNamespaceSelectorNoSelector = `The DynaKube does not specificy namespaces where it should inject into while another Dynakube already injects into namespaces, which is not supported.
Make sure you have a namespaceSelector doesn't conflict with other Dynakubes namespaceSelector
`
)

func conflictingNamespaceSelector(dv *dynakubeValidator, dynakube *dynatracev1beta1.DynaKube) string {
if !dynakube.NeedAppInjection() {
return ""
}
dkMapper := mapper.NewDynakubeMapper(context.TODO(), dv.clt, dv.apiReader, dynakube.Namespace, dynakube, log)
_, err := dkMapper.MatchingNamespaces()
if err != nil && err.Error() == mapper.ConflictErrorMessage {
if dynakube.NamespaceSelector().MatchExpressions == nil && dynakube.NamespaceSelector().MatchLabels == nil {
log.Info("requested dynakube has conflicting namespaceSelector", "name", dynakube.Name, "namespace", dynakube.Namespace)
return errorConflictingNamespaceSelectorNoSelector
} else {
log.Info("requested dynakube has conflicting namespaceSelector", "name", dynakube.Name, "namespace", dynakube.Namespace)
return errorConflictingNamespaceSelector
}

}
return ""
}
Loading