pkg/apis/agent/v1alpha1/validations.go (252 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.
package v1alpha1
import (
"fmt"
"reflect"
"strings"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/util/validation/field"
commonv1 "github.com/elastic/cloud-on-k8s/v3/pkg/apis/common/v1"
"github.com/elastic/cloud-on-k8s/v3/pkg/controller/common/version"
)
var (
defaultChecks = []func(*Agent) field.ErrorList{
checkNoUnknownFields,
checkNameLength,
checkSupportedVersion,
checkAtMostOneDeploymentOption,
checkAtMostOneDefaultESRef,
checkESRefsNamed,
checkSingleConfigSource,
checkSpec,
checkEmptyConfigForFleetMode,
checkFleetServerOnlyInFleetMode,
checkHTTPConfigOnlyForFleetServer,
checkFleetServerOrFleetServerRef,
checkReferenceSetForMode,
checkSingleESRefInFleetMode,
checkAssociations,
}
updateChecks = []func(old, curr *Agent) field.ErrorList{
checkNoDowngrade,
checkPVCchanges,
}
)
const (
pvcImmutableMsg = "Volume claim templates cannot be modified"
)
func checkNoUnknownFields(a *Agent) field.ErrorList {
return commonv1.NoUnknownFields(a, a.ObjectMeta)
}
func checkNameLength(a *Agent) field.ErrorList {
return commonv1.CheckNameLength(a)
}
func checkSupportedVersion(a *Agent) field.ErrorList {
if a.Spec.FleetModeEnabled() {
return commonv1.CheckSupportedStackVersion(a.Spec.Version, version.SupportedFleetModeAgentVersions)
}
return commonv1.CheckSupportedStackVersion(a.Spec.Version, version.SupportedAgentVersions)
}
func checkIfVersionDeprecated(a *Agent) (string, field.ErrorList) {
return commonv1.CheckDeprecatedStackVersion(a.Spec.Version)
}
func checkAtMostOneDeploymentOption(a *Agent) field.ErrorList {
var enabledSpecsNames []string
if a.Spec.DaemonSet != nil {
enabledSpecsNames = append(enabledSpecsNames, "daemonSet")
}
if a.Spec.Deployment != nil {
enabledSpecsNames = append(enabledSpecsNames, "deployment")
}
if a.Spec.StatefulSet != nil {
enabledSpecsNames = append(enabledSpecsNames, "statefulSet")
}
if enabledSpecsLen := len(enabledSpecsNames); enabledSpecsLen > 1 {
msg := fmt.Sprintf("Specify at most one of [%s]", strings.Join(enabledSpecsNames, ", "))
errList := make(field.ErrorList, enabledSpecsLen)
for index, specName := range enabledSpecsNames {
errList[index] = field.Forbidden(field.NewPath("spec").Child(specName), msg)
}
return errList
}
return nil
}
func checkAtMostOneDefaultESRef(a *Agent) field.ErrorList {
var found int
for _, o := range a.Spec.ElasticsearchRefs {
if o.OutputName == "default" {
found++
}
}
if found > 1 {
return field.ErrorList{
field.Forbidden(field.NewPath("spec").Child("elasticsearchRefs"), "only one elasticsearchRef may have the outputName 'default'"),
}
}
return nil
}
func checkESRefsNamed(a *Agent) field.ErrorList {
if len(a.Spec.ElasticsearchRefs) <= 1 {
// a single output does not need to be named
return nil
}
var notNamed []string
for _, o := range a.Spec.ElasticsearchRefs {
if o.OutputName == "" {
notNamed = append(notNamed, o.NamespacedName().String())
}
}
if len(notNamed) > 0 {
msg := fmt.Sprintf("when declaring multiple refs all have to be named, missing outputName on %v", notNamed)
return field.ErrorList{
field.Forbidden(field.NewPath("spec").Child("elasticsearchRefs"), msg),
}
}
return nil
}
func checkNoDowngrade(prev, curr *Agent) field.ErrorList {
if commonv1.IsConfiguredToAllowDowngrades(curr) {
return nil
}
return commonv1.CheckNoDowngrade(prev.Spec.Version, curr.Spec.Version)
}
// checkPVCchanges ensures no PVCs are changed, as volume claim templates are immutable in StatefulSets.
func checkPVCchanges(current, proposed *Agent) field.ErrorList {
var errs field.ErrorList
if current == nil || proposed == nil {
return errs
}
// need to check if current and proposed are both statefulsets
if current.Spec.StatefulSet == nil || proposed.Spec.StatefulSet == nil {
return errs
}
// checking semantic equality here allows providing PVC storage size with different units (eg. 1Ti vs. 1024Gi).
if !apiequality.Semantic.DeepEqual(current.Spec.StatefulSet.VolumeClaimTemplates, proposed.Spec.StatefulSet.VolumeClaimTemplates) {
errs = append(errs, field.Invalid(field.NewPath("spec").Child("statefulSet.").Child("volumeClaimTemplates"),
proposed.Spec.StatefulSet.VolumeClaimTemplates, pvcImmutableMsg))
}
return errs
}
func checkSingleConfigSource(a *Agent) field.ErrorList {
if a.Spec.Config != nil && a.Spec.ConfigRef != nil {
msg := "Specify at most one of [`config`, `configRef`], not both"
return field.ErrorList{
field.Forbidden(field.NewPath("spec").Child("config"), msg),
field.Forbidden(field.NewPath("spec").Child("configRef"), msg),
}
}
return nil
}
func checkSpec(a *Agent) field.ErrorList {
enabledSpecs := 0
if a.Spec.DaemonSet != nil {
enabledSpecs++
}
if a.Spec.Deployment != nil {
enabledSpecs++
}
if a.Spec.StatefulSet != nil {
enabledSpecs++
}
if enabledSpecs != 1 {
return field.ErrorList{
field.Invalid(field.NewPath("spec"), a.Spec, "either daemonSet or deployment or statefulSet must be specified"),
}
}
return nil
}
func checkEmptyConfigForFleetMode(a *Agent) field.ErrorList {
var errors field.ErrorList
if a.Spec.FleetModeEnabled() {
if a.Spec.Config != nil {
errors = append(errors, field.Invalid(
field.NewPath("spec").Child("config"),
a.Spec.Config,
"remove config, it can't be set in fleet mode",
))
}
if a.Spec.ConfigRef != nil {
errors = append(errors, field.Invalid(
field.NewPath("spec").Child("configRef"),
a.Spec.ConfigRef,
"remove configRef, it can't be set in fleet mode",
))
}
}
return errors
}
func checkFleetServerOnlyInFleetMode(a *Agent) field.ErrorList {
if a.Spec.StandaloneModeEnabled() && a.Spec.FleetServerEnabled {
return field.ErrorList{field.Invalid(
field.NewPath("spec").Child("fleetServerEnabled"),
a.Spec.FleetServerEnabled,
"disable Fleet Server, it can't be enabled in standalone mode",
)}
}
return nil
}
func checkFleetServerOrFleetServerRef(a *Agent) field.ErrorList {
if a.Spec.FleetServerEnabled && a.Spec.FleetServerRef.IsDefined() {
return field.ErrorList{
field.Invalid(
field.NewPath("spec"),
a.Spec,
"enable Fleet Server or specify Fleet Server reference, not both",
),
}
}
return nil
}
func checkHTTPConfigOnlyForFleetServer(a *Agent) field.ErrorList {
if !a.Spec.FleetServerEnabled && !reflect.DeepEqual(a.Spec.HTTP, commonv1.HTTPConfig{}) {
return field.ErrorList{
field.Invalid(
field.NewPath("spec").Child("http"),
a.Spec.HTTP,
"don't specify http configuration, it can't be set when Fleet Server is not enabled",
),
}
}
return nil
}
func checkReferenceSetForMode(a *Agent) field.ErrorList {
var errors field.ErrorList
if a.Spec.StandaloneModeEnabled() {
if a.Spec.FleetServerRef.IsDefined() {
errors = append(errors, field.Invalid(
field.NewPath("spec").Child("fleetServerRef"),
a.Spec.FleetServerRef,
"don't specify Fleet Server reference, it can't be set in standalone mode",
))
}
if a.Spec.KibanaRef.IsDefined() {
errors = append(errors, field.Invalid(
field.NewPath("spec").Child("kibanaRef"),
a.Spec.KibanaRef,
"don't specify Kibana reference, it can't be set in standalone mode",
))
}
} else if a.Spec.FleetModeEnabled() {
if !a.Spec.FleetServerEnabled && len(a.Spec.ElasticsearchRefs) > 0 {
errors = append(errors, field.Invalid(
field.NewPath("spec").Child("fleetServerEnabled"),
a.Spec.FleetServerEnabled,
"remove Elasticsearch reference, it can't be enabled in fleet mode when Fleet Server is not enabled as well",
))
}
}
return errors
}
func checkSingleESRefInFleetMode(a *Agent) field.ErrorList {
if a.Spec.FleetModeEnabled() && len(a.Spec.ElasticsearchRefs) > 1 {
return field.ErrorList{
field.Invalid(
field.NewPath("spec").Child("elasticsearchRefs"),
a.Spec.HTTP,
"don't specify more than one Elasticsearch reference, this is not supported in fleet mode",
),
}
}
return nil
}
func checkAssociations(a *Agent) field.ErrorList {
err1 := commonv1.CheckAssociationRefs(field.NewPath("spec").Child("elasticsearchRefs"), a.ElasticsearchRefs()...)
err2 := commonv1.CheckAssociationRefs(field.NewPath("spec").Child("kibanaRef"), a.Spec.KibanaRef)
err3 := commonv1.CheckAssociationRefs(field.NewPath("spec").Child("fleetServerRef"), a.Spec.FleetServerRef)
return append(append(err1, err2...), err3...)
}