func validateCardinality(
	cr *kdv1.KubeDirectorCluster,
	appCR *kdv1.KubeDirectorApp,
	valErrors []string,
	patches []clusterPatchSpec,
) ([]string, []clusterPatchSpec) {
	anyError := false
	totalMembers := int32(0)
	numRoles := len(cr.Spec.Roles)
	rolesPath := field.NewPath("spec", "roles")
	for i := 0; i < numRoles; i++ {
		role := &(cr.Spec.Roles[i])
		appRole := catalog.GetRoleFromID(appCR, role.Name)
		if appRole == nil {
			continue
		}
		cardinality, isScaleOut := catalog.GetRoleCardinality(appRole)
		if role.Members != nil {
			var invalidMemberCount = false
			if isScaleOut {
				if *(role.Members) < cardinality {
					invalidMemberCount = true
				}
			} else {
				if *(role.Members) != cardinality {
					invalidMemberCount = true
				}
			}
			if invalidMemberCount {
				anyError = true
				valErrors = append(
					valErrors,
					fmt.Sprintf(
						invalidCardinality,
						role.Name,
						*(role.Members),
						appRole.Cardinality,
					),
				)
			}
		} else {
			role.Members = &cardinality
			patches = append(
				patches,
				clusterPatchSpec{
					Op:   "add",
					Path: "/spec/roles/" + strconv.Itoa(i) + "/members",
					Value: clusterPatchValue{
						ValueInt: role.Members,
					},
				},
			)
		}
		totalMembers += *role.Members
		if totalMembers > maxKDMembers {
			anyError = true
			valErrors = append(
				valErrors,
				fmt.Sprint(
					maxMemberLimit,
					maxKDMembers,
				),
			)
			break
		}
		var anyLabelAnnError bool
		valErrors, anyLabelAnnError = validateLabelsAndAnnotations(
			rolesPath.Index(i),
			role.PodLabels,
			role.PodAnnotations,
			role.ServiceLabels,
			role.ServiceAnnotations,
			valErrors,
		)
		anyError = anyError || anyLabelAnnError
	}
	if anyError {
		var emptyPatchList []clusterPatchSpec
		return valErrors, emptyPatchList
	}
	return valErrors, patches
}
validateCardinality checks the member count specified for a role in the
cluster CR against the cardinality value from the app CR. Any generated
error messages will be added to the input list and returned. If there were
no errors generated, a list of PATCH specs will also be returned for the
purpose of populating default members values as necessary.
package validator
import (
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"
	kdv1 "github.com/bluek8s/kubedirector/pkg/apis/kubedirector/v1beta1"
	"github.com/bluek8s/kubedirector/pkg/catalog"
	"github.com/bluek8s/kubedirector/pkg/controller/kubedirectorcluster"
	"github.com/bluek8s/kubedirector/pkg/observer"
	"github.com/bluek8s/kubedirector/pkg/secretkeys"
	"github.com/bluek8s/kubedirector/pkg/shared"
	"k8s.io/api/admission/v1beta1"
	v1 "k8s.io/api/authentication/v1"
	sar "k8s.io/api/authorization/v1"
	"k8s.io/apimachinery/pkg/api/equality"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/validation/field"
)
type secretValidateResult int
const maxKDMembers = 1000
const (
	secretIsValid secretValidateResult = iota
	secretPrefixNotMatched
	secretNotFound
)
type clusterPatchSpec struct {
	Op    string            `json:"op"`
	Path  string            `json:"path"`
	Value clusterPatchValue `json:"value"`
}
type clusterPatchValue struct {
	ValueInt           *int32
	ValueStr           *string
	ValueClusterStatus *kdv1.KubeDirectorClusterStatus
	ValueKDSecret      *kdv1.KDSecret
	ValueSecretKey     *kdv1.SecretKey
	ValueDict          *dictValue
}
func (obj clusterPatchValue) MarshalJSON() ([]byte, error) {
	if obj.ValueInt != nil {
		return json.Marshal(obj.ValueInt)
	}
	if obj.ValueKDSecret != nil {
		return json.Marshal(obj.ValueKDSecret)
	}
	if obj.ValueClusterStatus != nil {
		return json.Marshal(obj.ValueClusterStatus)
	}
	if obj.ValueSecretKey != nil {
		return json.Marshal(obj.ValueSecretKey)
	}
	if obj.ValueDict != nil {
		return json.Marshal(obj.ValueDict)
	}
	return json.Marshal(obj.ValueStr)
}
func validateSpecChange(
	cr *kdv1.KubeDirectorCluster,
	prevCr *kdv1.KubeDirectorCluster,
	valErrors []string,
	patches []clusterPatchSpec,
) ([]string, []clusterPatchSpec) {
	if cr.Status == nil {
		valErrors = append(
			valErrors,
			multipleSpecChange,
		)
		return valErrors, patches
	}
	for _, roleStatus := range cr.Status.Roles {
		for _, memberStatus := range roleStatus.Members {
			if len(memberStatus.StateDetail.PendingNotifyCmds) != 0 {
				valErrors = append(
					valErrors,
					pendingNotifies,
				)
				return valErrors, patches
			}
		}
	}
	stringStateModified := string(kubedirectorcluster.ClusterSpecModified)
	if cr.Status.State == stringStateModified {
		valErrors = append(
			valErrors,
			multipleSpecChange,
		)
		return valErrors, patches
	}
	newState := stringStateModified
	patches = append(
		patches,
		clusterPatchSpec{
			Op:   "replace",
			Path: "/status/state",
			Value: clusterPatchValue{
				ValueStr: &newState,
			},
		},
	)
	return valErrors, patches
}