func getCFSSLPodSpec(statefulSetName, dataVolumeName, secretName string, cr *apiv1alpha1.Astarte) v1.PodSpec {
	cfsslImage := getAstarteImageFromChannel("cfssl", deps.GetDefaultVersionForCFSSL(cr.Spec.Version), cr)
	if cr.Spec.CFSSL.Image != "" {
		cfsslImage = cr.Spec.CFSSL.Image
	} else if cr.Spec.CFSSL.Version != "" {
		cfsslImage = getAstarteImageFromChannel("cfssl", cr.Spec.CFSSL.Version, cr)
	}
	resources := v1.ResourceRequirements{}
	if cr.Spec.CFSSL.Resources != nil {
		resources = *cr.Spec.CFSSL.Resources
	}
	volumeMounts := []v1.VolumeMount{
		{
			Name:      "config",
			MountPath: "/etc/cfssl",
		},
	}
	volumes := []v1.Volume{
		{
			Name: "config",
			VolumeSource: v1.VolumeSource{
				ConfigMap: &v1.ConfigMapVolumeSource{
					LocalObjectReference: v1.LocalObjectReference{Name: statefulSetName + "-config"},
				},
			},
		},
	}
	env := []v1.EnvVar{}
	if dataVolumeName != "" {
		volumeMounts = append(volumeMounts, v1.VolumeMount{
			Name:      dataVolumeName,
			MountPath: "/data",
		})
	} else {
		volumeMounts = append(volumeMounts, v1.VolumeMount{
			Name:      "devices-ca",
			MountPath: "/devices-ca",
			ReadOnly:  true,
		})
		volumes = append(volumes, v1.Volume{
			Name: "devices-ca",
			VolumeSource: v1.VolumeSource{
				Secret: &v1.SecretVolumeSource{
					SecretName: secretName,
				},
			},
		})
		env = append(env, v1.EnvVar{
			Name:  "KUBERNETES",
			Value: "1",
		}, v1.EnvVar{
			Name:  "CFSSL_CA_CERTIFICATE",
			Value: "/devices-ca/" + v1.TLSCertKey,
		}, v1.EnvVar{
			Name:  "CFSSL_CA_PRIVATE_KEY",
			Value: "/devices-ca/" + v1.TLSPrivateKeyKey,
		})
		if cr.Spec.CFSSL.DBConfig != nil {
			env = append(env, v1.EnvVar{
				Name:  "CFSSL_USE_DB",
				Value: "1",
			})
		}
	}
	ps := v1.PodSpec{
		TerminationGracePeriodSeconds: pointy.Int64(30),
		ImagePullSecrets:              cr.Spec.ImagePullSecrets,
		Containers: []v1.Container{
			{
				Name:            "cfssl",
				VolumeMounts:    volumeMounts,
				Env:             env,
				Image:           cfsslImage,
				ImagePullPolicy: getImagePullPolicy(cr),
				Ports: []v1.ContainerPort{
					{Name: "http", ContainerPort: 8080},
				},
				ReadinessProbe: getCFSSLProbe(cr),
				LivenessProbe:  getCFSSLProbe(cr),
				Resources:      resources,
			},
		},
		Volumes: volumes,
	}
	return ps
}
TODO: Deprecate dataVolumeName and all the jargon when we won't support < 1.0 anymore
package reconcile
import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	cfsslcsr "github.com/cloudflare/cfssl/csr"
	"github.com/cloudflare/cfssl/initca"
	"github.com/openlyinc/pointy"
	appsv1 "k8s.io/api/apps/v1"
	v1 "k8s.io/api/core/v1"
	kerrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/intstr"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
	commontypes "github.com/astarte-platform/astarte-kubernetes-operator/apis/api/commontypes"
	apiv1alpha1 "github.com/astarte-platform/astarte-kubernetes-operator/apis/api/v1alpha1"
	"github.com/astarte-platform/astarte-kubernetes-operator/lib/deps"
	"github.com/astarte-platform/astarte-kubernetes-operator/lib/misc"
	"github.com/astarte-platform/astarte-kubernetes-operator/version"
)
func EnsureCFSSL(cr *apiv1alpha1.Astarte, c client.Client, scheme *runtime.Scheme) error {
	if err := validateCFSSLDefinition(cr.Spec.CFSSL); err != nil {
		return err
	}
	if version.CheckConstraintAgainstAstarteVersion("< 1.0.0", cr.Spec.Version) == nil {
		return ensureCFSSLStatefulSet(cr, c, scheme)
	}
	return ensureCFSSLDeployment(cr, c, scheme)
}
func ensureCFSSLDeployment(cr *apiv1alpha1.Astarte, c client.Client, scheme *runtime.Scheme) error {
	deploymentName := cr.Name + "-cfssl"
	labels := map[string]string{"app": deploymentName}
	if !pointy.BoolValue(cr.Spec.CFSSL.Deploy, true) {
		log.Info("Skipping CFSSL Deployment")
		theDeployment := &appsv1.Deployment{}
		err := c.Get(context.TODO(), types.NamespacedName{Name: deploymentName, Namespace: cr.Namespace}, theDeployment)
		if err == nil {
			log.Info("Deleting previously existing CFSSL Deployment, which is no longer needed")
			if err = c.Delete(context.TODO(), theDeployment); err != nil {
				return err
			}
		}
		return nil
	}
	if err := ensureCFSSLCommonSidecars(deploymentName, labels, cr, c, scheme); err != nil {
		return err
	}
	caSecretName := cr.Name + "-devices-ca"
	if cr.Spec.CFSSL.CASecret.Name != "" {
		caSecretName = cr.Spec.CFSSL.CASecret.Name
	} else if err := ensureCFSSLCASecret(caSecretName, cr, c, scheme); err != nil {
		return err
	}
	if err := ensureCFSSLCAProxySecret(caSecretName, cr.Name+"-cfssl-ca", cr, c, scheme); err != nil {
		return err
	}
	deploymentSpec := appsv1.DeploymentSpec{
		Selector: &metav1.LabelSelector{
			MatchLabels: labels,
		},
		Template: v1.PodTemplateSpec{
			ObjectMeta: metav1.ObjectMeta{
				Labels: labels,
			},
			Spec: getCFSSLPodSpec(deploymentName, "", caSecretName, cr),
		},
	}
	cfsslDeployment := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: deploymentName, Namespace: cr.Namespace}}
	result, err := controllerutil.CreateOrUpdate(context.TODO(), c, cfsslDeployment, func() error {
		if e := controllerutil.SetControllerReference(cr, cfsslDeployment, scheme); e != nil {
			return e
		}
		cfsslDeployment.Spec = deploymentSpec
		cfsslDeployment.Spec.Replicas = pointy.Int32(1)
		return nil
	})
	if err != nil {
		return err
	}
	misc.LogCreateOrUpdateOperationResult(log, result, cr, cfsslDeployment)
	return nil
}
func ensureCFSSLStatefulSet(cr *apiv1alpha1.Astarte, c client.Client, scheme *runtime.Scheme) error {
	statefulSetName := cr.Name + "-cfssl"
	labels := map[string]string{"app": statefulSetName}
	if !pointy.BoolValue(cr.Spec.CFSSL.Deploy, true) {
		log.Info("Skipping CFSSL Deployment")
		theStatefulSet := &appsv1.StatefulSet{}
		err := c.Get(context.TODO(), types.NamespacedName{Name: statefulSetName, Namespace: cr.Namespace}, theStatefulSet)
		if err == nil {
			log.Info("Deleting previously existing CFSSL StatefulSet, which is no longer needed")
			if err = c.Delete(context.TODO(), theStatefulSet); err != nil {
				return err
			}
		}
		return nil
	}
	if err := ensureCFSSLCommonSidecars(statefulSetName, labels, cr, c, scheme); err != nil {
		return err
	}
	dataVolumeName, persistentVolumeClaim := computePersistentVolumeClaim(statefulSetName+"-data", resource.NewScaledQuantity(4, resource.Giga),
		cr.Spec.CFSSL.Storage, cr)
	statefulSetSpec := appsv1.StatefulSetSpec{
		ServiceName: statefulSetName,
		Selector: &metav1.LabelSelector{
			MatchLabels: labels,
		},
		Template: v1.PodTemplateSpec{
			ObjectMeta: metav1.ObjectMeta{
				Labels: labels,
			},
			Spec: getCFSSLPodSpec(statefulSetName, dataVolumeName, "", cr),
		},
	}
	if persistentVolumeClaim != nil {
		statefulSetSpec.VolumeClaimTemplates = []v1.PersistentVolumeClaim{*persistentVolumeClaim}
	}
	cfsslStatefulSet := &appsv1.StatefulSet{ObjectMeta: metav1.ObjectMeta{Name: statefulSetName, Namespace: cr.Namespace}}
	result, err := controllerutil.CreateOrUpdate(context.TODO(), c, cfsslStatefulSet, func() error {
		if e := controllerutil.SetControllerReference(cr, cfsslStatefulSet, scheme); e != nil {
			return e
		}
		cfsslStatefulSet.Spec = statefulSetSpec
		cfsslStatefulSet.Spec.Replicas = pointy.Int32(1)
		return nil
	})
	if err != nil {
		return err
	}
	misc.LogCreateOrUpdateOperationResult(log, result, cr, cfsslStatefulSet)
	return nil
}
func ensureCFSSLCommonSidecars(resourceName string, labels map[string]string, cr *apiv1alpha1.Astarte, c client.Client, scheme *runtime.Scheme) error {
	service := &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: resourceName, Namespace: cr.Namespace}}
	if result, err := controllerutil.CreateOrUpdate(context.TODO(), c, service, func() error {
		if err := controllerutil.SetControllerReference(cr, service, scheme); err != nil {
			return err
		}
		service.Spec.Type = v1.ServiceTypeClusterIP
		service.Spec.Ports = []v1.ServicePort{
			{
				Name:       "http",
				Port:       80,
				TargetPort: intstr.FromString("http"),
				Protocol:   v1.ProtocolTCP,
			},
		}
		service.Spec.Selector = labels
		return nil
	}); err == nil {
		misc.LogCreateOrUpdateOperationResult(log, result, cr, service)
	} else {
		return err
	}
	configMap, err := getCFSSLConfigMapData(cr)
	if err != nil {
		return err
	}
	if _, e := misc.ReconcileConfigMap(resourceName+"-config", configMap, cr, c, scheme, log); e != nil {
		return e
	}
	return nil
}
func validateCFSSLDefinition(cfssl commontypes.AstarteCFSSLSpec) error {
	if !pointy.BoolValue(cfssl.Deploy, true) && cfssl.URL == "" {
		return errors.New("When not deploying CFSSL, the 'url' must be specified")
	}
	return nil
}
func getCFSSLProbe(cr *apiv1alpha1.Astarte) *v1.Probe {
	if version.CheckConstraintAgainstAstarteComponentVersion("< 0.11.0", cr.Spec.CFSSL.Version, cr.Spec.Version) == nil {
		return &v1.Probe{
			Handler:             v1.Handler{TCPSocket: &v1.TCPSocketAction{Port: intstr.FromString("http")}},
			InitialDelaySeconds: 10,
			TimeoutSeconds:      5,
			PeriodSeconds:       30,
			FailureThreshold:    3,
		}
	}
	return &v1.Probe{
		Handler:             v1.Handler{HTTPGet: &v1.HTTPGetAction{Path: "/api/v1/cfssl/health", Port: intstr.FromString("http")}},
		InitialDelaySeconds: 10,
		TimeoutSeconds:      5,
		PeriodSeconds:       20,
		FailureThreshold:    3,
	}
}