internal/resource/statefulset_utils.go (240 lines of code) (raw):

package resource import ( "fmt" . "git.jetbrains.team/tch/teamcity-operator/api/v1beta1" v1 "k8s.io/api/apps/v1" v12 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" "strings" ) const ( TEAMCITY_CONTAINER_NAME = "teamcity-server" TEAMCITY_DB_USER_SECRET_KEY = "connectionProperties.user" TEAMCITY_DB_PASSWORD_SECRET_KEY = "connectionProperties.password" TEAMCITY_DB_URL_SECRET_KEY = "connectionUrl" ) func CreateEmptyStatefulSet(name string, namespace string, labels map[string]string) v1.StatefulSet { return v1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, Labels: labels, }, Spec: v1.StatefulSetSpec{ Selector: &metav1.LabelSelector{ MatchLabels: labels, }, Template: v12.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{Labels: labels}, Spec: v12.PodSpec{ InitContainers: []v12.Container{}, Containers: []v12.Container{}, }, }, }, } } func DefaultEnvironmentVariableBuilder(nodeName string, xmxValue string, dataDirPath string, extraServerOpts string) []v12.EnvVar { return []v12.EnvVar{ PodNameEnvVariableBuilder(), PodNamespaceEnvVariableBuilder(), DataDirPathEnvVar(dataDirPath), LogDirPathEnvVar(dataDirPath), ServerMemOptsEnvVar(xmxValue), ServerOptsEnvVar(dataDirPath, nodeName, extraServerOpts), } } func DataDirPathEnvVar(dataDirPath string) v12.EnvVar { return v12.EnvVar{ Name: "TEAMCITY_DATA_PATH", Value: fmt.Sprintf("%s", dataDirPath), } } func LogDirPathEnvVar(dataDirPath string) v12.EnvVar { return v12.EnvVar{ Name: "TEAMCITY_LOGS_PATH", Value: fmt.Sprintf("%s%s", dataDirPath, "/logs"), } } func ServerMemOptsEnvVar(xmxValue string) v12.EnvVar { return v12.EnvVar{ Name: "TEAMCITY_SERVER_MEM_OPTS", Value: fmt.Sprintf("%s%s", "-Xmx", xmxValue), } } func ServerOptsEnvVar(dataDirPath string, nodeName string, extraServerOpts string) v12.EnvVar { return v12.EnvVar{ Name: "TEAMCITY_SERVER_OPTS", Value: "-XX:+HeapDumpOnOutOfMemoryError -XX:+DisableExplicitGC" + fmt.Sprintf(" -XX:HeapDumpPath=%s%s%s", dataDirPath, "/memoryDumps/", nodeName) + fmt.Sprintf(" -Dteamcity.server.nodeId=%s", nodeName) + fmt.Sprintf(" -Dteamcity.server.rootURL=http://$(POD_NAME).$(POD_NAMESPACE)") + extraServerOpts, } } func XmxValueCalculator(percentage int64, requestedMemoryValue int64) (xmxValue string) { ratio := float64(percentage) / 100 xmxValue = resource.NewQuantity(int64(ratio*float64(requestedMemoryValue)), resource.DecimalSI).String() return } func LifecycleOptionsBuilder() (lifecycle *v12.Lifecycle) { lifecycle = &v12.Lifecycle{ PostStart: nil, PreStop: &v12.LifecycleHandler{ Exec: &v12.ExecAction{ Command: []string{"/bin/sh", "-c", "/opt/teamcity/bin/shutdown.sh"}, }, HTTPGet: nil, }, } return } func ConvertStartUpPropertiesToServerOptions(startupProperties map[string]string) (res string) { sortedKeys := SortKeysAlphabeticallyInMap(startupProperties) for _, k := range sortedKeys { res += fmt.Sprintf(" -D%s=%s", k, startupProperties[k]) } return } func PodNameEnvVariableBuilder() v12.EnvVar { return v12.EnvVar{ Name: "POD_NAME", ValueFrom: &v12.EnvVarSource{ FieldRef: &v12.ObjectFieldSelector{ FieldPath: "metadata.name", }, }, } } func PodNamespaceEnvVariableBuilder() v12.EnvVar { return v12.EnvVar{ Name: "POD_NAMESPACE", ValueFrom: &v12.EnvVarSource{ FieldRef: &v12.ObjectFieldSelector{ FieldPath: "metadata.namespace", }, }, } } func BuildVolumesFromPersistentVolumeClaims(persistentVolumeClaims []CustomPersistentVolumeClaim) (volumes []v12.Volume) { for _, claim := range persistentVolumeClaims { volumes = append(volumes, createVolumeFromCustomPersistentVolumeClaim(claim)) } return } func createVolumeFromCustomPersistentVolumeClaim(persistentVolumeClaim CustomPersistentVolumeClaim) v12.Volume { return v12.Volume{Name: persistentVolumeClaim.Name, VolumeSource: v12.VolumeSource{ PersistentVolumeClaim: &v12.PersistentVolumeClaimVolumeSource{ ClaimName: persistentVolumeClaim.Name}, }, } } func BuildVolumeMountsFromPersistentVolumeClaims(persistentVolumeClaims []CustomPersistentVolumeClaim) (volumeMounts []v12.VolumeMount) { for _, claim := range persistentVolumeClaims { volumeMounts = append(volumeMounts, createVolumeMountFromCustomPersistentVolumeClaim(claim)) } return } func createVolumeMountFromCustomPersistentVolumeClaim(persistentVolumeClaim CustomPersistentVolumeClaim) v12.VolumeMount { return v12.VolumeMount{Name: persistentVolumeClaim.VolumeMount.Name, MountPath: persistentVolumeClaim.VolumeMount.MountPath} } func ConfigureContainer(instance *TeamCity, node Node, container *v12.Container) { container.Name = TEAMCITY_CONTAINER_NAME container.Image = instance.Spec.Image container.ImagePullPolicy = v12.PullIfNotPresent container.Lifecycle = LifecycleOptionsBuilder() container.LivenessProbe = &node.Spec.LivenessProbeSettings container.ReadinessProbe = &node.Spec.ReadinessProbeSettings container.StartupProbe = &node.Spec.StartupProbeSettings container.Resources.Limits = node.Spec.Limits container.Resources.Requests = node.Spec.Requests container.Ports = []v12.ContainerPort{instance.Spec.TeamCityServerPort} container.LivenessProbe.ProbeHandler.HTTPGet = &instance.Spec.ReadinessEndpoint container.ReadinessProbe.ProbeHandler.HTTPGet = &instance.Spec.ReadinessEndpoint container.StartupProbe.ProbeHandler.HTTPGet = &instance.Spec.HealthEndpoint allPersistentVolumeClaims := instance.GetAllCustomPersistentVolumeClaim() volumeMounts := BuildVolumeMountsFromPersistentVolumeClaims(allPersistentVolumeClaims) container.VolumeMounts = volumeMounts envVars := BuildEnvVariablesFromGlobalAndNodeSpecificSettings(instance, node) container.Env = envVars } func ConfigureStatefulSet(instance *TeamCity, node Node, current *v1.StatefulSet) { allPersistentVolumeClaims := instance.GetAllCustomPersistentVolumeClaim() volumes := BuildVolumesFromPersistentVolumeClaims(allPersistentVolumeClaims) current.Spec.Replicas = pointer.Int32(1) current.Spec.Template.Annotations = node.Annotations current.Spec.Template.Spec.Volumes = volumes current.Spec.Template.Spec.InitContainers = node.Spec.InitContainers current.Spec.Template.Spec.NodeSelector = node.Spec.NodeSelector current.Spec.Template.Spec.Affinity = &node.Spec.Affinity current.Spec.Template.Spec.SecurityContext = &node.Spec.PodSecurityContext current.Spec.Template.Spec.ServiceAccountName = instance.Spec.ServiceAccount.Name current.Spec.Template.Spec.DeprecatedServiceAccount = "" } func ConvertNodeEnvVars(env map[string]string) (envVars []v12.EnvVar) { for k, v := range env { var envVar = v12.EnvVar{ Name: k, Value: v, ValueFrom: nil, } envVars = append(envVars, envVar) } return envVars } func DatabaseEnvVarBuilder(databaseSecretName string) []v12.EnvVar { return []v12.EnvVar{ { Name: "TEAMCITY_DB_USER", ValueFrom: &v12.EnvVarSource{ SecretKeyRef: &v12.SecretKeySelector{ LocalObjectReference: v12.LocalObjectReference{Name: databaseSecretName}, Key: TEAMCITY_DB_USER_SECRET_KEY, }, }, }, { Name: "TEAMCITY_DB_PASSWORD", ValueFrom: &v12.EnvVarSource{ SecretKeyRef: &v12.SecretKeySelector{ LocalObjectReference: v12.LocalObjectReference{Name: databaseSecretName}, Key: TEAMCITY_DB_PASSWORD_SECRET_KEY, }, }, }, { Name: "TEAMCITY_DB_URL", ValueFrom: &v12.EnvVarSource{ SecretKeyRef: &v12.SecretKeySelector{ LocalObjectReference: v12.LocalObjectReference{Name: databaseSecretName}, Key: TEAMCITY_DB_URL_SECRET_KEY, }, }, }, } } func BuildEnvVariablesFromGlobalAndNodeSpecificSettings(instance *TeamCity, node Node) []v12.EnvVar { dataDirPath := instance.DataDirPath() extraServerOpts := ConvertStartUpPropertiesToServerOptions(instance.Spec.StartupPropertiesConfig) var responsibilities string if len(node.Spec.Responsibilities) > 0 { responsibilities = ConvertResponsibilitiesToServerOptions(node.Spec.Responsibilities) } extraServerOpts = responsibilities + extraServerOpts xmxValue := XmxValueCalculator(instance.Spec.XmxPercentage, node.Spec.Requests.Memory().Value()) envVars := DefaultEnvironmentVariableBuilder(node.Name, xmxValue, dataDirPath, extraServerOpts) nodeSpecificEnvVars := ConvertNodeEnvVars(node.Spec.Env) envVars = append(envVars, nodeSpecificEnvVars...) if instance.DatabaseSecretProvided() { databaseEnvVars := DatabaseEnvVarBuilder(instance.Spec.DatabaseSecret.Secret) envVars = append(envVars, databaseEnvVars...) } return envVars } func ConvertResponsibilitiesToServerOptions(responsibilities []string) string { stringResponsibilities := strings.Join(responsibilities, ",") return fmt.Sprintf(" -Dteamcity.server.responsibilities=%s", stringResponsibilities) }