penguin/monok8s

k8s image for Mono Gateway Dev Kit

clitools/pkg/render/controller.go

raw ยท 6932 bytes

package render

import (
	"bytes"
	"fmt"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/serializer/json"
	"k8s.io/apimachinery/pkg/util/intstr"

	monov1alpha1 "example.com/monok8s/pkg/apis/monok8s/v1alpha1"
	templates "example.com/monok8s/pkg/templates"
)

func RenderControllerDeployments(namespace string) (string, error) {
	vals := templates.LoadTemplateValuesFromEnv()

	labels := map[string]string{
		"app.kubernetes.io/name":       monov1alpha1.ControlAgentName,
		"app.kubernetes.io/component":  "controller",
		"app.kubernetes.io/part-of":    "monok8s",
		"app.kubernetes.io/managed-by": "ctl",
	}

	objs := []runtime.Object{
		buildControllerServiceAccount(namespace, labels),
		buildControllerClusterRole(labels),
		buildControllerClusterRoleBinding(namespace, labels),
		buildControllerDeployment(vals, namespace, labels),
	}

	s := runtime.NewScheme()
	_ = corev1.AddToScheme(s)
	_ = rbacv1.AddToScheme(s)
	_ = appsv1.AddToScheme(s)

	serializer := json.NewYAMLSerializer(json.DefaultMetaFactory, s, s)

	var buf bytes.Buffer

	for i, obj := range objs {
		if i > 0 {
			if _, err := fmt.Fprintln(&buf, "---"); err != nil {
				return "", err
			}
		}
		if err := serializer.Encode(obj, &buf); err != nil {
			return "", err
		}
	}

	return buf.String(), nil
}

func buildControllerServiceAccount(namespace string, labels map[string]string) *corev1.ServiceAccount {

	automount := true

	return &corev1.ServiceAccount{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "v1",
			Kind:       "ServiceAccount",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      monov1alpha1.ControlAgentName,
			Namespace: namespace,
			Labels:    labels,
		},
		AutomountServiceAccountToken: &automount,
	}
}

func buildControllerClusterRole(labels map[string]string) *rbacv1.ClusterRole {
	wantRules := []rbacv1.PolicyRule{
		{
			APIGroups: []string{monov1alpha1.Group},
			Resources: []string{"osupgrades"},
			Verbs:     []string{"get", "list", "watch"},
		},
		{
			APIGroups: []string{monov1alpha1.Group},
			Resources: []string{"osupgrades/status"},
			Verbs:     []string{"get", "patch", "update"},
		},
		{
			APIGroups: []string{monov1alpha1.Group},
			Resources: []string{"osupgradeprogresses"},
			Verbs:     []string{"get", "create"},
		},
		{
			APIGroups: []string{monov1alpha1.Group},
			Resources: []string{"osupgradeprogresses/status"},
			Verbs:     []string{"update"},
		},
		{
			APIGroups: []string{""},
			Resources: []string{"nodes"},
			Verbs:     []string{"get", "list"},
		},
	}

	return &rbacv1.ClusterRole{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "rbac.authorization.k8s.io/v1",
			Kind:       "ClusterRole",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:   monov1alpha1.ControlAgentName,
			Labels: labels,
		},
		Rules: wantRules,
	}
}

func buildControllerClusterRoleBinding(namespace string, labels map[string]string) *rbacv1.ClusterRoleBinding {

	wantSubjects := []rbacv1.Subject{
		{
			Kind:      "ServiceAccount",
			Name:      monov1alpha1.ControlAgentName,
			Namespace: namespace,
		},
	}

	wantRoleRef := rbacv1.RoleRef{
		APIGroup: rbacv1.GroupName,
		Kind:     "ClusterRole",
		Name:     monov1alpha1.ControlAgentName,
	}

	return &rbacv1.ClusterRoleBinding{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "rbac.authorization.k8s.io/v1",
			Kind:       "ClusterRoleBinding",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:   monov1alpha1.ControlAgentName,
			Labels: labels,
		},
		Subjects: wantSubjects,
		RoleRef:  wantRoleRef,
	}
}

func buildControllerDeployment(tVals templates.TemplateValues, namespace string, labels map[string]string) *appsv1.Deployment {
	replicas := int32(1)

	selectorLabels := map[string]string{
		"app.kubernetes.io/name":      monov1alpha1.ControlAgentName,
		"app.kubernetes.io/component": "controller",
	}

	podLabels := mergeStringMaps(labels, selectorLabels)

	runAsNonRoot := true
	allowPrivilegeEscalation := false

	return &appsv1.Deployment{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "apps/v1",
			Kind:       "Deployment",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      monov1alpha1.ControlAgentName,
			Namespace: namespace,
			Labels:    labels,
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: &replicas,
			Selector: &metav1.LabelSelector{
				MatchLabels: selectorLabels,
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: podLabels,
				},
				Spec: corev1.PodSpec{
					ServiceAccountName: monov1alpha1.ControlAgentName,
					Containers: []corev1.Container{
						{
							Name:            "controller",
							Image:           fmt.Sprintf("registry.local/control-agent:%s", tVals.KubernetesVersion),
							ImagePullPolicy: corev1.PullIfNotPresent,
							Args: []string{
								"controller",
								"--namespace",
								namespace,
							},
							Env: []corev1.EnvVar{
								{
									Name: "POD_NAME",
									ValueFrom: &corev1.EnvVarSource{
										FieldRef: &corev1.ObjectFieldSelector{
											APIVersion: "v1",
											FieldPath:  "metadata.name",
										},
									},
								},
								{
									Name: "POD_NAMESPACE",
									ValueFrom: &corev1.EnvVarSource{
										FieldRef: &corev1.ObjectFieldSelector{
											APIVersion: "v1",
											FieldPath:  "metadata.namespace",
										},
									},
								},
								{
									Name: "NODE_NAME",
									ValueFrom: &corev1.EnvVarSource{
										FieldRef: &corev1.ObjectFieldSelector{
											APIVersion: "v1",
											FieldPath:  "spec.nodeName",
										},
									},
								},
							},
							Ports: []corev1.ContainerPort{
								{
									Name:          "http",
									ContainerPort: 8080,
									Protocol:      corev1.ProtocolTCP,
								},
								{
									Name:          "https",
									ContainerPort: 8443,
									Protocol:      corev1.ProtocolTCP,
								},
							},
							LivenessProbe: &corev1.Probe{
								ProbeHandler: corev1.ProbeHandler{
									HTTPGet: &corev1.HTTPGetAction{
										Path: "/healthz",
										Port: intstr.FromString("http"),
									},
								},
							},
							ReadinessProbe: &corev1.Probe{
								ProbeHandler: corev1.ProbeHandler{
									HTTPGet: &corev1.HTTPGetAction{
										Path: "/readyz",
										Port: intstr.FromString("http"),
									},
								},
							},
							SecurityContext: &corev1.SecurityContext{
								RunAsNonRoot:             &runAsNonRoot,
								AllowPrivilegeEscalation: &allowPrivilegeEscalation,
							},
						},
					},
				},
			},
		},
	}
}

func mergeStringMaps(maps ...map[string]string) map[string]string {
	var total int
	for _, m := range maps {
		total += len(m)
	}
	if total == 0 {
		return nil
	}

	out := make(map[string]string, total)
	for _, m := range maps {
		for k, v := range m {
			out[k] = v
		}
	}
	return out
}