pkg/controller/common/statefulset/validation.go (67 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 statefulset import ( "context" "errors" "fmt" appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/rand" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/elastic/cloud-on-k8s/v3/pkg/utils/k8s" ulog "github.com/elastic/cloud-on-k8s/v3/pkg/utils/log" ) type PodTemplateError struct { Parent metav1.Object StatefulSet appsv1.StatefulSet Causes []metav1.StatusCause } func (e *PodTemplateError) Error() string { return fmt.Sprintf( "Validation of PodTemplate for StatefulSet %s in %s/%s failed for the following reasons: %v", e.StatefulSet.Name, e.Parent.GetNamespace(), e.Parent.GetName(), e.Causes, ) } // validatePodTemplate validates a Pod Template by issuing a dry-run API request. // This check is performed as "best-effort" for the following reasons: // * It is only supported by the API server starting 1.13 // * There might be some admission webhooks on the validation path that are not compatible with dry-run requests. func validatePodTemplate( ctx context.Context, c k8s.Client, parent metav1.Object, sset appsv1.StatefulSet, ) error { template := sset.Spec.Template // Create a dummy Pod with the pod template dummyPod := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Namespace: sset.GetNamespace(), Name: sset.GetName() + "-dummy-" + rand.String(5), Labels: template.Labels, Annotations: template.Annotations, }, Spec: template.Spec, } if err := c.Create(ctx, dummyPod, client.DryRunAll); err != nil { return toPodTemplateError(ctx, parent, sset, err) } return nil } // toPodTemplateError attempts to extract the meaningful information from the dry run API call by converting the original // error into a PodTemplateError. func toPodTemplateError(ctx context.Context, parent metav1.Object, sset appsv1.StatefulSet, err error) error { var statusErr *k8serrors.StatusError if !errors.As(err, &statusErr) { // Not a Kubernetes API error (e.g. timeout) return err } if statusErr.ErrStatus.Reason == metav1.StatusReasonInvalid { // If the Pod spec is invalid the expected error is 422. // Since "details" is a pointer let's check that it's not nil before going further. if statusErr.ErrStatus.Details == nil { return statusErr } // We are only interested in the causes, other information is not relevant since it is a "dummy" Pod return &PodTemplateError{ Parent: parent, StatefulSet: sset, Causes: statusErr.ErrStatus.Details.Causes, } } // The Kubernetes API returns an error which is not related to the spec. of the Pod. In order to not block // the reconciliation loop we skip the validation. // TODO: Before moving to `common`, this would state `es_name` in place of `name`, indicating that this was an // Elasticsearch pod where validation is being skipped. It would be useful to include the `Kind` of the // parent in this log message. ulog.FromContext(ctx).Info("Pod validation skipped", "namespace", parent.GetNamespace(), "name", parent.GetName(), "error", statusErr) return nil }